Skip to content

Commit

Permalink
[analyzer] Add doc imports to UnlinkedLibraryDirective and report err…
Browse files Browse the repository at this point in the history
…ors.

This CL adds doc import information to UnlinkedLibraryDirective and reports any static errors such as `URI_DOES_NOT_EXIST` from the doc imports.

The doc imports should be treated similarly to actual imports so it uses a lot of the same logic.

Based largely on Sam's WIP work in this CL:
https://dart-review.googlesource.com/c/sdk/+/344340

Bug: #50702
Change-Id: Ic001fd6d4077eea04905288be0424e7b11b2b56c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345361
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
  • Loading branch information
kallentu authored and Commit Queue committed Feb 1, 2024
1 parent 423e769 commit 1aa0575
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 82 deletions.
102 changes: 65 additions & 37 deletions pkg/analyzer/lib/src/dart/analysis/file_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,7 @@ class FileState {
UnlinkedPartOfNameDirective? partOfNameDirective;
UnlinkedPartOfUriDirective? partOfUriDirective;
var augmentations = <UnlinkedAugmentationImportDirective>[];
var docImports = <UnlinkedLibraryImportDirective>[];
var exports = <UnlinkedLibraryExportDirective>[];
var imports = <UnlinkedLibraryImportDirective>[];
var parts = <UnlinkedPartDirective>[];
Expand Down Expand Up @@ -904,8 +905,17 @@ class FileState {
length: uri.length,
),
);
// TODO(srawlins): Add doc imports.
} else if (directive is LibraryDirective) {
var libraryDocComment = directive.documentationComment;
if (libraryDocComment != null) {
for (var docImport in libraryDocComment.docImports) {
var builder = _serializeImport(docImport.import);
docImports.add(builder);
}
}
libraryDirective = UnlinkedLibraryDirective(
docImports: docImports.toFixedList(),
name: directive.name2?.name,
);
} else if (directive is PartDirective) {
Expand Down Expand Up @@ -2073,6 +2083,7 @@ final class LibraryImportWithUriStr<U extends DirectiveUriWithString>

abstract class LibraryOrAugmentationFileKind extends FileKind {
List<AugmentationImportState>? _augmentationImports;
List<LibraryImportState>? _libraryDocImports;
List<LibraryExportState>? _libraryExports;
List<LibraryImportState>? _libraryImports;

Expand Down Expand Up @@ -2110,6 +2121,18 @@ abstract class LibraryOrAugmentationFileKind extends FileKind {
}).toFixedList();
}

/// The import states of each `@docImport` on the library directive.
List<LibraryImportState> get libraryDocImports {
var existingDocImports = _libraryDocImports;
if (existingDocImports != null) return existingDocImports;

var docImports = file.unlinked2.libraryDirective?.docImports
.map(_createLibraryImportState)
.toFixedList();
if (docImports == null) return _libraryDocImports = [];
return _libraryDocImports = docImports;
}

