Revert 'Import Library' quick fix changes.

This basically reverts https://dart-review.googlesource.com/c/sdk/+/103921
because internally IntelliJ does not provide module dependencies yet.

Change-Id: I7717b2841bf3d6391b991875a594c6df9e246ff1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106482
Reviewed-by: Ari Aye <ariaye@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2019-06-18 17:31:56 +00:00 committed by commit-bot@chromium.org
parent 4adaa1fd56
commit 143e5ef556
17 changed files with 796 additions and 197 deletions

View file

@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/plugin/edit/fix/fix_core.dart';
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
@ -22,10 +21,4 @@ abstract class DartFixContext implements FixContext {
* The workspace in which the fix contributor operates.
*/
ChangeWorkspace get workspace;
/**
* Return top-level declarations with the [name] in libraries that are
* available to this context.
*/
List<TopLevelDeclaration> getTopLevelDeclarations(String name);
}

View file

@ -23,7 +23,6 @@ import 'package:analysis_server/src/services/correction/assist_internal.dart';
import 'package:analysis_server/src/services/correction/change_workspace.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/fix/analysis_options/fix_generator.dart';
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
import 'package:analysis_server/src/services/correction/fix/manifest/fix_generator.dart';
import 'package:analysis_server/src/services/correction/fix/pubspec/fix_generator.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
@ -635,16 +634,7 @@ class EditDomainHandler extends AbstractRequestHandler {
int errorLine = lineInfo.getLocation(error.offset).lineNumber;
if (errorLine == requestLine) {
var workspace = DartChangeWorkspace(server.currentSessions);
var context =
new DartFixContextImpl(workspace, result, error, (name) {
var tracker = server.declarationsTracker;
var provider = TopLevelDeclarationsProvider(tracker);
return provider.get(
result.session.analysisContext,
result.path,
name,
);
});
var context = new DartFixContextImpl(workspace, result, error);
List<Fix> fixes =
await new DartFixContributor().computeFixes(context);
if (fixes.isNotEmpty) {

View file

@ -28,12 +28,7 @@ class FixErrorTask {
Future<void> fixError(ResolvedUnitResult result, AnalysisError error) async {
final workspace = DartChangeWorkspace(listener.server.currentSessions);
final dartContext = new DartFixContextImpl(
workspace,
result,
error,
(name) => [],
);
final dartContext = new DartFixContextImpl(workspace, result, error);
final processor = new FixProcessor(dartContext);
Fix fix = await processor.computeFix();
final location = listener.locationFor(result, error.offset, error.length);

View file

@ -18,7 +18,6 @@ import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/assist_internal.dart';
import 'package:analysis_server/src/services/correction/change_workspace.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
import 'package:analysis_server/src/services/refactoring/refactoring.dart';
import 'package:analyzer/dart/analysis/results.dart';
@ -195,14 +194,7 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
int errorLine = lineInfo.getLocation(error.offset).lineNumber - 1;
if (errorLine >= range.start.line && errorLine <= range.end.line) {
var workspace = DartChangeWorkspace(server.currentSessions);
var context = new DartFixContextImpl(workspace, unit, error, (name) {
var tracker = server.declarationsTracker;
return TopLevelDeclarationsProvider(tracker).get(
unit.session.analysisContext,
unit.path,
name,
);
});
var context = new DartFixContextImpl(workspace, unit, error);
final fixes = await fixContributor.computeFixes(context);
if (fixes.isNotEmpty) {
fixes.sort(Fix.SORT_BY_RELEVANCE);

View file

@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/plugin/edit/fix/fix_dart.dart';
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/error/error.dart';
@ -120,16 +119,7 @@ class DartFixContextImpl implements DartFixContext {
@override
final AnalysisError error;
final List<TopLevelDeclaration> Function(String name)
getTopLevelDeclarationsFunction;
DartFixContextImpl(this.workspace, this.resolveResult, this.error,
this.getTopLevelDeclarationsFunction);
@override
List<TopLevelDeclaration> getTopLevelDeclarations(String name) {
return getTopLevelDeclarationsFunction(name);
}
DartFixContextImpl(this.workspace, this.resolveResult, this.error);
}
/// An enumeration of quick fix kinds found in a Dart file.

View file

@ -1,107 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/src/services/available_declarations.dart';
/// Information about a single top-level declaration.
class TopLevelDeclaration {
/// The path of the library that exports this declaration.
final String path;
/// The URI of the library that exports this declaration.
final Uri uri;
final TopLevelDeclarationKind kind;
final String name;
/// Is `true` if the declaration is exported, not declared in the [path].
final bool isExported;
TopLevelDeclaration(
this.path,
this.uri,
this.kind,
this.name,
this.isExported,
);
@override
String toString() => '($path, $uri, $kind, $name, $isExported)';
}
/// Kind of a top-level declaration.
///
/// We don't need it to be precise, just enough to support quick fixes.
enum TopLevelDeclarationKind { type, function, variable }
class TopLevelDeclarationsProvider {
final DeclarationsTracker tracker;
TopLevelDeclarationsProvider(this.tracker);
void doTrackerWork() {
while (tracker.hasWork) {
tracker.doWork();
}
}
List<TopLevelDeclaration> get(
AnalysisContext analysisContext,
String path,
String name,
) {
var declarations = <TopLevelDeclaration>[];
void addDeclarations(Library library) {
for (var declaration in library.declarations) {
if (declaration.name != name) continue;
var kind = _getTopKind(declaration.kind);
if (kind == null) continue;
declarations.add(
TopLevelDeclaration(
library.path,
library.uri,
kind,
name,
declaration.locationLibraryUri != library.uri,
),
);
}
}
var declarationsContext = tracker.getContext(analysisContext);
var libraries = declarationsContext.getLibraries(path);
libraries.context.forEach(addDeclarations);
libraries.dependencies.forEach(addDeclarations);
libraries.sdk.forEach(addDeclarations);
return declarations;
}
TopLevelDeclarationKind _getTopKind(DeclarationKind kind) {
switch (kind) {
case DeclarationKind.CLASS:
case DeclarationKind.CLASS_TYPE_ALIAS:
case DeclarationKind.ENUM:
case DeclarationKind.FUNCTION_TYPE_ALIAS:
case DeclarationKind.MIXIN:
return TopLevelDeclarationKind.type;
break;
case DeclarationKind.FUNCTION:
return TopLevelDeclarationKind.function;
break;
case DeclarationKind.GETTER:
case DeclarationKind.SETTER:
case DeclarationKind.VARIABLE:
return TopLevelDeclarationKind.variable;
break;
default:
return null;
}
}
}

View file

@ -10,7 +10,6 @@ import 'package:analysis_server/plugin/edit/fix/fix_core.dart';
import 'package:analysis_server/plugin/edit/fix/fix_dart.dart';
import 'package:analysis_server/src/services/completion/dart/utilities.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
import 'package:analysis_server/src/services/correction/levenshtein.dart';
import 'package:analysis_server/src/services/correction/namespace.dart';
import 'package:analysis_server/src/services/correction/strings.dart';
@ -27,6 +26,7 @@ import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/session_helper.dart';
import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
@ -105,11 +105,7 @@ class DartFixContributor implements FixContributor {
// For each fix, put the fix into the HashMap.
for (int i = 0; i < allAnalysisErrors.length; i++) {
final FixContext fixContextI = new DartFixContextImpl(
context.workspace,
context.resolveResult,
allAnalysisErrors[i],
(name) => [],
);
context.workspace, context.resolveResult, allAnalysisErrors[i]);
final FixProcessor processorI = new FixProcessor(fixContextI);
final List<Fix> fixesListI = await processorI.compute();
for (Fix f in fixesListI) {
@ -2416,7 +2412,7 @@ class FixProcessor {
}
// may be there is an existing import,
// but it is with prefix and we don't use this prefix
var alreadyImportedWithPrefix = new Set<String>();
Set<Source> alreadyImportedWithPrefix = new Set<Source>();
for (ImportElement imp in unitLibraryElement.imports) {
// prepare element
LibraryElement libraryElement = imp.importedLibrary;
@ -2458,7 +2454,7 @@ class FixProcessor {
libraryName = libraryElement.source.shortName;
}
// don't add this library again
alreadyImportedWithPrefix.add(libraryElement.source.fullName);
alreadyImportedWithPrefix.add(libraryElement.source);
// update library
String newShowCode = 'show ${showNames.join(', ')}';
int offset = showCombinator.offset;
@ -2477,21 +2473,25 @@ class FixProcessor {
}
// Find new top-level declarations.
{
var declarations = await context.getTopLevelDeclarations(name);
for (var declaration in declarations) {
var declarations = await session.getTopLevelDeclarations(name);
for (TopLevelDeclarationInSource declaration in declarations) {
// Check the kind.
if (!kinds2.contains(declaration.kind)) {
if (!kinds2.contains(declaration.declaration.kind)) {
continue;
}
// Check the source.
if (alreadyImportedWithPrefix.contains(declaration.path)) {
Source librarySource = declaration.source;
if (alreadyImportedWithPrefix.contains(librarySource)) {
continue;
}
if (!_isSourceVisibleToLibrary(librarySource)) {
continue;
}
// Compute the fix kind.
FixKind fixKind;
if (declaration.uri.isScheme('dart')) {
if (librarySource.isInSystemLibrary) {
fixKind = DartFixKind.IMPORT_LIBRARY_SDK;
} else if (_isLibSrcPath(declaration.path)) {
} else if (_isLibSrcPath(librarySource.fullName)) {
// Bad: non-API.
fixKind = DartFixKind.IMPORT_LIBRARY_PROJECT3;
} else if (declaration.isExported) {
@ -2503,8 +2503,8 @@ class FixProcessor {
}
// Add the fix.
var relativeURI =
_getRelativeURIFromLibrary(unitLibraryElement, declaration.path);
await _addFix_importLibrary(fixKind, declaration.uri, relativeURI);
_getRelativeURIFromLibrary(unitLibraryElement, librarySource);
await _addFix_importLibrary(fixKind, librarySource.uri, relativeURI);
}
}
}
@ -4246,20 +4246,21 @@ class FixProcessor {
}
/**
* Return the relative uri from the passed [library] to the given [path].
* If the [path] is not in the LibraryElement, `null` is returned.
* Return the relative uri from the passed [library] to the passed
* [source]. If the [source] is not in the LibraryElement, `null` is returned.
*/
String _getRelativeURIFromLibrary(LibraryElement library, String path) {
String _getRelativeURIFromLibrary(LibraryElement library, Source source) {
var librarySource = library?.librarySource;
if (librarySource == null) {
return null;
}
var pathCtx = resourceProvider.pathContext;
var libraryDirectory = pathCtx.dirname(librarySource.fullName);
var sourceDirectory = pathCtx.dirname(path);
if (pathCtx.isWithin(libraryDirectory, path) ||
var sourceDirectory = pathCtx.dirname(source.fullName);
if (pathCtx.isWithin(libraryDirectory, source.fullName) ||
pathCtx.isWithin(sourceDirectory, libraryDirectory)) {
String relativeFile = pathCtx.relative(path, from: libraryDirectory);
String relativeFile =
pathCtx.relative(source.fullName, from: libraryDirectory);
return pathCtx.split(relativeFile).join('/');
}
return null;
@ -4468,6 +4469,30 @@ class FixProcessor {
return false;
}
/**
* Return `true` if the [source] can be imported into current library.
*/
bool _isSourceVisibleToLibrary(Source source) {
String path = source.fullName;
var contextRoot = context.resolveResult.session.analysisContext.contextRoot;
if (contextRoot == null) {
return true;
}
// We don't want to use private libraries of other packages.
if (source.uri.isScheme('package') && _isLibSrcPath(path)) {
return contextRoot.root.contains(path);
}
// We cannot use relative URIs to reference files outside of our package.
if (source.uri.isScheme('file')) {
return contextRoot.root.contains(path);
}
return true;
}
bool _isToListMethodElement(MethodElement method) {
if (method.name != 'toList') {
return false;

View file

@ -135,12 +135,6 @@ class AbstractAnalysisTest with ResourceProviderMixin {
handleSuccessfulRequest(request, handler: analysisHandler);
}
void doAllDeclarationsTrackerWork() {
while (server.declarationsTracker.hasWork) {
server.declarationsTracker.doWork();
}
}
/**
* Returns the offset of [search] in [testCode].
* Fails if not found.

View file

@ -40,7 +40,6 @@ main() {
}
''');
await waitForTasksFinished();
doAllDeclarationsTrackerWork();
List<AnalysisErrorFixes> errorFixes =
await _getFixesAt('Completer<String>');
expect(errorFixes, hasLength(1));
@ -144,6 +143,9 @@ dependencies:
bbb: any
''');
// Ensure that the target is analyzed as an implicit source.
newFile('/aaa/lib/foo.dart', content: 'import "package:bbb/target.dart";');
newFolder('/bbb');
newFile('/bbb/.packages', content: '''
bbb:${toUri('/bbb/lib')}
@ -161,7 +163,6 @@ bbb:${toUri('/bbb/lib')}
_addOverlay(testFile, testCode);
await waitForTasksFinished();
doAllDeclarationsTrackerWork();
List<String> fixes = (await _getFixesAt('Foo()'))
.single

View file

@ -7,12 +7,9 @@ import 'dart:async';
import 'package:analysis_server/plugin/edit/fix/fix_core.dart';
import 'package:analysis_server/src/services/correction/change_workspace.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/error/lint_codes.dart';
import 'package:analyzer/src/services/available_declarations.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart'
hide AnalysisError;
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
@ -253,19 +250,7 @@ abstract class FixProcessorTest extends AbstractSingleUnitTest {
/// Computes fixes for the given [error] in [testUnit].
Future<List<Fix>> _computeFixes(AnalysisError error) async {
var tracker = DeclarationsTracker(MemoryByteStore(), resourceProvider);
tracker.addContext(driver.analysisContext);
var context = new DartFixContextImpl(
workspace,
testAnalysisResult,
error,
(name) {
var provider = TopLevelDeclarationsProvider(tracker);
provider.doTrackerWork();
return provider.get(driver.analysisContext, testFile, name);
},
);
var context = new DartFixContextImpl(workspace, testAnalysisResult, error);
return await new DartFixContributor().computeFixes(context);
}

View file

@ -11,6 +11,7 @@ import 'package:analyzer/dart/analysis/uri_converter.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
@ -121,6 +122,11 @@ abstract class AnalysisSession {
/// complete with [SourceKind.UNKNOWN].
Future<SourceKind> getSourceKind(String path);
/// Return a future that will complete with a list of the top-level
/// declarations with the given [name] in all known libraries.
Future<List<TopLevelDeclarationInSource>> getTopLevelDeclarations(
String name);
/// Return a future that will complete with information about the results of
/// building the element model for the file with the given absolute,
/// normalized[path].

View file

@ -28,6 +28,7 @@ import 'package:analyzer/src/dart/analysis/results.dart';
import 'package:analyzer/src/dart/analysis/search.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
import 'package:analyzer/src/dart/analysis/status.dart';
import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/engine.dart'
show
@ -215,6 +216,11 @@ class AnalysisDriver implements AnalysisDriverGeneric {
*/
final _referencingNameTasks = <_FilesReferencingNameTask>[];
/**
* The list of tasks to compute top-level declarations of a name.
*/
final _topLevelNameDeclarationsTasks = <_TopLevelNameDeclarationsTask>[];
/**
* The mapping from the files for which the index was requested using
* [getIndex] to the [Completer]s to report the result.
@ -513,6 +519,9 @@ class AnalysisDriver implements AnalysisDriverGeneric {
if (_unitElementRequestedFiles.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_topLevelNameDeclarationsTasks.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_priorityFiles.isNotEmpty) {
for (String path in _priorityFiles) {
if (_fileTracker.isFilePending(path)) {
@ -961,6 +970,19 @@ class AnalysisDriver implements AnalysisDriverGeneric {
return null;
}
/**
* Return a [Future] that completes with top-level declarations with the
* given [name] in all known libraries.
*/
Future<List<TopLevelDeclarationInSource>> getTopLevelNameDeclarations(
String name) {
_discoverAvailableFiles();
var task = new _TopLevelNameDeclarationsTask(this, name);
_topLevelNameDeclarationsTasks.add(task);
_scheduler.notify(this);
return task.completer.future;
}
/**
* Return a [Future] that completes with the [UnitElementResult] for the
* file with the given [path], or with `null` if the file cannot be analyzed.
@ -1186,6 +1208,16 @@ class AnalysisDriver implements AnalysisDriverGeneric {
return;
}
// Compute top-level declarations.
if (_topLevelNameDeclarationsTasks.isNotEmpty) {
_TopLevelNameDeclarationsTask task = _topLevelNameDeclarationsTasks.first;
bool isDone = task.perform();
if (isDone) {
_topLevelNameDeclarationsTasks.remove(task);
}
return;
}
// Analyze a priority file.
if (_priorityFiles.isNotEmpty) {
for (String path in _priorityFiles) {
@ -2515,3 +2547,66 @@ class _FilesReferencingNameTask {
return true;
}
}
/**
* Task that computes top-level declarations for a certain name in all
* known libraries.
*/
class _TopLevelNameDeclarationsTask {
final AnalysisDriver driver;
final String name;
final Completer<List<TopLevelDeclarationInSource>> completer =
new Completer<List<TopLevelDeclarationInSource>>();
final List<TopLevelDeclarationInSource> libraryDeclarations =
<TopLevelDeclarationInSource>[];
final Set<String> checkedFiles = new Set<String>();
final List<String> filesToCheck = <String>[];
_TopLevelNameDeclarationsTask(this.driver, this.name);
/**
* Perform a single piece of work, and either complete the [completer] and
* return `true` to indicate that the task is done, return `false` to indicate
* that the task should continue to be run.
*/
bool perform() {
// Prepare files to check.
if (filesToCheck.isEmpty) {
filesToCheck.addAll(driver.addedFiles.difference(checkedFiles));
filesToCheck.addAll(driver.knownFiles.difference(checkedFiles));
}
// If no more files to check, complete and done.
if (filesToCheck.isEmpty) {
completer.complete(libraryDeclarations);
return true;
}
// Check the next file.
String path = filesToCheck.removeLast();
if (checkedFiles.add(path)) {
FileState file = driver._fsState.getFileForPath(path);
if (!file.isPart) {
bool isExported = false;
TopLevelDeclaration declaration;
for (FileState part in file.libraryFiles) {
declaration ??= part.topLevelDeclarations[name];
}
if (declaration == null) {
declaration = file.exportedTopLevelDeclarations[name];
isExported = true;
}
if (declaration != null) {
libraryDeclarations.add(new TopLevelDeclarationInSource(
file.source, declaration, isExported));
}
}
}
// We're not done yet.
return false;
}
}

View file

@ -18,6 +18,7 @@ import 'package:analyzer/src/dart/analysis/library_graph.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/analysis/referenced_names.dart';
import 'package:analyzer/src/dart/analysis/unlinked_api_signature.dart';
import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
import 'package:analyzer/src/dart/scanner/reader.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer/src/generated/engine.dart';
@ -84,6 +85,11 @@ class FileContentOverlay {
* should be called.
*/
class FileState {
/**
* The next value for [_exportDeclarationsId].
*/
static int _exportDeclarationsNextId = 0;
final FileSystemState _fsState;
/**
@ -136,6 +142,10 @@ class FileState {
String _transitiveSignature;
String _transitiveSignatureLinked;
Map<String, TopLevelDeclaration> _topLevelDeclarations;
Map<String, TopLevelDeclaration> _exportedTopLevelDeclarations;
int _exportDeclarationsId = 0;
/**
* The flag that shows whether the file has an error or warning that
* might be fixed by a change to another file.
@ -206,6 +216,18 @@ class FileState {
*/
List<FileState> get exportedFiles => _exportedFiles;
/**
* Return [TopLevelDeclaration]s exported from the this library file. The
* keys to the map are names of declarations.
*/
Map<String, TopLevelDeclaration> get exportedTopLevelDeclarations {
if (AnalysisDriver.useSummary2) {
return <String, TopLevelDeclaration>{};
}
_exportDeclarationsNextId = 1;
return _computeExportedDeclarations().declarations;
}
@override
int get hashCode => uri.hashCode;
@ -303,6 +325,63 @@ class FileState {
@visibleForTesting
FileStateTestView get test => new FileStateTestView(this);
/**
* Return public top-level declarations declared in the file. The keys to the
* map are names of declarations.
*/
Map<String, TopLevelDeclaration> get topLevelDeclarations {
if (AnalysisDriver.useSummary2) {
return <String, TopLevelDeclaration>{};
}
if (_topLevelDeclarations == null) {
_topLevelDeclarations = <String, TopLevelDeclaration>{};
void addDeclaration(TopLevelDeclarationKind kind, String name) {
if (!name.startsWith('_')) {
_topLevelDeclarations[name] = new TopLevelDeclaration(kind, name);
}
}
// Add types.
for (UnlinkedClass type in unlinked.classes) {
addDeclaration(TopLevelDeclarationKind.type, type.name);
}
for (UnlinkedEnum type in unlinked.enums) {
addDeclaration(TopLevelDeclarationKind.type, type.name);
}
for (UnlinkedClass type in unlinked.mixins) {
addDeclaration(TopLevelDeclarationKind.type, type.name);
}
for (UnlinkedTypedef type in unlinked.typedefs) {
addDeclaration(TopLevelDeclarationKind.type, type.name);
}
// Add functions and variables.
Set<String> addedVariableNames = new Set<String>();
for (UnlinkedExecutable executable in unlinked.executables) {
String name = executable.name;
if (executable.kind == UnlinkedExecutableKind.functionOrMethod) {
addDeclaration(TopLevelDeclarationKind.function, name);
} else if (executable.kind == UnlinkedExecutableKind.getter ||
executable.kind == UnlinkedExecutableKind.setter) {
if (executable.kind == UnlinkedExecutableKind.setter) {
name = name.substring(0, name.length - 1);
}
if (addedVariableNames.add(name)) {
addDeclaration(TopLevelDeclarationKind.variable, name);
}
}
}
for (UnlinkedVariable variable in unlinked.variables) {
String name = variable.name;
if (addedVariableNames.add(name)) {
addDeclaration(TopLevelDeclarationKind.variable, name);
}
}
}
return _topLevelDeclarations;
}
/**
* Return the set of transitive files - the file itself and all of the
* directly or indirectly referenced files.
@ -472,6 +551,10 @@ class FileState {
library.libraryCycle?.invalidate();
}
}
for (FileState file in _fsState._uriToFile.values) {
file._exportedTopLevelDeclarations = null;
}
}
// This file is potentially not a library for its previous parts anymore.
@ -538,6 +621,69 @@ class FileState {
@override
String toString() => path ?? '<unresolved>';
/**
* Compute the full or partial map of exported declarations for this library.
*/
_ExportedDeclarations _computeExportedDeclarations() {
// If we know exported declarations, return them.
if (_exportedTopLevelDeclarations != null) {
return new _ExportedDeclarations(0, _exportedTopLevelDeclarations);
}
// If we are already computing exported declarations for this library,
// report that we found a cycle.
if (_exportDeclarationsId != 0) {
return new _ExportedDeclarations(_exportDeclarationsId, null);
}
var declarations = <String, TopLevelDeclaration>{};
// Give each library a unique identifier.
_exportDeclarationsId = _exportDeclarationsNextId++;
// Append the exported declarations.
int firstCycleId = 0;
for (int i = 0; i < _exportedFiles.length; i++) {
var exported = _exportedFiles[i]._computeExportedDeclarations();
if (exported.declarations != null) {
for (TopLevelDeclaration t in exported.declarations.values) {
if (_exportFilters[i].accepts(t.name)) {
declarations[t.name] = t;
}
}
}
if (exported.firstCycleId > 0) {
if (firstCycleId == 0 || firstCycleId > exported.firstCycleId) {
firstCycleId = exported.firstCycleId;
}
}
}
// If this library is the first component of the cycle, then we are at
// the beginning of this cycle, and combination of partial export
// namespaces of other exported libraries and declarations of this library
// is the full export namespace of this library.
if (firstCycleId != 0 && firstCycleId == _exportDeclarationsId) {
firstCycleId = 0;
}
// We're done with this library, successfully or not.
_exportDeclarationsId = 0;
// Append the library declarations.
for (FileState file in libraryFiles) {
declarations.addAll(file.topLevelDeclarations);
}
// Record the declarations only if it is the full result.
if (firstCycleId == 0) {
_exportedTopLevelDeclarations = declarations;
}
// Return the full or partial result.
return new _ExportedDeclarations(firstCycleId, declarations);
}
CompilationUnit _createEmptyCompilationUnit(FeatureSet featureSet) {
var token = new Token.eof(0);
return astFactory.compilationUnit2(
@ -573,6 +719,7 @@ class FileState {
_definedTopLevelNames = null;
_definedClassMemberNames = null;
_referencedNames = null;
_topLevelDeclarations = null;
if (_driverUnlinkedUnit != null) {
for (var name in _driverUnlinkedUnit.subtypedNames) {
@ -687,6 +834,10 @@ class FileState {
library.libraryCycle?.invalidate();
}
}
for (FileState file in _fsState._uriToFile.values) {
file._exportedTopLevelDeclarations = null;
}
}
// This file is potentially not a library for its previous parts anymore.
@ -1109,6 +1260,23 @@ class FileSystemStateTestView {
.where((f) => f._libraryCycle == null)
.toSet();
}
Set<FileState> get librariesWithComputedExportedDeclarations {
return state._uriToFile.values
.where((f) => !f.isPart && f._exportedTopLevelDeclarations != null)
.toSet();
}
}
/**
* The result of computing exported top-level declarations.
* It can be full (when [firstCycleId] is zero), or partial (when a cycle)
*/
class _ExportedDeclarations {
final int firstCycleId;
final Map<String, TopLevelDeclaration> declarations;
_ExportedDeclarations(this.firstCycleId, this.declarations);
}
/**

View file

@ -12,6 +12,7 @@ import 'package:analyzer/dart/analysis/uri_converter.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/driver.dart' as driver;
import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
import 'package:analyzer/src/dart/analysis/uri_converter.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
import 'package:analyzer/src/generated/resolver.dart';
@ -157,6 +158,13 @@ class AnalysisSessionImpl implements AnalysisSession {
return _driver.getSourceKind(path);
}
@override
Future<List<TopLevelDeclarationInSource>> getTopLevelDeclarations(
String name) {
_checkConsistency();
return _driver.getTopLevelNameDeclarations(name);
}
@override
Future<UnitElementResult> getUnitElement(String path) {
_checkConsistency();

View file

@ -0,0 +1,50 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/src/generated/source.dart';
/**
* Information about a single top-level declaration.
*/
class TopLevelDeclaration {
final TopLevelDeclarationKind kind;
final String name;
TopLevelDeclaration(this.kind, this.name);
@override
String toString() => '($kind, $name)';
}
/**
* A declaration in a source.
*/
class TopLevelDeclarationInSource {
/**
* The declaring source.
*/
final Source source;
/**
* The declaration.
*/
final TopLevelDeclaration declaration;
/**
* Is `true` if the [declaration] is exported, not declared in the [source].
*/
final bool isExported;
TopLevelDeclarationInSource(this.source, this.declaration, this.isExported);
@override
String toString() => '($source, $declaration, $isExported)';
}
/**
* Kind of a top-level declaration.
*
* We don't need it to be precise, just enough to support quick fixes.
*/
enum TopLevelDeclarationKind { type, function, variable }

View file

@ -15,6 +15,7 @@ import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/analysis/status.dart';
import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
import 'package:analyzer/src/dart/constant/evaluation.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/error/codes.dart';
@ -2227,6 +2228,102 @@ var A2 = B1;
expect(await driver.getSourceKind(path), SourceKind.PART);
}
test_getTopLevelNameDeclarations() async {
var a = convertPath('/test/lib/a.dart');
var b = convertPath('/test/lib/b.dart');
var c = convertPath('/test/lib/c.dart');
var d = convertPath('/test/lib/d.dart');
newFile(a, content: 'class A {}');
newFile(b, content: 'export "a.dart"; class B {}');
newFile(c, content: 'import "d.dart"; class C {}');
newFile(d, content: 'class D {}');
driver.addFile(a);
driver.addFile(b);
driver.addFile(c);
// Don't add d.dart, it is referenced implicitly.
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('A'), [a, b], [false, true]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('B'), [b], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('C'), [c], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('D'), [d], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('X'), [], []);
}
test_getTopLevelNameDeclarations_discover() async {
var t = convertPath('/test/lib/test.dart');
var a1 = convertPath('/aaa/lib/a1.dart');
var a2 = convertPath('/aaa/lib/src/a2.dart');
var b = convertPath('/bbb/lib/b.dart');
var c = convertPath('/ccc/lib/c.dart');
newFile(t, content: 'class T {}');
newFile(a1, content: 'class A1 {}');
newFile(a2, content: 'class A2 {}');
newFile(b, content: 'class B {}');
newFile(c, content: 'class C {}');
driver.addFile(t);
// Don't add a1.dart, a2.dart, or b.dart - they should be discovered.
// And c.dart is not in .packages, so should not be discovered.
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('T'), [t], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('A1'), [a1], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('A2'), [a2], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('B'), [b], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('C'), [], []);
}
test_getTopLevelNameDeclarations_parts() async {
var a = convertPath('/test/lib/a.dart');
var b = convertPath('/test/lib/b.dart');
var c = convertPath('/test/lib/c.dart');
newFile(a, content: r'''
library lib;
part 'b.dart';
part 'c.dart';
class A {}
''');
newFile(b, content: 'part of lib; class B {}');
newFile(c, content: 'part of lib; class C {}');
driver.addFile(a);
driver.addFile(b);
driver.addFile(c);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('A'), [a], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('B'), [a], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('C'), [a], [false]);
_assertTopLevelDeclarations(
await driver.getTopLevelNameDeclarations('X'), [], []);
}
test_getUnitElement() async {
String content = r'''
foo(int p) {}
@ -3416,6 +3513,20 @@ var v = 0
}
}
void _assertTopLevelDeclarations(
List<TopLevelDeclarationInSource> declarations,
List<String> expectedFiles,
List<bool> expectedIsExported) {
expect(expectedFiles, hasLength(expectedIsExported.length));
for (int i = 0; i < expectedFiles.length; i++) {
expect(declarations,
contains(predicate((TopLevelDeclarationInSource declaration) {
return declaration.source.fullName == expectedFiles[i] &&
declaration.isExported == expectedIsExported[i];
})));
}
}
void _expectCircularityError(EvaluationResultImpl evaluationResult) {
expect(evaluationResult, isNotNull);
expect(evaluationResult.value, isNull);

View file

@ -11,6 +11,7 @@ import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/library_graph.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
import 'package:analyzer/src/file_system/file_system.dart';
import 'package:analyzer/src/generated/engine.dart'
show AnalysisOptions, AnalysisOptionsImpl;
@ -104,6 +105,249 @@ var G, H;
unorderedEquals(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']));
}
test_exportedTopLevelDeclarations_cycle() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
String c = convertPath('/aaa/lib/c.dart');
newFile(a, content: r'''
export 'b.dart';
class A {}
''');
newFile(b, content: r'''
export 'c.dart';
class B {}
''');
newFile(c, content: r'''
export 'a.dart';
class C {}
''');
_assertExportedTopLevelDeclarations(a, ['A', 'B', 'C']);
// We asked for 'a', and it was computed.
// But 'b' and 'c' are not computed, because we detect that there is
// cycle with 'a', so we cannot get all exported declarations of 'a'.
_assertHasComputedExportedDeclarations([a]);
}
test_exportedTopLevelDeclarations_cycle_anotherOutsideCycle() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
String c = convertPath('/aaa/lib/c.dart');
String d = convertPath('/aaa/lib/d.dart');
newFile(a, content: r'''
export 'b.dart';
class A {}
''');
newFile(b, content: r'''
export 'c.dart';
class B {}
''');
newFile(c, content: r'''
export 'b.dart';
export 'd.dart';
class C {}
''');
newFile(d, content: r'''
class D {}
''');
_assertExportedTopLevelDeclarations(a, ['A', 'B', 'C', 'D']);
// To compute 'a' we compute 'b'.
// But 'c' is not computed, because of the cycle [b, c].
// However 'd' is not a part of a cycle, so it is computed too.
_assertHasComputedExportedDeclarations([a, b, d]);
}
test_exportedTopLevelDeclarations_cycle_onSequence() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
String c = convertPath('/aaa/lib/c.dart');
String d = convertPath('/aaa/lib/d.dart');
String e = convertPath('/aaa/lib/e.dart');
newFile(a, content: r'''
export 'b.dart';
class A {}
''');
newFile(b, content: r'''
export 'c.dart';
class B {}
''');
newFile(c, content: r'''
export 'd.dart';
class C {}
''');
newFile(d, content: r'''
export 'e.dart';
class D {}
''');
newFile(e, content: r'''
export 'c.dart';
class E {}
''');
// We compute 'a'.
// To compute it we also compute 'b' and 'c'.
// But 'd' and 'e' are not computed, because of the cycle [c, d, e].
_assertExportedTopLevelDeclarations(a, ['A', 'B', 'C', 'D', 'E']);
_assertHasComputedExportedDeclarations([a, b, c]);
// We compute 'd', and try to compute 'e', because 'd' needs 'e'; 'e' can
// be computed because 'c' is ready, so the cycle [c, d, e] is broken.
_assertExportedTopLevelDeclarations(d, ['C', 'D', 'E']);
_assertHasComputedExportedDeclarations([a, b, c, d, e]);
}
test_exportedTopLevelDeclarations_export() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
newFile(a, content: r'''
class A {}
''');
newFile(b, content: r'''
export 'a.dart';
class B {}
''');
_assertExportedTopLevelDeclarations(b, ['A', 'B']);
_assertHasComputedExportedDeclarations([a, b]);
}
test_exportedTopLevelDeclarations_export2_show() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
String c = convertPath('/aaa/lib/c.dart');
newFile(a, content: r'''
class A1 {}
class A2 {}
class A3 {}
''');
newFile(b, content: r'''
export 'a.dart' show A1, A2;
class B1 {}
class B2 {}
''');
newFile(c, content: r'''
export 'b.dart' show A2, A3, B1;
class C {}
''');
_assertExportedTopLevelDeclarations(c, ['A2', 'B1', 'C']);
_assertHasComputedExportedDeclarations([a, b, c]);
}
test_exportedTopLevelDeclarations_export_flushOnChange() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
newFile(a, content: r'''
class A {}
''');
newFile(b, content: r'''
export 'a.dart';
class B {}
''');
// Initial exported declarations.
_assertExportedTopLevelDeclarations(b, ['A', 'B']);
// Update a.dart, so a.dart and b.dart exported declarations are flushed.
newFile(a, content: 'class A {} class A2 {}');
fileSystemState.getFileForPath(a).refresh();
_assertExportedTopLevelDeclarations(b, ['A', 'A2', 'B']);
}
test_exportedTopLevelDeclarations_export_hide() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
newFile(a, content: r'''
class A1 {}
class A2 {}
class A3 {}
''');
newFile(b, content: r'''
export 'a.dart' hide A2;
class B {}
''');
_assertExportedTopLevelDeclarations(b, ['A1', 'A3', 'B']);
}
test_exportedTopLevelDeclarations_export_preferLocal() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
newFile(a, content: r'''
class V {}
''');
newFile(b, content: r'''
export 'a.dart';
int V;
''');
FileState file = fileSystemState.getFileForPath(b);
Map<String, TopLevelDeclaration> declarations =
file.exportedTopLevelDeclarations;
expect(declarations.keys, unorderedEquals(['V']));
expect(declarations['V'].kind, TopLevelDeclarationKind.variable);
}
test_exportedTopLevelDeclarations_export_show() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
newFile(a, content: r'''
class A1 {}
class A2 {}
''');
newFile(b, content: r'''
export 'a.dart' show A2;
class B {}
''');
_assertExportedTopLevelDeclarations(b, ['A2', 'B']);
}
test_exportedTopLevelDeclarations_export_show2() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
String c = convertPath('/aaa/lib/c.dart');
String d = convertPath('/aaa/lib/d.dart');
newFile(a, content: r'''
export 'b.dart' show Foo;
export 'c.dart' show Bar;
''');
newFile(b, content: r'''
export 'd.dart';
''');
newFile(c, content: r'''
export 'd.dart';
''');
newFile(d, content: r'''
class Foo {}
class Bar {}
''');
_assertExportedTopLevelDeclarations(a, ['Foo', 'Bar']);
}
test_exportedTopLevelDeclarations_import() {
String a = convertPath('/aaa/lib/a.dart');
String b = convertPath('/aaa/lib/b.dart');
newFile(a, content: r'''
class A {}
''');
newFile(b, content: r'''
import 'a.dart';
class B {}
''');
_assertExportedTopLevelDeclarations(b, ['B']);
}
test_exportedTopLevelDeclarations_parts() {
String a = convertPath('/aaa/lib/a.dart');
String a2 = convertPath('/aaa/lib/a2.dart');
newFile(a, content: r'''
library lib;
part 'a2.dart';
class A1 {}
''');
newFile(a2, content: r'''
part of lib;
class A2 {}
''');
_assertExportedTopLevelDeclarations(a, ['A1', 'A2']);
}
test_getFileForPath_doesNotExist() {
String path = convertPath('/aaa/lib/a.dart');
FileState file = fileSystemState.getFileForPath(path);
@ -597,6 +841,52 @@ class Z implements C, D {}
expect(file.referencedNames, unorderedEquals(['A', 'B', 'C', 'D']));
}
test_topLevelDeclarations() {
String path = convertPath('/aaa/lib/a.dart');
newFile(path, content: r'''
class C {}
typedef F();
enum E {E1, E2}
mixin M {}
void f() {}
var V1;
get V2 => null;
set V3(_) {}
get V4 => null;
set V4(_) {}
class _C {}
typedef _F();
enum _E {E1, E2}
mixin _M {}
void _f() {}
var _V1;
get _V2 => null;
set _V3(_) {}
''');
FileState file = fileSystemState.getFileForPath(path);
Map<String, TopLevelDeclaration> declarations = file.topLevelDeclarations;
void assertHas(String name, TopLevelDeclarationKind kind) {
expect(declarations[name]?.kind, kind);
}
expect(
declarations.keys,
unorderedEquals(['C', 'F', 'E', 'M', 'f', 'V1', 'V2', 'V3', 'V4']),
);
assertHas('C', TopLevelDeclarationKind.type);
assertHas('F', TopLevelDeclarationKind.type);
assertHas('E', TopLevelDeclarationKind.type);
assertHas('M', TopLevelDeclarationKind.type);
assertHas('f', TopLevelDeclarationKind.function);
assertHas('V1', TopLevelDeclarationKind.variable);
assertHas('V2', TopLevelDeclarationKind.variable);
assertHas('V3', TopLevelDeclarationKind.variable);
assertHas('V4', TopLevelDeclarationKind.variable);
}
test_transitiveSignature() {
String pa = convertPath('/aaa/lib/a.dart');
String pb = convertPath('/aaa/lib/b.dart');
@ -657,11 +947,24 @@ part of 'a.dart';
expect(bSignature, isNot(aSignature));
}
void _assertExportedTopLevelDeclarations(String path, List<String> expected) {
FileState file = fileSystemState.getFileForPath(path);
Map<String, TopLevelDeclaration> declarations =
file.exportedTopLevelDeclarations;
expect(declarations.keys, unorderedEquals(expected));
}
void _assertFilesWithoutLibraryCycle(List<FileState> expected) {
var actual = fileSystemState.test.filesWithoutLibraryCycle;
expect(_excludeSdk(actual), unorderedEquals(expected));
}
void _assertHasComputedExportedDeclarations(List<String> expectedPathList) {
FileSystemStateTestView test = fileSystemState.test;
expect(test.librariesWithComputedExportedDeclarations.map((f) => f.path),
unorderedEquals(expectedPathList));
}
void _assertIsUnresolvedFile(FileState file) {
expect(file.path, isNull);
expect(file.uri, isNull);