blob: da36857e3af8812d264f8cca0bfdad114cd6b0f7 [file] [log] [blame]
// Copyright (c) 2020, 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 'dart:collection';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.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/namespace.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
class ImportLibrary extends MultiCorrectionProducer {
final _ImportKind _importKind;
ImportLibrary(this._importKind);
@override
Iterable<CorrectionProducer> get producers sync* {
if (_importKind == _ImportKind.dartAsync) {
yield* _importLibrary(DartFixKind.IMPORT_ASYNC, Uri.parse('dart:async'));
} else if (_importKind == _ImportKind.forExtension) {
if (node is SimpleIdentifier) {
var extensionName = (node as SimpleIdentifier).name;
yield* _importLibraryForElement(
extensionName,
const [ElementKind.EXTENSION],
const [TopLevelDeclarationKind.extension]);
}
} else if (_importKind == _ImportKind.forFunction) {
if (node is SimpleIdentifier) {
if (node.parent is MethodInvocation) {
var invocation = node.parent as MethodInvocation;
if (invocation.realTarget != null || invocation.methodName != node) {
return;
}
}
var name = (node as SimpleIdentifier).name;
yield* _importLibraryForElement(name, const [
ElementKind.FUNCTION,
ElementKind.TOP_LEVEL_VARIABLE
], const [
TopLevelDeclarationKind.function,
TopLevelDeclarationKind.variable
]);
}
} else if (_importKind == _ImportKind.forTopLevelVariable) {
var node = this.node;
if (node is Annotation) {
Annotation annotation = node;
var name = annotation.name;
if (name != null && name.staticElement == null) {
if (annotation.arguments != null) {
return;
}
node = name;
}
}
if (node is SimpleIdentifier) {
var name = node.name;
yield* _importLibraryForElement(
name,
const [ElementKind.TOP_LEVEL_VARIABLE],
const [TopLevelDeclarationKind.variable]);
}
} else if (_importKind == _ImportKind.forType) {
var node = this.node;
if (node is Annotation) {
Annotation annotation = node;
var name = annotation.name;
if (name != null && name.staticElement == null) {
if (annotation.arguments == null) {
return;
}
node = name;
}
}
if (mightBeTypeIdentifier(node)) {
var typeName = (node is SimpleIdentifier)
? node.name
: (node as PrefixedIdentifier).prefix.name;
yield* _importLibraryForElement(
typeName,
const [ElementKind.CLASS, ElementKind.FUNCTION_TYPE_ALIAS],
const [TopLevelDeclarationKind.type]);
} else if (mightBeImplicitConstructor(node)) {
var typeName = (node as SimpleIdentifier).name;
yield* _importLibraryForElement(typeName, const [ElementKind.CLASS],
const [TopLevelDeclarationKind.type]);
}
}
}
@override
bool mightBeTypeIdentifier(AstNode node) {
if (super.mightBeTypeIdentifier(node)) {
return true;
}
if (node is PrefixedIdentifier) {
var parent = node.parent;
if (parent is TypeName) {
return true;
}
}
return false;
}
/// Return the relative uri from the passed [library] to the given [path].
/// If the [path] is not in the LibraryElement, `null` is returned.
String _getRelativeURIFromLibrary(LibraryElement library, String path) {
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) ||
pathCtx.isWithin(sourceDirectory, libraryDirectory)) {
var relativeFile = pathCtx.relative(path, from: libraryDirectory);
return pathCtx.split(relativeFile).join('/');
}
return null;
}
Iterable<CorrectionProducer> _importLibrary(FixKind fixKind, Uri library,
[String relativeURI]) sync* {
yield _ImportAbsoluteLibrary(fixKind, library);
if (relativeURI != null && relativeURI.isNotEmpty) {
yield _ImportRelativeLibrary(fixKind, relativeURI);
}
}
Iterable<CorrectionProducer> _importLibraryForElement(
String name,
List<ElementKind> elementKinds,
List<TopLevelDeclarationKind> kinds2) sync* {
// ignore if private
if (name.startsWith('_')) {
return;
}
// may be there is an existing import,
// but it is with prefix and we don't use this prefix
var alreadyImportedWithPrefix = <String>{};
for (var imp in libraryElement.imports) {
// prepare element
var libraryElement = imp.importedLibrary;
var element = getExportedElement(libraryElement, name);
if (element == null) {
continue;
}
if (element is PropertyAccessorElement) {
element = (element as PropertyAccessorElement).variable;
}
if (!elementKinds.contains(element.kind)) {
continue;
}
// may be apply prefix
var prefix = imp.prefix;
if (prefix != null) {
yield _ImportLibraryPrefix(imp);
continue;
}
// may be update "show" directive
var combinators = imp.combinators;
if (combinators.length == 1 && combinators[0] is ShowElementCombinator) {
var showCombinator = combinators[0] as ShowElementCombinator;
// prepare new set of names to show
Set<String> showNames = SplayTreeSet<String>();
showNames.addAll(showCombinator.shownNames);
showNames.add(name);
// prepare library name - unit name or 'dart:name' for SDK library
var libraryName =
libraryElement.definingCompilationUnit.source.uri.toString();
if (libraryElement.isInSdk) {
libraryName = libraryElement.source.shortName;
}
// don't add this library again
alreadyImportedWithPrefix.add(libraryElement.source.fullName);
yield _ImportLibraryShow(libraryName, showCombinator, showNames);
}
}
// Find new top-level declarations.
var declarations = getTopLevelDeclarations(name);
for (var declaration in declarations) {
// Check the kind.
if (!kinds2.contains(declaration.kind)) {
continue;
}
// Check the source.
if (alreadyImportedWithPrefix.contains(declaration.path)) {
continue;
}
// Check that the import doesn't end with '.template.dart'
if (declaration.uri.path.endsWith('.template.dart')) {
continue;
}
// Compute the fix kind.
FixKind fixKind;
if (declaration.uri.isScheme('dart')) {
fixKind = DartFixKind.IMPORT_LIBRARY_SDK;
} else if (_isLibSrcPath(declaration.path)) {
// Bad: non-API.
fixKind = DartFixKind.IMPORT_LIBRARY_PROJECT3;
} else if (declaration.isExported) {
// Ugly: exports.
fixKind = DartFixKind.IMPORT_LIBRARY_PROJECT2;
} else {
// Good: direct declaration.
fixKind = DartFixKind.IMPORT_LIBRARY_PROJECT1;
}
// Add the fix.
var relativeURI =
_getRelativeURIFromLibrary(libraryElement, declaration.path);
yield* _importLibrary(fixKind, declaration.uri, relativeURI);
}
}
bool _isLibSrcPath(String path) {
var parts = resourceProvider.pathContext.split(path);
for (var i = 0; i < parts.length - 2; i++) {
if (parts[i] == 'lib' && parts[i + 1] == 'src') {
return true;
}
}
return false;
}
/// Return an instance of this class that will add an import of `dart:async`.
/// Used as a tear-off in `FixProcessor`.
static ImportLibrary dartAsync() => ImportLibrary(_ImportKind.dartAsync);
/// Return an instance of this class that will add an import for an extension.
/// Used as a tear-off in `FixProcessor`.
static ImportLibrary forExtension() =>
ImportLibrary(_ImportKind.forExtension);
/// Return an instance of this class that will add an import for a top-level
/// function. Used as a tear-off in `FixProcessor`.
static ImportLibrary forFunction() => ImportLibrary(_ImportKind.forFunction);
/// Return an instance of this class that will add an import for a top-level
/// variable. Used as a tear-off in `FixProcessor`.
static ImportLibrary forTopLevelVariable() =>
ImportLibrary(_ImportKind.forTopLevelVariable);
/// Return an instance of this class that will add an import for a type (class
/// or mixin). Used as a tear-off in `FixProcessor`.
static ImportLibrary forType() => ImportLibrary(_ImportKind.forType);
}
/// A correction processor that can make one of the possible change computed by
/// the [ImportLibrary] producer.
class _ImportAbsoluteLibrary extends CorrectionProducer {
final FixKind _fixKind;
final Uri _library;
String _uriText;
_ImportAbsoluteLibrary(this._fixKind, this._library);
@override
List<Object> get fixArguments => [_uriText];
@override
FixKind get fixKind => _fixKind;
@override
Future<void> compute(ChangeBuilder builder) async {
await builder.addDartFileEdit(file, (builder) {
_uriText = builder.importLibrary(_library);
});
}
}
enum _ImportKind {
dartAsync,
forExtension,
forFunction,
forTopLevelVariable,
forType
}
/// A correction processor that can make one of the possible change computed by
/// the [ImportLibrary] producer.
class _ImportLibraryPrefix extends CorrectionProducer {
final ImportElement _importElement;
String _libraryName;
String _prefixName;
_ImportLibraryPrefix(this._importElement);
@override
List<Object> get fixArguments => [_libraryName, _prefixName];
@override
FixKind get fixKind => DartFixKind.IMPORT_LIBRARY_PREFIX;
@override
Future<void> compute(ChangeBuilder builder) async {
var libraryElement = _importElement.importedLibrary;
var prefix = _importElement.prefix;
_libraryName = libraryElement.displayName;
_prefixName = prefix.displayName;
await builder.addDartFileEdit(file, (builder) {
builder.addSimpleReplacement(range.startLength(node, 0), '$_prefixName.');
});
}
}
/// A correction processor that can make one of the possible change computed by
/// the [ImportLibrary] producer.
class _ImportLibraryShow extends CorrectionProducer {
final String _libraryName;
final ShowElementCombinator _showCombinator;
final Set<String> _showNames;
_ImportLibraryShow(this._libraryName, this._showCombinator, this._showNames);
@override
List<Object> get fixArguments => [_libraryName];
@override
FixKind get fixKind => DartFixKind.IMPORT_LIBRARY_SHOW;
@override
Future<void> compute(ChangeBuilder builder) async {
var newShowCode = 'show ${_showNames.join(', ')}';
var offset = _showCombinator.offset;
var length = _showCombinator.end - offset;
var libraryFile = resolvedResult.libraryElement.source.fullName;
await builder.addDartFileEdit(libraryFile, (builder) {
builder.addSimpleReplacement(SourceRange(offset, length), newShowCode);
});
}
}
/// A correction processor that can make one of the possible change computed by
/// the [ImportLibrary] producer.
class _ImportRelativeLibrary extends CorrectionProducer {
final FixKind _fixKind;
final String _relativeURI;
_ImportRelativeLibrary(this._fixKind, this._relativeURI);
@override
List<Object> get fixArguments => [_relativeURI];
@override
FixKind get fixKind => _fixKind;
@override
Future<void> compute(ChangeBuilder builder) async {
await builder.addDartFileEdit(file, (builder) {
if (builder is DartFileEditBuilderImpl) {
builder.importLibraryWithRelativeUri(_relativeURI);
}
});
}
}