mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:59:47 +00:00
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:
parent
b898375e27
commit
25b71e07e7
|
@ -3,6 +3,7 @@
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
// 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/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:analysis_server/src/services/correction/fix/dart/top_level_declarations.dart';
|
||||||
import 'package:analyzer/dart/analysis/results.dart';
|
import 'package:analyzer/dart/analysis/results.dart';
|
||||||
import 'package:analyzer/instrumentation/service.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.
|
/// Clients may not extend, implement or mix-in this class.
|
||||||
abstract class DartFixContext implements FixContext {
|
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
|
/// Return the instrumentation service used to report errors that prevent a
|
||||||
/// fix from being composed.
|
/// fix from being composed.
|
||||||
InstrumentationService get instrumentationService;
|
InstrumentationService get instrumentationService;
|
||||||
|
|
|
@ -686,6 +686,7 @@ class ServerContextManagerCallbacks extends ContextManagerCallbacks {
|
||||||
path, server.doAnalysisError_listFromEngine(result));
|
path, server.doAnalysisError_listFromEngine(result));
|
||||||
}
|
}
|
||||||
analysisServer.getDocumentationCacheFor(result)?.cacheFromResult(result);
|
analysisServer.getDocumentationCacheFor(result)?.cacheFromResult(result);
|
||||||
|
analysisServer.getExtensionCacheFor(result)?.cacheFromResult(result);
|
||||||
var unit = result.unit;
|
var unit = result.unit;
|
||||||
if (unit != null) {
|
if (unit != null) {
|
||||||
if (analysisServer._hasAnalysisServiceSubscription(
|
if (analysisServer._hasAnalysisServiceSubscription(
|
||||||
|
|
|
@ -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/crash_reporting_attachments.dart';
|
||||||
import 'package:analysis_server/src/server/diagnostic_server.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/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/correction/namespace.dart';
|
||||||
import 'package:analysis_server/src/services/pub/pub_api.dart';
|
import 'package:analysis_server/src/services/pub/pub_api.dart';
|
||||||
import 'package:analysis_server/src/services/pub/pub_package_service.dart';
|
import 'package:analysis_server/src/services/pub/pub_package_service.dart';
|
||||||
|
@ -91,6 +92,10 @@ abstract class AbstractAnalysisServer {
|
||||||
/// each context.
|
/// each context.
|
||||||
Map<AnalysisContext, DocumentationCache> documentationForContext = {};
|
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
|
/// The DiagnosticServer for this AnalysisServer. If available, it can be used
|
||||||
/// to start an http diagnostics server or return the port for an existing
|
/// to start an http diagnostics server or return the port for an existing
|
||||||
/// server.
|
/// server.
|
||||||
|
@ -229,6 +234,7 @@ abstract class AbstractAnalysisServer {
|
||||||
void addContextsToDeclarationsTracker() {
|
void addContextsToDeclarationsTracker() {
|
||||||
declarationsTracker?.discardContexts();
|
declarationsTracker?.discardContexts();
|
||||||
documentationForContext.clear();
|
documentationForContext.clear();
|
||||||
|
extensionForContext.clear();
|
||||||
for (var driver in driverMap.values) {
|
for (var driver in driverMap.values) {
|
||||||
declarationsTracker?.addContext(driver.analysisContext!);
|
declarationsTracker?.addContext(driver.analysisContext!);
|
||||||
driver.resetUriResolution();
|
driver.resetUriResolution();
|
||||||
|
@ -347,6 +353,14 @@ abstract class AbstractAnalysisServer {
|
||||||
return element;
|
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
|
/// 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
|
/// given [offset] of the given [file], or with `null` if there is no node as
|
||||||
/// the [offset].
|
/// the [offset].
|
||||||
|
|
|
@ -636,7 +636,7 @@ class EditDomainHandler extends AbstractRequestHandler {
|
||||||
result.path!,
|
result.path!,
|
||||||
name,
|
name,
|
||||||
);
|
);
|
||||||
});
|
}, extensionCache: server.getExtensionCacheFor(result));
|
||||||
|
|
||||||
List<Fix> fixes;
|
List<Fix> fixes;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -312,7 +312,7 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
|
||||||
unit.path!,
|
unit.path!,
|
||||||
name,
|
name,
|
||||||
);
|
);
|
||||||
});
|
}, extensionCache: server.getExtensionCacheFor(unit));
|
||||||
final fixes = await fixContributor.computeFixes(context);
|
final fixes = await fixContributor.computeFixes(context);
|
||||||
if (fixes.isNotEmpty) {
|
if (fixes.isNotEmpty) {
|
||||||
fixes.sort(Fix.SORT_BY_RELEVANCE);
|
fixes.sort(Fix.SORT_BY_RELEVANCE);
|
||||||
|
|
|
@ -836,6 +836,8 @@ class LspServerContextManagerCallbacks extends ContextManagerCallbacks {
|
||||||
final serverErrors = protocol.doAnalysisError_listFromEngine(result);
|
final serverErrors = protocol.doAnalysisError_listFromEngine(result);
|
||||||
recordAnalysisErrors(path, serverErrors);
|
recordAnalysisErrors(path, serverErrors);
|
||||||
}
|
}
|
||||||
|
analysisServer.getDocumentationCacheFor(result)?.cacheFromResult(result);
|
||||||
|
analysisServer.getExtensionCacheFor(result)?.cacheFromResult(result);
|
||||||
final unit = result.unit;
|
final unit = result.unit;
|
||||||
if (unit != null) {
|
if (unit != null) {
|
||||||
if (analysisServer.shouldSendClosingLabelsFor(path)) {
|
if (analysisServer.shouldSendClosingLabelsFor(path)) {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -4,13 +4,10 @@
|
||||||
|
|
||||||
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
|
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/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/ast/ast.dart';
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
import 'package:analyzer/dart/element/type.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';
|
import 'package:analyzer/src/dart/resolver/scope.dart';
|
||||||
|
|
||||||
/// A contributor that produces suggestions based on the members of an
|
/// A contributor that produces suggestions based on the members of an
|
||||||
|
@ -87,7 +84,7 @@ class ExtensionMemberContributor extends DartCompletionContributor {
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
// Without a type we can't find the extensions that apply. We shouldn't
|
// 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
|
// 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.
|
// to ensure that we can return the suggestions from other providers.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +98,7 @@ class ExtensionMemberContributor extends DartCompletionContributor {
|
||||||
var nameScope = containingLibrary.scope;
|
var nameScope = containingLibrary.scope;
|
||||||
for (var extension in nameScope.extensions) {
|
for (var extension in nameScope.extensions) {
|
||||||
var extendedType =
|
var extendedType =
|
||||||
_resolveExtendedType(containingLibrary, extension, type);
|
extension.resolvedExtendedType(containingLibrary, type);
|
||||||
if (extendedType != null && typeSystem.isSubtypeOf(type, extendedType)) {
|
if (extendedType != null && typeSystem.isSubtypeOf(type, extendedType)) {
|
||||||
var inheritanceDistance = 0.0;
|
var inheritanceDistance = 0.0;
|
||||||
if (type is InterfaceType && extendedType is InterfaceType) {
|
if (type is InterfaceType && extendedType is InterfaceType) {
|
||||||
|
@ -141,34 +138,4 @@ class ExtensionMemberContributor extends DartCompletionContributor {
|
||||||
accessor: accessor, inheritanceDistance: inheritanceDistance);
|
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
|
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
|
||||||
import 'package:analysis_server/plugin/edit/fix/fix_dart.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/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/fix/data_driven/transform_override_set.dart';
|
||||||
import 'package:analysis_server/src/services/correction/util.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].
|
/// Returns the EOL to use for this [CompilationUnit].
|
||||||
String get eol => utils.endOfLine;
|
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;
|
String get file => _context.file;
|
||||||
|
|
||||||
Flutter get flutter => Flutter.instance;
|
Flutter get flutter => Flutter.instance;
|
||||||
|
|
|
@ -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/fix/dart/top_level_declarations.dart';
|
||||||
import 'package:analysis_server/src/services/correction/namespace.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/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/ast/ast.dart';
|
||||||
import 'package:analyzer/dart/element/element.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/source/source_range.dart';
|
||||||
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart.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/change_builder/change_builder_core.dart';
|
||||||
|
@ -24,26 +27,58 @@ class ImportLibrary extends MultiCorrectionProducer {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Iterable<CorrectionProducer> get producers sync* {
|
Iterable<CorrectionProducer> get producers sync* {
|
||||||
|
final node = this.node;
|
||||||
if (_importKind == _ImportKind.dartAsync) {
|
if (_importKind == _ImportKind.dartAsync) {
|
||||||
yield* _importLibrary(DartFixKind.IMPORT_ASYNC, Uri.parse('dart:async'));
|
yield* _importLibrary(DartFixKind.IMPORT_ASYNC, Uri.parse('dart:async'));
|
||||||
} else if (_importKind == _ImportKind.forExtension) {
|
} else if (_importKind == _ImportKind.forExtension) {
|
||||||
if (node is SimpleIdentifier) {
|
if (node is SimpleIdentifier) {
|
||||||
var extensionName = (node as SimpleIdentifier).name;
|
var extensionName = node.name;
|
||||||
yield* _importLibraryForElement(
|
yield* _importLibraryForElement(
|
||||||
extensionName,
|
extensionName,
|
||||||
const [ElementKind.EXTENSION],
|
const [ElementKind.EXTENSION],
|
||||||
const [TopLevelDeclarationKind.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) {
|
} else if (_importKind == _ImportKind.forFunction) {
|
||||||
if (node is SimpleIdentifier) {
|
if (node is SimpleIdentifier) {
|
||||||
if (node.parent is MethodInvocation) {
|
var parent = node.parent;
|
||||||
var invocation = node.parent as MethodInvocation;
|
if (parent is MethodInvocation) {
|
||||||
if (invocation.realTarget != null || invocation.methodName != node) {
|
if (parent.realTarget != null || parent.methodName != node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = (node as SimpleIdentifier).name;
|
var name = node.name;
|
||||||
yield* _importLibraryForElement(name, const [
|
yield* _importLibraryForElement(name, const [
|
||||||
ElementKind.FUNCTION,
|
ElementKind.FUNCTION,
|
||||||
ElementKind.TOP_LEVEL_VARIABLE
|
ElementKind.TOP_LEVEL_VARIABLE
|
||||||
|
@ -127,6 +162,45 @@ class ImportLibrary extends MultiCorrectionProducer {
|
||||||
return null;
|
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.
|
/// Returns a list of one or two import corrections.
|
||||||
///
|
///
|
||||||
/// If [relativeUri] is `null`, only one correction, with an absolute import
|
/// If [relativeUri] is `null`, only one correction, with an absolute import
|
||||||
|
@ -187,21 +261,22 @@ class ImportLibrary extends MultiCorrectionProducer {
|
||||||
}
|
}
|
||||||
// may be update "show" directive
|
// may be update "show" directive
|
||||||
var combinators = imp.combinators;
|
var combinators = imp.combinators;
|
||||||
if (combinators.length == 1 && combinators[0] is ShowElementCombinator) {
|
if (combinators.length == 1) {
|
||||||
var showCombinator = combinators[0] as ShowElementCombinator;
|
var combinator = combinators[0];
|
||||||
// prepare new set of names to show
|
if (combinator is HideElementCombinator) {
|
||||||
Set<String> showNames = SplayTreeSet<String>();
|
// TODO(brianwilkerson) Support removing the element name from a
|
||||||
showNames.addAll(showCombinator.shownNames);
|
// hide combinator.
|
||||||
showNames.add(name);
|
} else if (combinator is ShowElementCombinator) {
|
||||||
// prepare library name - unit name or 'dart:name' for SDK library
|
// prepare library name - unit name or 'dart:name' for SDK library
|
||||||
var libraryName =
|
var libraryName =
|
||||||
libraryElement.definingCompilationUnit.source.uri.toString();
|
libraryElement.definingCompilationUnit.source.uri.toString();
|
||||||
if (libraryElement.isInSdk) {
|
if (libraryElement.isInSdk) {
|
||||||
libraryName = libraryElement.source.shortName;
|
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.
|
// Find new top-level declarations.
|
||||||
|
@ -250,6 +325,47 @@ class ImportLibrary extends MultiCorrectionProducer {
|
||||||
return false;
|
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`.
|
/// Return an instance of this class that will add an import of `dart:async`.
|
||||||
/// Used as a tear-off in `FixProcessor`.
|
/// Used as a tear-off in `FixProcessor`.
|
||||||
static ImportLibrary dartAsync() => ImportLibrary(_ImportKind.dartAsync);
|
static ImportLibrary dartAsync() => ImportLibrary(_ImportKind.dartAsync);
|
||||||
|
@ -259,6 +375,9 @@ class ImportLibrary extends MultiCorrectionProducer {
|
||||||
static ImportLibrary forExtension() =>
|
static ImportLibrary forExtension() =>
|
||||||
ImportLibrary(_ImportKind.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
|
/// Return an instance of this class that will add an import for a top-level
|
||||||
/// function. Used as a tear-off in `FixProcessor`.
|
/// function. Used as a tear-off in `FixProcessor`.
|
||||||
static ImportLibrary forFunction() => ImportLibrary(_ImportKind.forFunction);
|
static ImportLibrary forFunction() => ImportLibrary(_ImportKind.forFunction);
|
||||||
|
@ -273,8 +392,7 @@ class ImportLibrary extends MultiCorrectionProducer {
|
||||||
static ImportLibrary forType() => ImportLibrary(_ImportKind.forType);
|
static ImportLibrary forType() => ImportLibrary(_ImportKind.forType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A correction processor that can make one of the possible change computed by
|
/// A correction processor that can add an import using an absolute URI.
|
||||||
/// the [ImportLibrary] producer.
|
|
||||||
class _ImportAbsoluteLibrary extends CorrectionProducer {
|
class _ImportAbsoluteLibrary extends CorrectionProducer {
|
||||||
final FixKind _fixKind;
|
final FixKind _fixKind;
|
||||||
|
|
||||||
|
@ -301,13 +419,53 @@ class _ImportAbsoluteLibrary extends CorrectionProducer {
|
||||||
enum _ImportKind {
|
enum _ImportKind {
|
||||||
dartAsync,
|
dartAsync,
|
||||||
forExtension,
|
forExtension,
|
||||||
|
forExtensionMember,
|
||||||
forFunction,
|
forFunction,
|
||||||
forTopLevelVariable,
|
forTopLevelVariable,
|
||||||
forType
|
forType
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A correction processor that can make one of the possible change computed by
|
/// A correction processor that can add an import of a library containing an
|
||||||
/// the [ImportLibrary] producer.
|
/// 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 {
|
class _ImportLibraryPrefix extends CorrectionProducer {
|
||||||
final LibraryElement _importedLibrary;
|
final LibraryElement _importedLibrary;
|
||||||
final PrefixElement _importPrefix;
|
final PrefixElement _importPrefix;
|
||||||
|
@ -334,16 +492,16 @@ class _ImportLibraryPrefix extends CorrectionProducer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A correction processor that can make one of the possible change computed by
|
/// A correction processor that can add a name to the show combinator of an
|
||||||
/// the [ImportLibrary] producer.
|
/// existing import.
|
||||||
class _ImportLibraryShow extends CorrectionProducer {
|
class _ImportLibraryShow extends CorrectionProducer {
|
||||||
final String _libraryName;
|
final String _libraryName;
|
||||||
|
|
||||||
final ShowElementCombinator _showCombinator;
|
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
|
@override
|
||||||
List<Object> get fixArguments => [_libraryName];
|
List<Object> get fixArguments => [_libraryName];
|
||||||
|
@ -353,7 +511,10 @@ class _ImportLibraryShow extends CorrectionProducer {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> compute(ChangeBuilder builder) async {
|
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 offset = _showCombinator.offset;
|
||||||
var length = _showCombinator.end - offset;
|
var length = _showCombinator.end - offset;
|
||||||
var libraryFile = resolvedResult.libraryElement.source.fullName;
|
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
|
/// A correction processor that can add an import using a relative URI.
|
||||||
/// the [ImportLibrary] producer.
|
|
||||||
class _ImportRelativeLibrary extends CorrectionProducer {
|
class _ImportRelativeLibrary extends CorrectionProducer {
|
||||||
final FixKind _fixKind;
|
final FixKind _fixKind;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
// 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/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/dart/top_level_declarations.dart';
|
||||||
import 'package:analysis_server/src/services/linter/lint_names.dart';
|
import 'package:analysis_server/src/services/linter/lint_names.dart';
|
||||||
import 'package:analyzer/dart/analysis/results.dart';
|
import 'package:analyzer/dart/analysis/results.dart';
|
||||||
|
@ -129,11 +130,16 @@ class DartFixContextImpl implements DartFixContext {
|
||||||
@override
|
@override
|
||||||
final AnalysisError error;
|
final AnalysisError error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final ExtensionCache extensionCache;
|
||||||
|
|
||||||
final List<TopLevelDeclaration> Function(String name)
|
final List<TopLevelDeclaration> Function(String name)
|
||||||
getTopLevelDeclarationsFunction;
|
getTopLevelDeclarationsFunction;
|
||||||
|
|
||||||
DartFixContextImpl(this.instrumentationService, this.workspace,
|
DartFixContextImpl(this.instrumentationService, this.workspace,
|
||||||
this.resolveResult, this.error, this.getTopLevelDeclarationsFunction);
|
this.resolveResult, this.error, this.getTopLevelDeclarationsFunction,
|
||||||
|
{ExtensionCache? extensionCache})
|
||||||
|
: extensionCache = extensionCache ?? ExtensionCache();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<TopLevelDeclaration> getTopLevelDeclarations(String name) {
|
List<TopLevelDeclaration> getTopLevelDeclarations(String name) {
|
||||||
|
|
|
@ -234,6 +234,7 @@ class FixInFileProcessor {
|
||||||
resolveResult,
|
resolveResult,
|
||||||
error,
|
error,
|
||||||
(name) => [],
|
(name) => [],
|
||||||
|
extensionCache: context.extensionCache,
|
||||||
);
|
);
|
||||||
fixState = await _fixError(fixContext, fixState, generator(), error);
|
fixState = await _fixError(fixContext, fixState, generator(), error);
|
||||||
}
|
}
|
||||||
|
@ -1131,6 +1132,7 @@ class FixProcessor extends BaseProcessor {
|
||||||
],
|
],
|
||||||
CompileTimeErrorCode.UNDEFINED_GETTER: [
|
CompileTimeErrorCode.UNDEFINED_GETTER: [
|
||||||
DataDriven.newInstance,
|
DataDriven.newInstance,
|
||||||
|
ImportLibrary.forExtensionMember,
|
||||||
ImportLibrary.forTopLevelVariable,
|
ImportLibrary.forTopLevelVariable,
|
||||||
ImportLibrary.forType,
|
ImportLibrary.forType,
|
||||||
],
|
],
|
||||||
|
@ -1143,6 +1145,7 @@ class FixProcessor extends BaseProcessor {
|
||||||
],
|
],
|
||||||
CompileTimeErrorCode.UNDEFINED_METHOD: [
|
CompileTimeErrorCode.UNDEFINED_METHOD: [
|
||||||
DataDriven.newInstance,
|
DataDriven.newInstance,
|
||||||
|
ImportLibrary.forExtensionMember,
|
||||||
ImportLibrary.forFunction,
|
ImportLibrary.forFunction,
|
||||||
ImportLibrary.forType,
|
ImportLibrary.forType,
|
||||||
],
|
],
|
||||||
|
@ -1150,9 +1153,13 @@ class FixProcessor extends BaseProcessor {
|
||||||
ChangeArgumentName.newInstance,
|
ChangeArgumentName.newInstance,
|
||||||
DataDriven.newInstance,
|
DataDriven.newInstance,
|
||||||
],
|
],
|
||||||
|
CompileTimeErrorCode.UNDEFINED_OPERATOR: [
|
||||||
|
ImportLibrary.forExtensionMember,
|
||||||
|
],
|
||||||
CompileTimeErrorCode.UNDEFINED_SETTER: [
|
CompileTimeErrorCode.UNDEFINED_SETTER: [
|
||||||
DataDriven.newInstance,
|
DataDriven.newInstance,
|
||||||
// TODO(brianwilkerson) Support ImportLibrary
|
// TODO(brianwilkerson) Support ImportLibrary for non-extension members.
|
||||||
|
ImportLibrary.forExtensionMember,
|
||||||
],
|
],
|
||||||
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS: [
|
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS: [
|
||||||
DataDriven.newInstance,
|
DataDriven.newInstance,
|
||||||
|
|
|
@ -2,7 +2,12 @@
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
// 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.
|
// 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/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 {
|
extension ClassElementExtensions on ClassElement {
|
||||||
/// Return `true` if this element represents the class `Iterable` from
|
/// 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 {
|
extension MethodElementExtensions on MethodElement {
|
||||||
/// Return `true` if this element represents the method `cast` from either
|
/// Return `true` if this element represents the method `cast` from either
|
||||||
/// `Iterable`, `List`, `Map`, or `Set`.
|
/// `Iterable`, `List`, `Map`, or `Set`.
|
||||||
|
|
|
@ -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 {
|
Future<void> test_read_typeAssignment() async {
|
||||||
await resolveTestCode('''
|
await resolveTestCode('''
|
||||||
main() {
|
main() {
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
// 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/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/change_workspace.dart';
|
||||||
import 'package:analysis_server/src/services/correction/fix.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/dart/top_level_declarations.dart';
|
||||||
import 'package:analysis_server/src/services/correction/fix_internal.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/error/error.dart';
|
||||||
import 'package:analyzer/src/dart/analysis/byte_store.dart';
|
import 'package:analyzer/src/dart/analysis/byte_store.dart';
|
||||||
import 'package:analyzer/src/dart/error/lint_codes.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.
|
/// A base class defining support for writing fix processor tests.
|
||||||
abstract class FixProcessorTest extends BaseFixProcessorTest {
|
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.
|
/// Return the kind of fixes being tested by this test class.
|
||||||
FixKind get kind;
|
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,
|
Future<void> assertHasFix(String expected,
|
||||||
{bool Function(AnalysisError)? errorFilter,
|
{bool Function(AnalysisError)? errorFilter,
|
||||||
int? length,
|
int? length,
|
||||||
|
@ -429,6 +440,7 @@ abstract class FixProcessorTest extends BaseFixProcessorTest {
|
||||||
|
|
||||||
var tracker = DeclarationsTracker(MemoryByteStore(), resourceProvider);
|
var tracker = DeclarationsTracker(MemoryByteStore(), resourceProvider);
|
||||||
tracker.addContext(analysisContext);
|
tracker.addContext(analysisContext);
|
||||||
|
extensionCache.cacheFromResult(testAnalysisResult);
|
||||||
|
|
||||||
var context = DartFixContextImpl(
|
var context = DartFixContextImpl(
|
||||||
TestInstrumentationService(),
|
TestInstrumentationService(),
|
||||||
|
@ -440,6 +452,7 @@ abstract class FixProcessorTest extends BaseFixProcessorTest {
|
||||||
provider.doTrackerWork();
|
provider.doTrackerWork();
|
||||||
return provider.get(analysisContext, testFile, name);
|
return provider.get(analysisContext, testFile, name);
|
||||||
},
|
},
|
||||||
|
extensionCache: extensionCache,
|
||||||
);
|
);
|
||||||
return await DartFixContributor().computeFixes(context);
|
return await DartFixContributor().computeFixes(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,201 @@ main() {
|
||||||
await assertNoFix();
|
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 {
|
Future<void> test_invalidUri_interpolation() async {
|
||||||
addSource('/home/test/lib/lib.dart', r'''
|
addSource('/home/test/lib/lib.dart', r'''
|
||||||
class Test {
|
class Test {
|
||||||
|
|
|
@ -19,6 +19,98 @@ class ImportLibraryShowTest extends FixProcessorTest {
|
||||||
@override
|
@override
|
||||||
FixKind get kind => DartFixKind.IMPORT_LIBRARY_SHOW;
|
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 {
|
Future<void> test_override_samePackage() async {
|
||||||
addSource('/home/test/lib/lib.dart', '''
|
addSource('/home/test/lib/lib.dart', '''
|
||||||
class A {}
|
class A {}
|
||||||
|
|
Loading…
Reference in a new issue