List<LibraryExportState> get libraryExports {
return _libraryExports ??=
file.unlinked2.exports.map<LibraryExportState>((unlinked) {
Expand Down Expand Up @@ -2153,43 +2176,7 @@ abstract class LibraryOrAugmentationFileKind extends FileKind {

List<LibraryImportState> get libraryImports {
return _libraryImports ??=
file.unlinked2.imports.map<LibraryImportState>((unlinked) {
final uris = file._buildNamespaceDirectiveUris(unlinked);
final selectedUri = uris.selected;
switch (selectedUri) {
case DirectiveUriWithFile():
return LibraryImportWithFile(
container: this,
unlinked: unlinked,
selectedUri: selectedUri,
uris: uris,
);
case DirectiveUriWithInSummarySource():
return LibraryImportWithInSummarySource(
unlinked: unlinked,
selectedUri: selectedUri,
uris: uris,
);
case DirectiveUriWithUri():
return LibraryImportWithUri(
unlinked: unlinked,
selectedUri: selectedUri,
uris: uris,
);
case DirectiveUriWithString():
return LibraryImportWithUriStr(
unlinked: unlinked,
selectedUri: selectedUri,
uris: uris,
);
case DirectiveUriWithoutString():
return LibraryImportState(
unlinked: unlinked,
selectedUri: selectedUri,
uris: uris,
);
}
}).toFixedList();
file.unlinked2.imports.map(_createLibraryImportState).toFixedList();
}

/// Collect files that are transitively referenced by this library.
Expand Down Expand Up @@ -2233,6 +2220,7 @@ abstract class LibraryOrAugmentationFileKind extends FileKind {
@override
void dispose() {
_augmentationImports?.disposeAll();
_libraryDocImports?.disposeAll();
_libraryExports?.disposeAll();
_libraryImports?.disposeAll();
super.dispose();
Expand Down Expand Up @@ -2261,6 +2249,46 @@ abstract class LibraryOrAugmentationFileKind extends FileKind {

/// Invalidates the containing [LibraryFileKind] cycle.
void invalidateLibraryCycle() {}

/// Creates a [LibraryImportState] with the given unlinked [directive].
LibraryImportState _createLibraryImportState(
UnlinkedLibraryImportDirective directive) {
final uris = file._buildNamespaceDirectiveUris(directive);
final selectedUri = uris.selected;
switch (selectedUri) {
case DirectiveUriWithFile():
return LibraryImportWithFile(
container: this,
unlinked: directive,
selectedUri: selectedUri,
uris: uris,
);
case DirectiveUriWithInSummarySource():
return LibraryImportWithInSummarySource(
unlinked: directive,
selectedUri: selectedUri,
uris: uris,
);
case DirectiveUriWithUri():
return LibraryImportWithUri(
unlinked: directive,
selectedUri: selectedUri,
uris: uris,
);
case DirectiveUriWithString():
return LibraryImportWithUriStr(
unlinked: directive,
selectedUri: selectedUri,
uris: uris,
);
case DirectiveUriWithoutString():
return LibraryImportState(
unlinked: directive,
selectedUri: selectedUri,
uris: uris,
);
}
}
}

class NamespaceDirectiveUris {
Expand Down
128 changes: 87 additions & 41 deletions pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,55 @@ class LibraryAnalyzer {
_computeConstants(_libraryUnits.values);
}

/// Reports URI-related import directive errors to the [errorReporter].
void _reportImportDirectiveErrors({
required ImportDirectiveImpl directive,
required LibraryImportState state,
required ErrorReporter errorReporter,
}) {
if (state is LibraryImportWithUri) {
final selectedUriStr = state.selectedUri.relativeUriStr;
if (selectedUriStr.startsWith('dart-ext:')) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.USE_OF_NATIVE_EXTENSION,
directive.uri,
);
} else if (state.importedSource == null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.URI_DOES_NOT_EXIST,
directive.uri,
[selectedUriStr],
);
} else if (state is LibraryImportWithFile && !state.importedFile.exists) {
final errorCode = isGeneratedSource(state.importedSource)
? CompileTimeErrorCode.URI_HAS_NOT_BEEN_GENERATED
: CompileTimeErrorCode.URI_DOES_NOT_EXIST;
errorReporter.reportErrorForNode(
errorCode,
directive.uri,
[selectedUriStr],
);
} else if (state.importedLibrarySource == null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.IMPORT_OF_NON_LIBRARY,
directive.uri,
[selectedUriStr],
);
}
} else if (state is LibraryImportWithUriStr) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_URI,
directive.uri,
[state.selectedUri.relativeUriStr],
);
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.URI_WITH_INTERPOLATION,
directive.uri,
);
}
}

void _resolveAugmentationImportDirective({
required AugmentationImportDirectiveImpl? directive,
required AugmentationImportElementImpl element,
Expand Down Expand Up @@ -757,6 +806,21 @@ class LibraryAnalyzer {
);
}
}

final docImports = containerUnit.directives
.whereType<LibraryDirective>()
.firstOrNull
?.documentationComment
?.docImports;
if (docImports != null) {
for (var i = 0; i < docImports.length; i++) {
_resolveLibraryDocImportDirective(
directive: docImports[i].import as ImportDirectiveImpl,
state: containerKind.libraryDocImports[i],
errorReporter: containerErrorReporter,
);
}
}
}

void _resolveFile(FileState file, CompilationUnitImpl unit) {
Expand Down Expand Up @@ -850,6 +914,24 @@ class LibraryAnalyzer {
);
}

