Add a quick fix to import extensions when members are referenced

Change-Id: Ic7f85cecdc06e255264441db7add6bd4a9bda5d5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/201800
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Brian Wilkerson 2021-06-01 23:28:25 +00:00 committed by commit-bot@chromium.org
parent b898375e27
commit 25b71e07e7
17 changed files with 708 additions and 70 deletions

View file

@ -3,6 +3,7 @@
// 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/completion/dart/extension_cache.dart';
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/instrumentation/service.dart';
@ -12,6 +13,9 @@ import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
///
/// Clients may not extend, implement or mix-in this class.
abstract class DartFixContext implements FixContext {
/// Return the extension cache used to find available extensions.
ExtensionCache get extensionCache;
/// Return the instrumentation service used to report errors that prevent a
/// fix from being composed.
InstrumentationService get instrumentationService;

View file

@ -686,6 +686,7 @@ class ServerContextManagerCallbacks extends ContextManagerCallbacks {
path, server.doAnalysisError_listFromEngine(result));
}
analysisServer.getDocumentationCacheFor(result)?.cacheFromResult(result);
analysisServer.getExtensionCacheFor(result)?.cacheFromResult(result);
var unit = result.unit;
if (unit != null) {
if (analysisServer._hasAnalysisServiceSubscription(

View file

@ -16,6 +16,7 @@ import 'package:analysis_server/src/plugin/plugin_watcher.dart';
import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
import 'package:analysis_server/src/server/diagnostic_server.dart';
import 'package:analysis_server/src/services/completion/dart/documentation_cache.dart';
import 'package:analysis_server/src/services/completion/dart/extension_cache.dart';
import 'package:analysis_server/src/services/correction/namespace.dart';
import 'package:analysis_server/src/services/pub/pub_api.dart';
import 'package:analysis_server/src/services/pub/pub_package_service.dart';
@ -91,6 +92,10 @@ abstract class AbstractAnalysisServer {
/// each context.
Map<AnalysisContext, DocumentationCache> documentationForContext = {};
/// A map from analysis contexts to the extension cache associated with
/// each context.
Map<AnalysisContext, ExtensionCache> extensionForContext = {};
/// The DiagnosticServer for this AnalysisServer. If available, it can be used
/// to start an http diagnostics server or return the port for an existing
/// server.
@ -229,6 +234,7 @@ abstract class AbstractAnalysisServer {
void addContextsToDeclarationsTracker() {
declarationsTracker?.discardContexts();
documentationForContext.clear();
extensionForContext.clear();
for (var driver in driverMap.values) {
declarationsTracker?.addContext(driver.analysisContext!);
driver.resetUriResolution();
@ -347,6 +353,14 @@ abstract class AbstractAnalysisServer {
return element;
}
/// Return the object used to cache information about extensions in the
/// context that produced the [result], or `null` if there is no cache for the
/// context.
ExtensionCache? getExtensionCacheFor(ResolvedUnitResult result) {
var context = result.session.analysisContext;
return extensionForContext.putIfAbsent(context, () => ExtensionCache());
}
/// Return a [Future] that completes with the resolved [AstNode] at the
/// given [offset] of the given [file], or with `null` if there is no node as
/// the [offset].

View file

@ -636,7 +636,7 @@ class EditDomainHandler extends AbstractRequestHandler {
result.path!,
name,
);
});
}, extensionCache: server.getExtensionCacheFor(result));
List<Fix> fixes;
try {

View file

@ -312,7 +312,7 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
unit.path!,
name,
);
});
}, extensionCache: server.getExtensionCacheFor(unit));
final fixes = await fixContributor.computeFixes(context);
if (fixes.isNotEmpty) {
fixes.sort(Fix.SORT_BY_RELEVANCE);

View file

@ -836,6 +836,8 @@ class LspServerContextManagerCallbacks extends ContextManagerCallbacks {
final serverErrors = protocol.doAnalysisError_listFromEngine(result);
recordAnalysisErrors(path, serverErrors);
}
analysisServer.getDocumentationCacheFor(result)?.cacheFromResult(result);
analysisServer.getExtensionCacheFor(result)?.cacheFromResult(result);
final unit = result.unit;
if (unit != null) {
if (analysisServer.shouldSendClosingLabelsFor(path)) {

View file

@ -0,0 +1,105 @@
// Copyright (c) 2021, 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/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
/// Cached data about the extensions defined in a single analysis context.
class ExtensionCache {
/// A set containing the paths of the compilation units that have been cached.
/// The set is used to prevent caching the same data multiple times.
final Set<String> processedUnits = {};
/// A map from the name of a non-static public extension member to the set of
/// paths to libraries defining an extension member with that name.
final Map<String, Set<UnitInLibrary>> membersByName = {};
/// Initialize a newly created cache.
ExtensionCache();
/// Fill the cache with data from the [result].
void cacheFromResult(ResolvedUnitResult result) {
var element = result.unit?.declaredElement;
if (element != null) {
_cacheFromElement(element);
for (var library in result.libraryElement.importedLibraries) {
_cacheLibrary(library);
}
}
}
/// Fill the cache with data from the [compilationUnit].
void _cacheFromElement(CompilationUnitElement compilationUnit) {
// Record that we've cached data for the compilation unit.
var unitPath = _keyForUnit(compilationUnit);
processedUnits.add(unitPath);
// Flush any data that was previously cached for the compilation unit.
for (var set in membersByName.values) {
set.removeWhere((element) => element.unitPath == unitPath);
}
// Cache the data for the compilation unit.
var libraryPath = compilationUnit.librarySource.fullName;
for (var extension in compilationUnit.extensions) {
var extensionName = extension.name;
if (extensionName != null && !Identifier.isPrivateName(extensionName)) {
for (var member in extension.accessors) {
if (!member.isSynthetic) {
_recordMember(unitPath, libraryPath, member.displayName);
}
}
for (var member in extension.fields) {
if (!member.isSynthetic) {
_recordMember(unitPath, libraryPath, member.name);
}
}
for (var member in extension.methods) {
_recordMember(unitPath, libraryPath, member.name);
}
}
}
}
/// Cache the data for the given [library] and every library exported from it
/// if it hasn't already been cached.
void _cacheLibrary(LibraryElement library) {
if (_hasDataFor(library.definingCompilationUnit)) {
return;
}
for (var unit in library.units) {
_cacheFromElement(unit);
}
for (var exported in library.exportedLibraries) {
_cacheLibrary(exported);
}
}
/// Return `true` if the cache contains data for the [compilationUnit].
bool _hasDataFor(CompilationUnitElement compilationUnit) {
return processedUnits.contains(_keyForUnit(compilationUnit));
}
/// Return the key used in the [extensionCache] for the [compilationUnit].
String _keyForUnit(CompilationUnitElement compilationUnit) =>
compilationUnit.source.fullName;
/// Record that an extension member with the given [name] is defined in the
/// compilation unit with the [unitPath] in the library with the
/// [libraryPath].
void _recordMember(String unitPath, String libraryPath, String name) {
membersByName
.putIfAbsent(name, () => {})
.add(UnitInLibrary(unitPath, libraryPath));
}
}
/// A representation of a compilation unit in a library.
class UnitInLibrary {
final String unitPath;
final String libraryPath;
UnitInLibrary(this.unitPath, this.libraryPath);
}

View file

@ -4,13 +4,10 @@
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analysis_server/src/utilities/extensions/element.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/generic_inferrer.dart'
show GenericInferrer;
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
/// A contributor that produces suggestions based on the members of an
@ -87,7 +84,7 @@ class ExtensionMemberContributor extends DartCompletionContributor {
if (type == null) {
// Without a type we can't find the extensions that apply. We shouldn't
// get to this point, but there's an NPE if we invoke
// `_resolveExtendedType` when `type` is `null`, so we guard against it
// `resolvedExtendedType` when `type` is `null`, so we guard against it
// to ensure that we can return the suggestions from other providers.
return;
}
@ -101,7 +98,7 @@ class ExtensionMemberContributor extends DartCompletionContributor {
var nameScope = containingLibrary.scope;
for (var extension in nameScope.extensions) {
var extendedType =
_resolveExtendedType(containingLibrary, extension, type);
extension.resolvedExtendedType(containingLibrary, type);
if (extendedType != null && typeSystem.isSubtypeOf(type, extendedType)) {
var inheritanceDistance = 0.0;
if (type is InterfaceType && extendedType is InterfaceType) {
@ -141,34 +138,4 @@ class ExtensionMemberContributor extends DartCompletionContributor {
accessor: accessor, inheritanceDistance: inheritanceDistance);
}
}
/// Use the [type] of the object being extended in the [library] to compute
/// the actual type extended by the [extension]. Return the computed type,
/// or `null` if the type cannot be computed.
DartType? _resolveExtendedType(
LibraryElement library,
ExtensionElement extension,
DartType type,
) {
var typeParameters = extension.typeParameters;
var inferrer =
GenericInferrer(library.typeSystem as TypeSystemImpl, typeParameters);
inferrer.constrainArgument(
type,
extension.extendedType,
'extendedType',
);
var typeArguments = inferrer.infer(typeParameters,
failAtError: true, genericMetadataIsEnabled: true);
if (typeArguments == null) {
return null;
}
var substitution = Substitution.fromPairs(
typeParameters,
typeArguments,
);
return substitution.substituteType(
extension.extendedType,
);
}
}

View file

@ -6,6 +6,7 @@ import 'dart:math' as math;
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
import 'package:analysis_server/plugin/edit/fix/fix_dart.dart';
import 'package:analysis_server/src/services/completion/dart/extension_cache.dart';
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
import 'package:analysis_server/src/services/correction/fix/data_driven/transform_override_set.dart';
import 'package:analysis_server/src/services/correction/util.dart';
@ -393,6 +394,9 @@ abstract class _AbstractCorrectionProducer {
/// Returns the EOL to use for this [CompilationUnit].
String get eol => utils.endOfLine;
/// Return the extension cache used to find available extensions.
ExtensionCache get extensionCache => _context.dartFixContext!.extensionCache;
String get file => _context.file;
Flutter get flutter => Flutter.instance;

View file

@ -9,8 +9,11 @@ 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:analysis_server/src/services/linter/lint_names.dart';
import 'package:analysis_server/src/utilities/extensions/element.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.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';
@ -24,26 +27,58 @@ class ImportLibrary extends MultiCorrectionProducer {
@override
Iterable<CorrectionProducer> get producers sync* {
final node = this.node;
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;
var extensionName = node.name;
yield* _importLibraryForElement(
extensionName,
const [ElementKind.EXTENSION],
const [TopLevelDeclarationKind.extension]);
}
} else if (_importKind == _ImportKind.forExtensionMember) {
/// Return producers that will import extensions that apply to the
/// [targetType] and that define a member with the given [memberName].
Iterable<CorrectionProducer> importMatchingExtensions(
String memberName, DartType? targetType) sync* {
if (targetType == null) {
return;
}
var definingLibraries = extensionCache.membersByName[memberName];
if (definingLibraries != null) {
for (var definingLibrary in definingLibraries) {
var libraryPath = definingLibrary.libraryPath;
var uri = sessionHelper.session.uriConverter.pathToUri(libraryPath);
if (uri != null) {
yield* _importExtensionInLibrary(uri, targetType, memberName);
}
}
}
}
if (node is SimpleIdentifier) {
var memberName = node.name;
if (memberName.startsWith('_')) {
return;
}
yield* importMatchingExtensions(memberName, _targetType(node));
} else if (node is BinaryExpression) {
var memberName = node.operator.lexeme;
yield* importMatchingExtensions(
memberName, node.leftOperand.staticType);
}
} 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) {
var parent = node.parent;
if (parent is MethodInvocation) {
if (parent.realTarget != null || parent.methodName != node) {
return;
}
}
var name = (node as SimpleIdentifier).name;
var name = node.name;
yield* _importLibraryForElement(name, const [
ElementKind.FUNCTION,
ElementKind.TOP_LEVEL_VARIABLE
@ -127,6 +162,45 @@ class ImportLibrary extends MultiCorrectionProducer {
return null;
}
Iterable<CorrectionProducer> _importExtensionInLibrary(
Uri uri, DartType targetType, String memberName) sync* {
// Look to see whether the library at the [uri] is already imported. If it
// is, then we can check the extension elements without needing to perform
// additional analysis.
var foundImport = false;
for (var imp in libraryElement.imports) {
// prepare element
var importedLibrary = imp.importedLibrary;
if (importedLibrary == null || importedLibrary.source.uri != uri) {
continue;
}
foundImport = true;
for (var extension in importedLibrary.matchingExtensionsWithMember(
libraryElement, targetType, memberName)) {
// If the import has a combinator that needs to be updated, then offer
// to update it.
var combinators = imp.combinators;
if (combinators.length == 1) {
var combinator = combinators[0];
if (combinator is HideElementCombinator) {
// TODO(brianwilkerson) Support removing the extension name from a
// hide combinator.
} else if (combinator is ShowElementCombinator) {
yield _ImportLibraryShow(
uri.toString(), combinator, extension.name!);
}
}
}
}
// If the library at the [uri] is not already imported, we return a
// correction producer that will either add an import or not based on the
// result of analyzing the library.
if (!foundImport) {
yield _ImportLibraryContainingExtension(uri, targetType, memberName);
}
}
/// Returns a list of one or two import corrections.
///
/// If [relativeUri] is `null`, only one correction, with an absolute import
@ -187,21 +261,22 @@ class ImportLibrary extends MultiCorrectionProducer {
}
// 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;
if (combinators.length == 1) {
var combinator = combinators[0];
if (combinator is HideElementCombinator) {
// TODO(brianwilkerson) Support removing the element name from a
// hide combinator.
} else if (combinator is ShowElementCombinator) {
// 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, combinator, name);
}
// don't add this library again
alreadyImportedWithPrefix.add(libraryElement.source.fullName);
yield _ImportLibraryShow(libraryName, showCombinator, showNames);
}
}
// Find new top-level declarations.
@ -250,6 +325,47 @@ class ImportLibrary extends MultiCorrectionProducer {
return false;
}
/// If the [node] might represent an access to a member of a type, return the
/// type of the object being accessed, otherwise return `null`.
DartType? _targetType(SimpleIdentifier node) {
var parent = node.parent;
if (parent is MethodInvocation && parent.methodName == node) {
var target = parent.realTarget;
if (target != null) {
return target.staticType;
}
} else if (parent is PropertyAccess && parent.propertyName == node) {
return parent.realTarget.staticType;
} else if (parent is PrefixedIdentifier && parent.identifier == node) {
return parent.prefix.staticType;
}
// If there is no explicit target, then return the type of an implicit
// `this`.
DartType? enclosingThisType(AstNode node) {
var parent = node.parent;
if (parent is ClassOrMixinDeclaration) {
return parent.declaredElement?.thisType;
} else if (parent is ExtensionDeclaration) {
return parent.extendedType.type;
}
}
while (parent != null) {
if (parent is MethodDeclaration) {
if (!parent.isStatic) {
return enclosingThisType(parent);
}
return null;
} else if (parent is FieldDeclaration) {
if (!parent.isStatic) {
return enclosingThisType(parent);
}
return null;
}
parent = parent.parent;
}
}
/// 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);
@ -259,6 +375,9 @@ class ImportLibrary extends MultiCorrectionProducer {
static ImportLibrary forExtension() =>
ImportLibrary(_ImportKind.forExtension);
static ImportLibrary forExtensionMember() =>
ImportLibrary(_ImportKind.forExtensionMember);
/// 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);
@ -273,8 +392,7 @@ class ImportLibrary extends MultiCorrectionProducer {
static ImportLibrary forType() => ImportLibrary(_ImportKind.forType);
}
/// A correction processor that can make one of the possible change computed by
/// the [ImportLibrary] producer.
/// A correction processor that can add an import using an absolute URI.
class _ImportAbsoluteLibrary extends CorrectionProducer {
final FixKind _fixKind;
@ -301,13 +419,53 @@ class _ImportAbsoluteLibrary extends CorrectionProducer {
enum _ImportKind {
dartAsync,
forExtension,
forExtensionMember,
forFunction,
forTopLevelVariable,
forType
}
/// A correction processor that can make one of the possible change computed by
/// the [ImportLibrary] producer.
/// A correction processor that can add an import of a library containing an
/// extension, but which does so only if the extension applies to a given type.
class _ImportLibraryContainingExtension extends CorrectionProducer {
/// The URI of the library defining the extension.
Uri uri;
/// The type of the target that the extension must apply to.
DartType targetType;
/// The name of the member that the extension must declare.
String memberName;
/// The URI that is being proposed for the import directive.
String _uriText = '';
_ImportLibraryContainingExtension(this.uri, this.targetType, this.memberName);
@override
List<Object> get fixArguments => [_uriText];
@override
FixKind get fixKind => DartFixKind.IMPORT_LIBRARY_PROJECT1;
@override
Future<void> compute(ChangeBuilder builder) async {
var result = await sessionHelper.session.getLibraryByUri2(uri.toString());
if (result is LibraryElementResult) {
var library = result.element;
if (library
.matchingExtensionsWithMember(libraryElement, targetType, memberName)
.isNotEmpty) {
await builder.addDartFileEdit(file, (builder) {
_uriText = builder.importLibrary(uri);
});
}
}
}
}
/// A correction processor that can add a prefix to an identifier defined in a
/// library that is already imported but that is imported with a prefix.
class _ImportLibraryPrefix extends CorrectionProducer {
final LibraryElement _importedLibrary;
final PrefixElement _importPrefix;
@ -334,16 +492,16 @@ class _ImportLibraryPrefix extends CorrectionProducer {
}
}
/// A correction processor that can make one of the possible change computed by
/// the [ImportLibrary] producer.
/// A correction processor that can add a name to the show combinator of an
/// existing import.
class _ImportLibraryShow extends CorrectionProducer {
final String _libraryName;
final ShowElementCombinator _showCombinator;
final Set<String> _showNames;
final String _addedName;
_ImportLibraryShow(this._libraryName, this._showCombinator, this._showNames);
_ImportLibraryShow(this._libraryName, this._showCombinator, this._addedName);
@override
List<Object> get fixArguments => [_libraryName];
@ -353,7 +511,10 @@ class _ImportLibraryShow extends CorrectionProducer {
@override
Future<void> compute(ChangeBuilder builder) async {
var newShowCode = 'show ${_showNames.join(', ')}';
Set<String> showNames = SplayTreeSet<String>();
showNames.addAll(_showCombinator.shownNames);
showNames.add(_addedName);
var newShowCode = 'show ${showNames.join(', ')}';
var offset = _showCombinator.offset;
var length = _showCombinator.end - offset;
var libraryFile = resolvedResult.libraryElement.source.fullName;
@ -363,8 +524,7 @@ class _ImportLibraryShow extends CorrectionProducer {
}
}
/// A correction processor that can make one of the possible change computed by
/// the [ImportLibrary] producer.
/// A correction processor that can add an import using a relative URI.
class _ImportRelativeLibrary extends CorrectionProducer {
final FixKind _fixKind;

View file

@ -3,6 +3,7 @@
// 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/completion/dart/extension_cache.dart';
import 'package:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
import 'package:analysis_server/src/services/linter/lint_names.dart';
import 'package:analyzer/dart/analysis/results.dart';
@ -129,11 +130,16 @@ class DartFixContextImpl implements DartFixContext {
@override
final AnalysisError error;
@override
final ExtensionCache extensionCache;
final List<TopLevelDeclaration> Function(String name)
getTopLevelDeclarationsFunction;
DartFixContextImpl(this.instrumentationService, this.workspace,
this.resolveResult, this.error, this.getTopLevelDeclarationsFunction);
this.resolveResult, this.error, this.getTopLevelDeclarationsFunction,
{ExtensionCache? extensionCache})
: extensionCache = extensionCache ?? ExtensionCache();
@override
List<TopLevelDeclaration> getTopLevelDeclarations(String name) {

View file

@ -234,6 +234,7 @@ class FixInFileProcessor {
resolveResult,
error,
(name) => [],
extensionCache: context.extensionCache,
);
fixState = await _fixError(fixContext, fixState, generator(), error);
}
@ -1131,6 +1132,7 @@ class FixProcessor extends BaseProcessor {
],
CompileTimeErrorCode.UNDEFINED_GETTER: [
DataDriven.newInstance,
ImportLibrary.forExtensionMember,
ImportLibrary.forTopLevelVariable,
ImportLibrary.forType,
],
@ -1143,6 +1145,7 @@ class FixProcessor extends BaseProcessor {
],
CompileTimeErrorCode.UNDEFINED_METHOD: [
DataDriven.newInstance,
ImportLibrary.forExtensionMember,
ImportLibrary.forFunction,
ImportLibrary.forType,
],
@ -1150,9 +1153,13 @@ class FixProcessor extends BaseProcessor {
ChangeArgumentName.newInstance,
DataDriven.newInstance,
],
CompileTimeErrorCode.UNDEFINED_OPERATOR: [
ImportLibrary.forExtensionMember,
],
CompileTimeErrorCode.UNDEFINED_SETTER: [
DataDriven.newInstance,
// TODO(brianwilkerson) Support ImportLibrary
// TODO(brianwilkerson) Support ImportLibrary for non-extension members.
ImportLibrary.forExtensionMember,
],
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS: [
DataDriven.newInstance,

View file

@ -2,7 +2,12 @@
// 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/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/generic_inferrer.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
extension ClassElementExtensions on ClassElement {
/// Return `true` if this element represents the class `Iterable` from
@ -55,6 +60,58 @@ extension ElementExtension on Element {
}
}
extension ExtensionElementExtensions on ExtensionElement {
/// Use the [type] of the object being extended in the [library] to compute
/// the actual type extended by this [extension]. Return the computed type,
/// or `null` if the type can't be computed.
DartType? resolvedExtendedType(LibraryElement library, DartType type) {
final typeParameters = this.typeParameters;
var inferrer =
GenericInferrer(library.typeSystem as TypeSystemImpl, typeParameters);
inferrer.constrainArgument(
type,
extendedType,
'extendedType',
);
var typeArguments = inferrer.infer(typeParameters,
failAtError: true, genericMetadataIsEnabled: true);
if (typeArguments == null) {
return null;
}
var substitution = Substitution.fromPairs(
typeParameters,
typeArguments,
);
return substitution.substituteType(
extendedType,
);
}
}
extension LibraryElementExtensions on LibraryElement {
/// Return the extensions in this library that can be applied, within the
/// [containingLibrary], to the [targetType] and that define a member with the
/// given [memberName].
Iterable<ExtensionElement> matchingExtensionsWithMember(
LibraryElement containingLibrary,
DartType targetType,
String memberName) sync* {
for (var unit in units) {
for (var extension in unit.extensions) {
var extensionName = extension.name;
if (extensionName != null && !Identifier.isPrivateName(extensionName)) {
var extendedType =
extension.resolvedExtendedType(containingLibrary, targetType);
if (extendedType != null &&
typeSystem.isSubtypeOf(targetType, extendedType)) {
yield extension;
}
}
}
}
}
}
extension MethodElementExtensions on MethodElement {
/// Return `true` if this element represents the method `cast` from either
/// `Iterable`, `List`, `Map`, or `Set`.

View file

@ -79,6 +79,17 @@ main() {
''');
}
@failingTest
Future<void> test_propertyAccess() async {
// We should not offer to define a local variable named 'g'.
await resolveTestCode('''
void f(String s) {
s.g;
}
''');
await assertNoFix();
}
Future<void> test_read_typeAssignment() async {
await resolveTestCode('''
main() {

View file

@ -3,10 +3,12 @@
// 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/completion/dart/extension_cache.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/dart/analysis/results.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';
@ -172,9 +174,18 @@ abstract class FixProcessorLintTest extends FixProcessorTest {
/// A base class defining support for writing fix processor tests.
abstract class FixProcessorTest extends BaseFixProcessorTest {
/// The extension cache used for test purposes.
ExtensionCache extensionCache = ExtensionCache();
/// Return the kind of fixes being tested by this test class.
FixKind get kind;
Future<void> addUnimportedFile(String filePath, String content) async {
addSource(filePath, content);
var result = await session.getResolvedUnit2(filePath);
extensionCache.cacheFromResult(result as ResolvedUnitResult);
}
Future<void> assertHasFix(String expected,
{bool Function(AnalysisError)? errorFilter,
int? length,
@ -429,6 +440,7 @@ abstract class FixProcessorTest extends BaseFixProcessorTest {
var tracker = DeclarationsTracker(MemoryByteStore(), resourceProvider);
tracker.addContext(analysisContext);
extensionCache.cacheFromResult(testAnalysisResult);
var context = DartFixContextImpl(
TestInstrumentationService(),
@ -440,6 +452,7 @@ abstract class FixProcessorTest extends BaseFixProcessorTest {
provider.doTrackerWork();
return provider.get(analysisContext, testFile, name);
},
extensionCache: extensionCache,
);
return await DartFixContributor().computeFixes(context);
}

View file

@ -39,6 +39,201 @@ main() {
await assertNoFix();
}
Future<void> test_extension_notImported_field_onThisType_fromClass() async {
addUnimportedFile('/home/test/lib/lib2.dart', '''
import 'package:test/lib1.dart';
extension E on C {
int m() => 0;
}
''');
addSource('/home/test/lib/lib1.dart', '''
class C {}
''');
await resolveTestCode('''
import 'package:test/lib1.dart';
class D extends C {
int f = m();
}
''');
await assertHasFix('''
import 'package:test/lib1.dart';
import 'package:test/lib2.dart';
class D extends C {
int f = m();
}
''');
}
Future<void> test_extension_notImported_getter() async {
addUnimportedFile('/home/test/lib/lib.dart', '''
extension E on String {
int get m => 0;
}
''');
await resolveTestCode('''
void f(String s) {
s.m;
}
''');
await assertHasFix('''
import 'package:test/lib.dart';
void f(String s) {
s.m;
}
''');
}
Future<void> test_extension_notImported_method() async {
addUnimportedFile('/home/test/lib/lib.dart', '''
extension E on String {
void m() {}
}
''');
await resolveTestCode('''
void f(String s) {
s.m();
}
''');
await assertHasFix('''
import 'package:test/lib.dart';
void f(String s) {
s.m();
}
''');
}
Future<void> test_extension_notImported_method_extendsGeneric() async {
addUnimportedFile('/home/test/lib/lib.dart', '''
import 'package:test/lib1.dart';
extension E<T extends num> on List<T> {
void m() {}
}
''');
await resolveTestCode('''
void f(List<int> l) {
l.m();
}
''');
await assertHasFix('''
import 'package:test/lib.dart';
void f(List<int> l) {
l.m();
}
''');
}
Future<void> test_extension_notImported_method_onThisType_fromClass() async {
addUnimportedFile('/home/test/lib/lib2.dart', '''
import 'package:test/lib1.dart';
extension E on C {
void m() {}
}
''');
addSource('/home/test/lib/lib1.dart', '''
class C {}
''');
await resolveTestCode('''
import 'package:test/lib1.dart';
class D extends C {
void m2() {
m();
}
}
''');
await assertHasFix('''
import 'package:test/lib1.dart';
import 'package:test/lib2.dart';
class D extends C {
void m2() {
m();
}
}
''');
}
Future<void>
test_extension_notImported_method_onThisType_fromExtension() async {
addUnimportedFile('/home/test/lib/lib2.dart', '''
import 'package:test/lib1.dart';
extension E on C {
void m() {}
}
''');
addSource('/home/test/lib/lib1.dart', '''
class C {}
''');
await resolveTestCode('''
import 'package:test/lib1.dart';
extension F on C {
void m2() {
m();
}
}
''');
await assertHasFix('''
import 'package:test/lib1.dart';
import 'package:test/lib2.dart';
extension F on C {
void m2() {
m();
}
}
''');
}
Future<void> test_extension_notImported_operator() async {
addUnimportedFile('/home/test/lib/lib.dart', '''
extension E on String {
String operator -(String other) => this;
}
''');
await resolveTestCode('''
void f(String s) {
s - '2';
}
''');
await assertHasFix('''
import 'package:test/lib.dart';
void f(String s) {
s - '2';
}
''');
}
Future<void> test_extension_notImported_setter() async {
addUnimportedFile('/home/test/lib/lib.dart', '''
extension E on String {
set m(int v) {}
}
''');
await resolveTestCode('''
void f(String s) {
s.m = 2;
}
''');
await assertHasFix('''
import 'package:test/lib.dart';
void f(String s) {
s.m = 2;
}
''');
}
Future<void> test_invalidUri_interpolation() async {
addSource('/home/test/lib/lib.dart', r'''
class Test {

View file

@ -19,6 +19,98 @@ class ImportLibraryShowTest extends FixProcessorTest {
@override
FixKind get kind => DartFixKind.IMPORT_LIBRARY_SHOW;
Future<void> test_extension_notShown_getter() async {
addSource('/home/test/lib/lib.dart', '''
class C {}
extension E on String {
int get m => 0;
}
''');
await resolveTestCode('''
import 'package:test/lib.dart' show C;
void f(String s, C c) {
s.m;
}
''');
await assertHasFix('''
import 'package:test/lib.dart' show C, E;
void f(String s, C c) {
s.m;
}
''');
}
Future<void> test_extension_notShown_method() async {
addSource('/home/test/lib/lib.dart', '''
class C {}
extension E on String {
void m() {}
}
''');
await resolveTestCode('''
import 'package:test/lib.dart' show C;
void f(String s, C c) {
s.m();
}
''');
await assertHasFix('''
import 'package:test/lib.dart' show C, E;
void f(String s, C c) {
s.m();
}
''');
}
Future<void> test_extension_notShown_operator() async {
addSource('/home/test/lib/lib.dart', '''
class C {}
extension E on String {
String operator -(String other) => this;
}
''');
await resolveTestCode('''
import 'package:test/lib.dart' show C;
void f(String s, C c) {
s - '2';
}
''');
await assertHasFix('''
import 'package:test/lib.dart' show C, E;
void f(String s, C c) {
s - '2';
}
''');
}
Future<void> test_extension_notShown_setter() async {
addSource('/home/test/lib/lib.dart', '''
class C {}
extension E on String {
set m(int v) {}
}
''');
await resolveTestCode('''
import 'package:test/lib.dart' show C;
void f(String s, C c) {
s.m = 2;
}
''');
await assertHasFix('''
import 'package:test/lib.dart' show C, E;
void f(String s, C c) {
s.m = 2;
}
''');
}
Future<void> test_override_samePackage() async {
addSource('/home/test/lib/lib.dart', '''
class A {}