diff --git a/pkg/analysis_server/lib/src/services/correction/dart/import_add_hide.dart b/pkg/analysis_server/lib/src/services/correction/dart/import_add_hide.dart new file mode 100644 index 000000000000..fa742020a66f --- /dev/null +++ b/pkg/analysis_server/lib/src/services/correction/dart/import_add_hide.dart @@ -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> 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 = {}; + 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, ResolvedUnitResult?, Set) _getImportDirectives( + ResolvedLibraryResult libraryResult, + ResolvedUnitResult? unitResult, + List conflictingElements, + String? prefix, + ) { + var uris = {}; + var importDirectives = {}; + // 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() + .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 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 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 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().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().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 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 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 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().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); + } + } + }); + } +} diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml index 269a45fb4f79..21081ad63c7a 100644 --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml @@ -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: |- diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart index 845b93b4a408..6d30d60989bf 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix.dart @@ -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, @@ -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, diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart index 07e97880e59a..f2ba868dc3b2 100644 --- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart +++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart @@ -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'; @@ -378,7 +378,6 @@ final _builtInLintProducers = >{ 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, @@ -461,9 +460,6 @@ final _builtInLintProducers = >{ 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: [ @@ -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, @@ -1139,11 +1138,7 @@ final _builtInNonLintProducers = >{ 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], diff --git a/pkg/analysis_server/test/src/services/correction/fix/import_add_hide_test.dart b/pkg/analysis_server/test/src/services/correction/fix/import_add_hide_test.dart new file mode 100644 index 000000000000..bebf15af984e --- /dev/null +++ b/pkg/analysis_server/test/src/services/correction/fix/import_add_hide_test.dart @@ -0,0 +1,689 @@ +// Copyright (c) 2018, 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:analyzer/src/error/codes.dart'; +import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'fix_processor.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(ImportAddHideTest); + defineReflectiveTests(ImportRemoveShowTest); + }); +} + +@reflectiveTest +class ImportAddHideTest extends FixProcessorTest { + @override + FixKind get kind => DartFixKind.IMPORT_LIBRARY_HIDE; + + Future test_double() async { + newFile('$testPackageLibPath/lib1.dart', ''' +class N {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +class N {}'''); + await resolveTestCode(''' +import 'lib1.dart'; +import 'lib2.dart'; + +void f(N? n) { + print(n); +} +'''); + await assertHasFixesWithoutApplying( + expectedNumberOfFixesForKind: 2, + matchFixMessages: [ + "Hide others to use 'N' from 'lib1.dart'", + "Hide others to use 'N' from 'lib2.dart'", + ]); + await assertHasFix(''' +import 'lib1.dart' hide N; +import 'lib2.dart'; + +void f(N? n) { + print(n); +} +''', matchFixMessage: "Hide others to use 'N' from 'lib2.dart'"); + await assertHasFix(''' +import 'lib1.dart'; +import 'lib2.dart' hide N; + +void f(N? n) { + print(n); +} +''', matchFixMessage: "Hide others to use 'N' from 'lib1.dart'"); + } + + Future test_double_aliased() async { + newFile('$testPackageLibPath/lib1.dart', ''' +class N {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +class N {}'''); + await resolveTestCode(''' +import 'lib1.dart' as i; +import 'lib2.dart' as i; + +void f(i.N? n) { + print(n); +} +'''); + await assertHasFixesWithoutApplying( + expectedNumberOfFixesForKind: 2, + matchFixMessages: [ + "Hide others to use 'N' from 'lib1.dart' as i", + "Hide others to use 'N' from 'lib2.dart' as i", + ]); + await assertHasFix(''' +import 'lib1.dart' as i; +import 'lib2.dart' as i hide N; + +void f(i.N? n) { + print(n); +} +''', matchFixMessage: "Hide others to use 'N' from 'lib1.dart' as i"); + await assertHasFix(''' +import 'lib1.dart' as i hide N; +import 'lib2.dart' as i; + +void f(i.N? n) { + print(n); +} +''', matchFixMessage: "Hide others to use 'N' from 'lib2.dart' as i"); + } + + Future test_double_constant() async { + newFile('$testPackageLibPath/lib1.dart', ''' +const foo = 0;'''); + newFile('$testPackageLibPath/lib2.dart', ''' +const foo = 0;'''); + await resolveTestCode(''' +import 'lib1.dart'; +import 'lib2.dart'; + +void f() { + print(foo); +} +'''); + await assertHasFix(''' +import 'lib1.dart'; +import 'lib2.dart' hide foo; + +void f() { + print(foo); +} +''', matchFixMessage: "Hide others to use 'foo' from 'lib1.dart'"); + } + + Future test_double_exportedByImport() async { + newFile('$testPackageLibPath/lib1.dart', ''' +export 'lib3.dart';'''); + newFile('$testPackageLibPath/lib2.dart', ''' +mixin M {}'''); + newFile('$testPackageLibPath/lib3.dart', ''' +mixin M {}'''); + await resolveTestCode(''' +import 'lib1.dart'; +import 'lib2.dart'; + +class C with M {} +'''); + await assertHasFix(''' +import 'lib1.dart' hide M; +import 'lib2.dart'; + +class C with M {} +''', matchFixMessage: "Hide others to use 'M' from 'lib2.dart'", + errorFilter: (error) { + return error.errorCode == CompileTimeErrorCode.AMBIGUOUS_IMPORT; + }); + } + + Future test_double_extension() async { + newFile('$testPackageLibPath/lib1.dart', ''' +extension E on int { + bool get isDivisibleByThree => this % 3 == 0; +}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +extension E on int { + bool get isDivisibleByThree => this % 3 == 0; +}'''); + await resolveTestCode(''' +import 'lib1.dart'; +import 'lib2.dart'; + +void foo(int i) { + print(E(i.isDivisibleByThree)); +} +'''); + await assertHasFix(''' +import 'lib1.dart'; +import 'lib2.dart' hide E; + +void foo(int i) { + print(E(i.isDivisibleByThree)); +} +''', matchFixMessage: "Hide others to use 'E' from 'lib1.dart'", + errorFilter: (error) { + return error.errorCode == CompileTimeErrorCode.AMBIGUOUS_IMPORT; + }); + } + + Future test_double_function() async { + newFile('$testPackageLibPath/lib1.dart', ''' +void bar() {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +void bar() {}'''); + await resolveTestCode(''' +import 'lib1.dart'; +import 'lib2.dart'; + +void foo() { + bar(); +} +'''); + await assertHasFix(''' +import 'lib1.dart'; +import 'lib2.dart' hide bar; + +void foo() { + bar(); +} +''', matchFixMessage: "Hide others to use 'bar' from 'lib1.dart'"); + } + + Future test_double_mixin() async { + newFile('$testPackageLibPath/lib1.dart', ''' +mixin M {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +mixin M {}'''); + await resolveTestCode(''' +import 'lib1.dart'; +import 'lib2.dart'; + +class C with M {} +'''); + await assertHasFix(''' +import 'lib1.dart'; +import 'lib2.dart' hide M; + +class C with M {} +''', matchFixMessage: "Hide others to use 'M' from 'lib1.dart'", + errorFilter: (error) { + return error.errorCode == CompileTimeErrorCode.AMBIGUOUS_IMPORT; + }); + } + + Future test_double_oneHide() async { + newFile('$testPackageLibPath/lib1.dart', ''' +class M {} class N {} class O {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +class N {}'''); + await resolveTestCode(''' +import 'lib1.dart' hide M, O; +import 'lib2.dart'; + +void f(N? n) { + print(n); +} +'''); + await assertHasFix(''' +import 'lib1.dart' hide M, N, O; +import 'lib2.dart'; + +void f(N? n) { + print(n); +} +''', matchFixMessage: "Hide others to use 'N' from 'lib2.dart'"); + } + + Future test_double_oneShow() async { + newFile('$testPackageLibPath/lib1.dart', ''' +class N {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +class N {}'''); + await resolveTestCode(''' +import 'lib1.dart' show N; +import 'lib2.dart'; + +void f(N? n) { + print(n); +} +'''); + await assertHasFix(''' +import 'lib1.dart' show N; +import 'lib2.dart' hide N; + +void f(N? n) { + print(n); +} +''', matchFixMessage: "Hide others to use 'N' from 'lib1.dart'"); + } + + Future test_double_variable() async { + newFile('$testPackageLibPath/lib1.dart', ''' +var foo = 0;'''); + newFile('$testPackageLibPath/lib2.dart', ''' +var foo = 0;'''); + await resolveTestCode(''' +import 'lib1.dart' show foo; +import 'lib2.dart'; + +void f() { + print(foo); +} +'''); + await assertHasFix(''' +import 'lib1.dart' show foo; +import 'lib2.dart' hide foo; + +void f() { + print(foo); +} +''', matchFixMessage: "Hide others to use 'foo' from 'lib1.dart'"); + } + + Future test_multiLevelParts() async { + // Create a tree of files that all import 'dart:math' and ensure we find + // only the import from the parent (not a grandparent, sibling, or child). + // + // - lib1 declares A + // - lib2 declares A + // + // - root has import + // - level1_other has import + // - level1 has imports, is the used reference + // - level2_other has import + // - test has reference <-- testing this + // - level3_other has import + + newFile('$testPackageLibPath/lib1.dart', ''' +class A {} +'''); + + newFile('$testPackageLibPath/lib2.dart', ''' +class A {} +'''); + + newFile('$testPackageLibPath/root.dart', ''' +import 'lib1.dart'; +part 'level1_other.dart'; +part 'level1.dart'; +'''); + + newFile('$testPackageLibPath/level1_other.dart', ''' +part of 'root.dart'; +import 'lib1.dart'; +'''); + + newFile('$testPackageLibPath/level1.dart', ''' +part of 'root.dart'; +import 'lib1.dart'; +import 'lib2.dart'; +part 'level2_other.dart'; +part 'test.dart'; +'''); + + newFile('$testPackageLibPath/level2_other.dart', ''' +part of 'level1.dart'; +import 'lib1.dart'; +'''); + + newFile('$testPackageLibPath/level3_other.dart', ''' +part of 'test.dart'; +import 'lib1.dart'; +'''); + + await resolveTestCode(''' +part of 'level1.dart'; +part 'level3_other.dart'; + +A? a; +'''); + + await assertHasFix( + ''' +part of 'root.dart'; +import 'lib1.dart' hide A; +import 'lib2.dart'; +part 'level2_other.dart'; +part 'test.dart'; +''', + target: '$testPackageLibPath/level1.dart', + matchFixMessage: "Hide others to use 'A' from 'lib2.dart'", + ); + } + + Future test_part() async { + newFile('$testPackageLibPath/lib1.dart', ''' +class N {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +class N {}'''); + newFile('$testPackageLibPath/other.dart', ''' +import 'lib1.dart'; +import 'lib2.dart'; +part 'test.dart'; +'''); + await resolveTestCode(''' +part of 'other.dart'; +import 'lib1.dart'; +import 'lib2.dart'; + +void f(N? n) { + print(n); +} +'''); + await assertHasFixesWithoutApplying( + expectedNumberOfFixesForKind: 2, + matchFixMessages: [ + "Hide others to use 'N' from 'lib1.dart'", + "Hide others to use 'N' from 'lib2.dart'", + ], + ); + await assertHasFix( + ''' +part of 'other.dart'; +import 'lib1.dart' hide N; +import 'lib2.dart'; + +void f(N? n) { + print(n); +} +''', + matchFixMessage: "Hide others to use 'N' from 'lib2.dart'", + ); + await assertHasFix( + ''' +part of 'other.dart'; +import 'lib1.dart'; +import 'lib2.dart' hide N; + +void f(N? n) { + print(n); +} +''', + matchFixMessage: "Hide others to use 'N' from 'lib1.dart'", + ); + } + + Future test_show_prefixed() async { + newFile('$testPackageLibPath/lib1.dart', ''' +class N {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +class N {}'''); + await resolveTestCode(''' +import 'lib1.dart' as l show N; +import 'lib2.dart' as l; + +void f(l.N? n) { + print(n); +} +'''); + await assertHasFix(''' +import 'lib1.dart' as l show N; +import 'lib2.dart' as l hide N; + +void f(l.N? n) { + print(n); +} +''', matchFixMessage: "Hide others to use 'N' from 'lib1.dart' as l"); + } + + Future test_triple() async { + newFile('$testPackageLibPath/lib1.dart', ''' +export 'lib3.dart';'''); + newFile('$testPackageLibPath/lib2.dart', ''' +mixin M {}'''); + newFile('$testPackageLibPath/lib3.dart', ''' +mixin M {}'''); + await resolveTestCode(''' +import 'lib1.dart'; +import 'lib2.dart'; +import 'lib3.dart'; + +class C with M {} +'''); + await assertHasFix(''' +import 'lib1.dart'; +import 'lib2.dart' hide M; +import 'lib3.dart' hide M; + +class C with M {} +''', matchFixMessage: "Hide others to use 'M' from 'lib1.dart'", + errorFilter: (error) { + return error.errorCode == CompileTimeErrorCode.AMBIGUOUS_IMPORT; + }); + await assertHasFix(''' +import 'lib1.dart' hide M; +import 'lib2.dart'; +import 'lib3.dart' hide M; + +class C with M {} +''', matchFixMessage: "Hide others to use 'M' from 'lib2.dart'", + errorFilter: (error) { + return error.errorCode == CompileTimeErrorCode.AMBIGUOUS_IMPORT; + }); + await assertHasFix(''' +import 'lib1.dart' hide M; +import 'lib2.dart' hide M; +import 'lib3.dart'; + +class C with M {} +''', matchFixMessage: "Hide others to use 'M' from 'lib3.dart'", + errorFilter: (error) { + return error.errorCode == CompileTimeErrorCode.AMBIGUOUS_IMPORT; + }); + } + + Future test_triple_oneAliased() async { + newFile('$testPackageLibPath/lib1.dart', ''' +const foo = 0;'''); + newFile('$testPackageLibPath/lib2.dart', ''' +const foo = 0;'''); + await resolveTestCode(''' +import 'lib1.dart' as lib; +import 'lib1.dart'; +import 'lib2.dart'; + +void f() { + print(foo); +} +'''); + await assertHasFix(''' +import 'lib1.dart' as lib; +import 'lib1.dart'; +import 'lib2.dart' hide foo; + +void f() { + print(foo); +} +''', matchFixMessage: "Hide others to use 'foo' from 'lib1.dart'"); + } + + Future test_triple_twoAliased() async { + newFile('$testPackageLibPath/lib1.dart', ''' +const foo = 0;'''); + newFile('$testPackageLibPath/lib2.dart', ''' +const foo = 0;'''); + await resolveTestCode(''' +import 'lib1.dart'; +import 'lib1.dart' as lib; +import 'lib2.dart' as lib; + +void f() { + print(lib.foo); +} +'''); + await assertHasFix(''' +import 'lib1.dart'; +import 'lib1.dart' as lib; +import 'lib2.dart' as lib hide foo; + +void f() { + print(lib.foo); +} +''', matchFixMessage: "Hide others to use 'foo' from 'lib1.dart' as lib"); + } +} + +@reflectiveTest +class ImportRemoveShowTest extends FixProcessorTest { + @override + FixKind get kind => DartFixKind.IMPORT_LIBRARY_REMOVE_SHOW; + + Future test_double() async { + newFile('$testPackageLibPath/lib1.dart', ''' +class N {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +class N {}'''); + await resolveTestCode(''' +import 'lib1.dart' show N; +import 'lib2.dart' show N; + +void f(N? n) { + print(n); +} +'''); + await assertHasFix(''' +import 'lib1.dart' hide N; +import 'lib2.dart' show N; + +void f(N? n) { + print(n); +} +''', matchFixMessage: "Remove show to use 'N' from 'lib2.dart'"); + } + + Future test_double_aliased() async { + newFile('$testPackageLibPath/lib1.dart', ''' +class N {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +class N {}'''); + await resolveTestCode(''' +import 'lib1.dart' as l show N; +import 'lib2.dart' as l show N; + +void f(l.N? n) { + print(n); +} +'''); + await assertHasFixesWithoutApplying( + expectedNumberOfFixesForKind: 2, + matchFixMessages: [ + "Remove show to use 'N' from 'lib1.dart' as l", + "Remove show to use 'N' from 'lib2.dart' as l", + ]); + await assertHasFix(''' +import 'lib1.dart' as l hide N; +import 'lib2.dart' as l show N; + +void f(l.N? n) { + print(n); +} +''', matchFixMessage: "Remove show to use 'N' from 'lib2.dart' as l"); + await assertHasFix(''' +import 'lib1.dart' as l show N; +import 'lib2.dart' as l hide N; + +void f(l.N? n) { + print(n); +} +''', matchFixMessage: "Remove show to use 'N' from 'lib1.dart' as l"); + } + + Future test_double_oneHide() async { + newFile('$testPackageLibPath/lib1.dart', ''' +const foo = 0;'''); + newFile('$testPackageLibPath/lib2.dart', ''' +const foo = 0;'''); + await resolveTestCode(''' +import 'lib1.dart' hide foo; +import 'lib2.dart' show foo; + +void f() { + print(foo); +} +'''); + await assertNoFix(); + } + + Future test_moreShow() async { + newFile('$testPackageLibPath/lib1.dart', ''' +class N {} +class M {}'''); + newFile('$testPackageLibPath/lib2.dart', ''' +class N {}'''); + await resolveTestCode(''' +import 'lib1.dart' show N, M; +import 'lib2.dart'; + +void f(N? n) { + print(n); +} +'''); + await assertHasFixesWithoutApplying( + expectedNumberOfFixesForKind: 1, + matchFixMessages: [ + "Remove show to use 'N' from 'lib2.dart'", + ]); + await assertHasFix(''' +import 'lib1.dart' show M; +import 'lib2.dart'; + +void f(N? n) { + print(n); +} +''', matchFixMessage: "Remove show to use 'N' from 'lib2.dart'"); + } + + Future test_one_show() async { + newFile('$testPackageLibPath/lib1.dart', ''' +var foo = 0;'''); + newFile('$testPackageLibPath/lib2.dart', ''' +var foo = 0;'''); + await resolveTestCode(''' +import 'lib1.dart' show foo; +import 'lib2.dart'; + +void f() { + print(foo); +} +'''); + await assertHasFix(''' +import 'lib1.dart' hide foo; +import 'lib2.dart'; + +void f() { + print(foo); +} +''', matchFixMessage: "Remove show to use 'foo' from 'lib2.dart'"); + } + + Future test_triple_twoAliased() async { + newFile('$testPackageLibPath/lib1.dart', ''' +const foo = 0;'''); + newFile('$testPackageLibPath/lib2.dart', ''' +const foo = 0;'''); + await resolveTestCode(''' +import 'lib1.dart'; +import 'lib1.dart' as lib show foo; +import 'lib2.dart' as lib; + +void f() { + print(lib.foo); +} +'''); + await assertHasFix(''' +import 'lib1.dart'; +import 'lib1.dart' as lib hide foo; +import 'lib2.dart' as lib; + +void f() { + print(lib.foo); +} +''', matchFixMessage: "Remove show to use 'foo' from 'lib2.dart' as lib"); + } +} diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart index ac465354a9ba..6465d938bb88 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart @@ -132,6 +132,7 @@ import 'fix_processor_map_test.dart' as fix_processor_map; import 'fix_test.dart' as fix; import 'format_file_test.dart' as format_file; import 'ignore_diagnostic_test.dart' as ignore_error; +import 'import_add_hide_test.dart' as import_add_hide; import 'import_library_hide_test.dart' as import_library_hide; import 'import_library_prefix_test.dart' as import_library_prefix; import 'import_library_project_test.dart' as import_library_project; @@ -416,6 +417,7 @@ void main() { fix_processor_map.main(); format_file.main(); ignore_error.main(); + import_add_hide.main(); import_library_hide.main(); import_library_prefix.main(); import_library_project.main();