Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New assist to add/edit hide at import for ambiguous import #56762

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// Copyright (c) 2024, 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:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/utilities/extensions/results.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:collection/collection.dart';

class AmbiguousImportFix extends MultiCorrectionProducer {
AmbiguousImportFix({required super.context});

@override
Future<List<ResolvedCorrectionProducer>> get producers async {
var node = this.node;
Element2? element;
String? prefix;
if (node is NamedType) {
element = node.element2;
prefix = node.importPrefix?.name.lexeme;
} else if (node is SimpleIdentifier) {
element = node.element;
if (node.parent case PrefixedIdentifier(prefix: var currentPrefix)) {
prefix = currentPrefix.name;
}
}
if (element is! MultiplyDefinedElement2) {
return const [];
}
var conflictingElements = element.conflictingElements2;
var name = element.name3;
if (name == null || name.isEmpty) {
return const [];
}

var (uris, unit, importDirectives) = _getImportDirectives(
libraryResult,
unitResult,
conflictingElements,
prefix,
);

if (unit == null || importDirectives.isEmpty || uris.isEmpty) {
return const [];
}

var producers = <ResolvedCorrectionProducer>{};
for (var uri in uris) {
var thisContext = CorrectionProducerContext.createResolved(
libraryResult: libraryResult,
unitResult: unit,
applyingBulkFixes: applyingBulkFixes,
dartFixContext: context.dartFixContext,
diagnostic: diagnostic,
selectionLength: selectionLength,
selectionOffset: selectionOffset,
);
var directives = importDirectives
.whereNot((directive) => directive.uri.stringValue == uri)
.toSet();
producers.addAll([
_ImportAddHide(name, uri, prefix, directives, context: thisContext),
_ImportRemoveShow(name, uri, prefix, directives, context: thisContext),
]);
}
return producers.toList();
}

/// Returns [ImportDirective]s that import the given [conflictingElements]
/// into [unitResult].
(Set<String>, ResolvedUnitResult?, Set<ImportDirective>) _getImportDirectives(
ResolvedLibraryResult libraryResult,
ResolvedUnitResult? unitResult,
List<Element2> conflictingElements,
String? prefix,
) {
var uris = <String>{};
var importDirectives = <ImportDirective>{};
// Search in each unit up the chain for related imports.
while (unitResult is ResolvedUnitResult) {
for (var conflictingElement in conflictingElements) {
var library = conflictingElement.enclosingElement2?.library2;
if (library == null) {
continue;
}

// Find all ImportDirective that import this library in this unit
// and have the same prefix.
for (var directive in unitResult.unit.directives
.whereType<ImportDirective>()
.whereNot(importDirectives.contains)) {
var imported = directive.libraryImport?.importedLibrary2;
if (imported == null) {
continue;
}
// If the prefix is different, then this directive is not relevant.
if (directive.prefix?.name != prefix) {
continue;
}

// If this library is imported directly or if the directive exports the
// library for this element.
if (imported == library ||
imported.exportedLibraries2.contains(library)) {
var uri = directive.uri.stringValue;
if (uri != null) {
uris.add(uri);
importDirectives.add(directive);
}
}
}
}

if (importDirectives.isNotEmpty) {
break;
}

// We continue up the chain.
unitResult = libraryResult.parentUnitOf(unitResult);
}

return (uris, unitResult, importDirectives);
}
}

class _ImportAddHide extends ResolvedCorrectionProducer {
final Set<ImportDirective> importDirectives;
final String uri;
final String? prefix;
final String _elementName;

_ImportAddHide(
this._elementName, this.uri, this.prefix, this.importDirectives,
{required super.context});

@override
CorrectionApplicability get applicability =>
// TODO(applicability): comment on why.
CorrectionApplicability.singleLocation;

@override
List<String> get fixArguments {
var prefix = '';
if (!this.prefix.isEmptyOrNull) {
prefix = ' as ${this.prefix}';
}
return [_elementName, uri, prefix];
}

@override
FixKind get fixKind => DartFixKind.IMPORT_LIBRARY_HIDE;

@override
Future<void> compute(ChangeBuilder builder) async {
if (_elementName.isEmpty || uri.isEmpty) {
return;
}

var hideCombinators =
<({ImportDirective directive, HideCombinator? hide})>[];

for (var directive in importDirectives) {
var show = directive.combinators.whereType<ShowCombinator>().firstOrNull;
// If there is an import with a show combinator, then we don't want to
// deal with this case here.
if (show != null) {
return;
}
var hide = directive.combinators.whereType<HideCombinator>().firstOrNull;
hideCombinators.add((directive: directive, hide: hide));
}

await builder.addDartFileEdit(file, (builder) {
for (var (:directive, :hide) in hideCombinators) {
if (hide != null) {
var allNames = [
_elementName,
...hide.hiddenNames.map((name) => name.name),
]..sort();
var combinator = 'hide ${allNames.join(', ')}';
var range = SourceRange(hide.offset, hide.length);
builder.addSimpleReplacement(range, combinator);
} else {
var hideCombinator = ' hide $_elementName';
builder.addSimpleInsertion(directive.end - 1, hideCombinator);
}
}
});
}
}