/// Resolves the `@docImport` directive URI and reports any import errors of
/// the [directive] to the [errorReporter].
void _resolveLibraryDocImportDirective({
required ImportDirectiveImpl directive,
required LibraryImportState state,
required ErrorReporter errorReporter,
}) {
_resolveNamespaceDirective(
configurationNodes: directive.configurations,
configurationUris: state.uris.configurations,
);
_reportImportDirectiveErrors(
directive: directive,
state: state,
errorReporter: errorReporter,
);
}

void _resolveLibraryExportDirective({
required ExportDirectiveImpl directive,
required LibraryExportElement element,
Expand Down Expand Up @@ -916,47 +998,11 @@ class LibraryAnalyzer {
configurationNodes: directive.configurations,
configurationUris: state.uris.configurations,
);
if (state is LibraryImportWithUri) {
final selectedUriStr = state.selectedUri.relativeUriStr;
if (selectedUriStr.startsWith('dart-ext:')) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.USE_OF_NATIVE_EXTENSION,
directive.uri,
);
} else if (state.importedSource == null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.URI_DOES_NOT_EXIST,
directive.uri,
[selectedUriStr],
);
} else if (state is LibraryImportWithFile && !state.importedFile.exists) {
final errorCode = isGeneratedSource(state.importedSource)
? CompileTimeErrorCode.URI_HAS_NOT_BEEN_GENERATED
: CompileTimeErrorCode.URI_DOES_NOT_EXIST;
errorReporter.reportErrorForNode(
errorCode,
directive.uri,
[selectedUriStr],
);
} else if (state.importedLibrarySource == null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.IMPORT_OF_NON_LIBRARY,
directive.uri,
[selectedUriStr],
);
}
} else if (state is LibraryImportWithUriStr) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_URI,
directive.uri,
[state.selectedUri.relativeUriStr],
);
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.URI_WITH_INTERPOLATION,
directive.uri,
);
}
_reportImportDirectiveErrors(
directive: directive,
state: state,
errorReporter: errorReporter,
);
}

void _resolveNamespaceDirective({
Expand Down
10 changes: 10 additions & 0 deletions pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -181,21 +181,31 @@ class UnlinkedLibraryAugmentationDirective {
}

class UnlinkedLibraryDirective {
/// `@docImport` directives in a library doc comment.
final List<UnlinkedLibraryImportDirective> docImports;

final String? name;

UnlinkedLibraryDirective({
required this.docImports,
required this.name,
});

factory UnlinkedLibraryDirective.read(
SummaryDataReader reader,
) {
return UnlinkedLibraryDirective(
docImports: reader.readTypedList(
() => UnlinkedLibraryImportDirective.read(reader),
),
name: reader.readOptionalStringUtf8(),
);
}

void write(BufferedSink sink) {
sink.writeList<UnlinkedLibraryImportDirective>(docImports, (docImport) {
docImport.write(sink);
});
sink.writeOptionalStringUtf8(name);
}
}
Expand Down
37 changes: 36 additions & 1 deletion pkg/analyzer/test/src/diagnostics/uri_does_not_exist_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,41 @@ void f() {
]);
}

test_libraryDocImport_cannotResolve_dart() async {
await assertErrorsInCode(r'''
/// @docImport 'dart:foo';
library;
''', [
error(CompileTimeErrorCode.URI_DOES_NOT_EXIST, 15, 10),
]);
}

test_libraryDocImport_cannotResolve_file() async {
await assertErrorsInCode(r'''
/// @docImport 'foo.dart';
library;
''', [
error(CompileTimeErrorCode.URI_DOES_NOT_EXIST, 15, 10),
]);
}

test_libraryDocImport_canResolve_dart() async {
await assertNoErrorsInCode(r'''
/// @docImport 'dart:math';
library;
''');
}

test_libraryDocImport_canResolve_file() async {
newFile('$testPackageLibPath/foo.dart', r'''
class A {}
''');
await assertNoErrorsInCode(r'''
/// @docImport 'foo.dart';
library;
''');
}

test_libraryExport() async {
await assertErrorsInCode('''
export 'unknown.dart';
Expand Down Expand Up @@ -69,7 +104,7 @@ import 'unknown.dart';
]);
}

test_libraryImport_appears_after_deleting_target() async {
test_libraryImport_appearsAfterDeletingTarget() async {
String filePath = newFile('$testPackageLibPath/target.dart', '').path;

await assertErrorsInCode('''
Expand Down

0 comments on commit 1aa0575

Please sign in to comment.