class _ImportRemoveShow extends ResolvedCorrectionProducer {
final Set<ImportDirective> importDirectives;
final String _elementName;
final String uri;
final String? prefix;

_ImportRemoveShow(
this._elementName, this.uri, this.prefix, this.importDirectives,
{required super.context});

@override
CorrectionApplicability get applicability =>
// TODO(applicability): comment on why.
CorrectionApplicability.singleLocation;

@override
List<String> get fixArguments {
var prefix = '';
if (!this.prefix.isEmptyOrNull) {
prefix = ' as ${this.prefix}';
}
return [_elementName, uri, prefix];
}

@override
FixKind get fixKind => DartFixKind.IMPORT_LIBRARY_REMOVE_SHOW;

@override
Future<void> compute(ChangeBuilder builder) async {
if (_elementName.isEmpty || uri.isEmpty) {
return;
}

var showCombinators =
<({ImportDirective directive, ShowCombinator show})>[];
for (var directive in importDirectives) {
var show = directive.combinators.whereType<ShowCombinator>().firstOrNull;
// If there is no show combinator, then we don't want to deal with this
// case here.
if (show == null) {
return;
}
showCombinators.add((directive: directive, show: show));
}

await builder.addDartFileEdit(file, (builder) {
for (var (:directive, :show) in showCombinators) {
var allNames = [
...show.shownNames
.map((name) => name.name)
.where((name) => name != _elementName),
]..sort();
if (allNames.isEmpty) {
builder.addDeletion(SourceRange(show.offset - 1, show.length + 1));
var hideCombinator = ' hide $_elementName';
builder.addSimpleInsertion(directive.end - 1, hideCombinator);
} else {
var combinator = 'show ${allNames.join(', ')}';
var range = SourceRange(show.offset, show.length);
builder.addSimpleReplacement(range, combinator);
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,11 @@ CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE:
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO:
status: hasFix
CompileTimeErrorCode.AMBIGUOUS_IMPORT:
status: needsFix
status: hasFix
notes: |-
1. For each imported name, add a fix to hide the name.
2. For each imported name, add a fix to add a prefix. We wouldn't be able to
add the prefix everywhere, but could add it wherever the name was already
unambiguous.
For each imported name, add a fix to add a prefix. We wouldn't be able to
add the prefix everywhere, but could add it wherever the name was already
unambiguous.
CompileTimeErrorCode.AMBIGUOUS_SET_OR_MAP_LITERAL_BOTH:
status: noFix
notes: |-
Expand Down
10 changes: 10 additions & 0 deletions pkg/analysis_server/lib/src/services/correction/fix.dart
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,11 @@ abstract final class DartFixKind {
DartFixKindPriority.standard + 5,
"Update library '{0}' import",
);
static const IMPORT_LIBRARY_HIDE = FixKind(
'dart.fix.import.libraryHide',
DartFixKindPriority.standard,
"Hide others to use '{0}' from '{1}'{2}",
);
static const IMPORT_LIBRARY_PREFIX = FixKind(
'dart.fix.import.libraryPrefix',
DartFixKindPriority.standard + 5,
Expand Down Expand Up @@ -864,6 +869,11 @@ abstract final class DartFixKind {
DartFixKindPriority.standard + 1,
"Import library '{0}' with prefix '{1}'",
);
static const IMPORT_LIBRARY_REMOVE_SHOW = FixKind(
'dart.fix.import.libraryRemoveShow',
DartFixKindPriority.standard - 1,
"Remove show to use '{0}' from '{1}'{2}",
);
static const IMPORT_LIBRARY_SDK = FixKind(
'dart.fix.import.librarySdk',
DartFixKindPriority.standard + 4,
Expand Down
15 changes: 5 additions & 10 deletions pkg/analysis_server/lib/src/services/correction/fix_internal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ import 'package:analysis_server/src/services/correction/dart/extend_class_for_mi
import 'package:analysis_server/src/services/correction/dart/extract_local_variable.dart';
import 'package:analysis_server/src/services/correction/dart/flutter_remove_widget.dart';
import 'package:analysis_server/src/services/correction/dart/ignore_diagnostic.dart';
import 'package:analysis_server/src/services/correction/dart/import_add_hide.dart';
import 'package:analysis_server/src/services/correction/dart/import_library.dart';
import 'package:analysis_server/src/services/correction/dart/inline_invocation.dart';
import 'package:analysis_server/src/services/correction/dart/inline_typedef.dart';
import 'package:analysis_server/src/services/correction/dart/insert_body.dart';
import 'package:analysis_server/src/services/correction/dart/insert_on_keyword.dart';
import 'package:analysis_server/src/services/correction/dart/insert_semicolon.dart';
import 'package:analysis_server/src/services/correction/dart/make_class_abstract.dart';
import 'package:analysis_server/src/services/correction/dart/make_conditional_on_debug_mode.dart';
Expand Down Expand Up @@ -378,7 +378,6 @@ final _builtInLintProducers = <LintCode, List<ProducerGenerator>>{
LinterLintCode.null_closures: [ReplaceNullWithClosure.new],
LinterLintCode.omit_local_variable_types: [ReplaceWithVar.new],
LinterLintCode.omit_obvious_local_variable_types: [ReplaceWithVar.new],
LinterLintCode.omit_obvious_property_types: [ReplaceWithVar.new],
LinterLintCode.prefer_adjacent_string_concatenation: [RemoveOperator.new],
LinterLintCode.prefer_collection_literals: [
ConvertToMapLiteral.new,
Expand Down Expand Up @@ -461,9 +460,6 @@ final _builtInLintProducers = <LintCode, List<ProducerGenerator>>{
LinterLintCode.specify_nonobvious_local_variable_types: [
AddTypeAnnotation.bulkFixable,
],
LinterLintCode.specify_nonobvious_property_types: [
AddTypeAnnotation.bulkFixable,
],
LinterLintCode.type_annotate_public_apis: [AddTypeAnnotation.bulkFixable],
LinterLintCode.type_init_formals: [RemoveTypeAnnotation.other],
LinterLintCode.type_literal_in_constant_pattern: [
Expand Down Expand Up @@ -536,6 +532,9 @@ final _builtInNonLintMultiProducers = {
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE: [
AddExtensionOverride.new,
],
CompileTimeErrorCode.AMBIGUOUS_IMPORT: [
AmbiguousImportFix.new,
],
CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE: [DataDriven.new],
CompileTimeErrorCode.CAST_TO_NON_TYPE: [
DataDriven.new,
Expand Down Expand Up @@ -1139,11 +1138,7 @@ final _builtInNonLintProducers = <ErrorCode, List<ProducerGenerator>>{
ParserErrorCode.EXPECTED_SWITCH_EXPRESSION_BODY: [InsertBody.new],
ParserErrorCode.EXPECTED_SWITCH_STATEMENT_BODY: [InsertBody.new],
ParserErrorCode.EXPECTED_TRY_STATEMENT_BODY: [InsertBody.new],
ParserErrorCode.EXPECTED_TOKEN: [
InsertSemicolon.new,
ReplaceWithArrow.new,
InsertOnKeyword.new,
],
ParserErrorCode.EXPECTED_TOKEN: [InsertSemicolon.new, ReplaceWithArrow.new],
ParserErrorCode.EXTENSION_AUGMENTATION_HAS_ON_CLAUSE: [RemoveOnClause.new],
ParserErrorCode.EXTENSION_DECLARES_CONSTRUCTOR: [RemoveConstructor.new],
ParserErrorCode.EXTERNAL_CLASS: [RemoveLexeme.modifier],
Expand Down
Loading