Skip to content

Commit dff0007

Browse files
DanTupCommit Queue
authored and
Commit Queue
committed
[analysis_server] Copy the original imports from source when moving top-levels to new files
Previously we would just import the library containing the declaration (which for other packages could be in `src/`). See #30310 (comment). Change-Id: I1f2c8f480b60240bd7f88e037fd768e157f96f17 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301400 Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent f0c14f5 commit dff0007

File tree

3 files changed

+118
-28
lines changed

3 files changed

+118
-28
lines changed

pkg/analysis_server/lib/src/services/refactoring/move_top_level_to_file.dart

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,17 @@ class MoveTopLevelToFile extends RefactoringProducer {
182182
void _addImportsForMovingDeclarations(
183183
DartFileEditBuilder builder, ImportAnalyzer analyzer) {
184184
for (var entry in analyzer.movingReferences.entries) {
185-
var library = entry.key.library;
186-
if (library != null && !library.isDartCore) {
187-
var uri = library.source.uri;
188-
for (var prefix in entry.value) {
189-
builder.importLibrary(uri, prefix: prefix.isEmpty ? null : prefix);
185+
var imports = entry.value;
186+
for (var import in imports) {
187+
var library = import.importedLibrary;
188+
if (library == null || library.isDartCore) {
189+
continue;
190190
}
191+
// TODO(dantup): This does not support show/hide. We should be able to
192+
// pass them in (and have them merge with any existing imports or
193+
// pending imports).
194+
builder.importLibrary(library.source.uri,
195+
prefix: import.prefix?.element.name);
191196
}
192197
}
193198
}

pkg/analysis_server/lib/src/utilities/import_analyzer.dart

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,20 @@ class ImportAnalyzer {
2525
final Set<Element> stayingDeclarations = {};
2626

2727
/// A map from the elements referenced by the declarations to be moved to the
28-
/// set of prefixes used to reference those declarations.
29-
final Map<Element, Set<String>> movingReferences = {};
28+
/// set of imports used to reference those declarations.
29+
final Map<Element, Set<LibraryImportElement>> movingReferences = {};
3030

3131
/// A map from the elements referenced by the declarations that are staying to
32-
/// the set of prefixes used to reference those declarations.
33-
final Map<Element, Set<String>> stayingReferences = {};
32+
/// the set of imports used to reference those declarations.
33+
final Map<Element, Set<LibraryImportElement>> stayingReferences = {};
3434

3535
/// Analyze the given library [result] to find the declarations and references
3636
/// being moved and that are staying. The declarations being moved are in the
3737
/// file at the given [path] in the given [range].
3838
ImportAnalyzer(this.result, String path, List<SourceRange> ranges) {
3939
for (var unit in result.units) {
4040
var finder = _ReferenceFinder(
41-
_ElementRecorder(this, path == unit.path ? ranges : []));
41+
unit, _ElementRecorder(this, path == unit.path ? ranges : []));
4242
unit.unit.accept(finder);
4343
}
4444
// Remove references that will be within the same file.
@@ -102,23 +102,27 @@ class _ElementRecorder {
102102
}
103103
}
104104

105-
/// Record that the [element] is referenced in the library at the
106-
/// [referenceOffset]. [prefix] is the prefixused to reference the element, or
107-
/// `null` if no prefix was used.
108-
void recordReference(
109-
Element referencedElement, int referenceOffset, PrefixElement? prefix) {
105+
/// Record that [referencedElement] is referenced in the library at the
106+
/// [referenceOffset]. [import] is the specific import used to reference the
107+
/// including any prefix, show, hide.
108+
void recordReference(Element referencedElement, int referenceOffset,
109+
LibraryImportElement? import) {
110110
if (referencedElement is PropertyAccessorElement &&
111111
referencedElement.isSynthetic) {
112112
referencedElement = referencedElement.variable;
113113
}
114114
if (_isBeingMoved(referenceOffset)) {
115-
var prefixes =
115+
var imports =
116116
analyzer.movingReferences.putIfAbsent(referencedElement, () => {});
117-
prefixes.add(prefix?.name ?? '');
117+
if (import != null) {
118+
imports.add(import);
119+
}
118120
} else {
119-
var prefixes =
121+
var imports =
120122
analyzer.stayingReferences.putIfAbsent(referencedElement, () => {});
121-
prefixes.add(prefix?.name ?? '');
123+
if (import != null) {
124+
imports.add(import);
125+
}
122126
}
123127
}
124128

@@ -139,8 +143,24 @@ class _ReferenceFinder extends RecursiveAstVisitor<void> {
139143
/// sent.
140144
final _ElementRecorder recorder;
141145

146+
/// The unit being searched for references.
147+
final ResolvedUnitResult unit;
148+
149+
/// A mapping of prefixes to the imports with those prefixes. An
150+
/// empty string is used for unprefixed imports.
151+
///
152+
/// Library imports are ordered the same as they appear in the source file
153+
/// (since this is a [LinkedHashSet]).
154+
final _importsByPrefix = <String, Set<LibraryImportElement>>{};
155+
142156
/// Initialize a newly created finder to send information to the [recorder].
143-
_ReferenceFinder(this.recorder);
157+
_ReferenceFinder(this.unit, this.recorder) {
158+
for (var import in unit.libraryElement.libraryImports) {
159+
_importsByPrefix
160+
.putIfAbsent(import.prefix?.element.name ?? '', () => {})
161+
.add(import);
162+
}
163+
}
144164

145165
@override
146166
void visitAssignmentExpression(AssignmentExpression node) {
@@ -249,6 +269,43 @@ class _ReferenceFinder extends RecursiveAstVisitor<void> {
249269
super.visitTopLevelVariableDeclaration(node);
250270
}
251271

272+
/// Finds the [LibraryImportElement] that is used to import [element] for use
273+
/// in [node].
274+
LibraryImportElement? _getImportForElement(AstNode? node, Element element) {
275+
var prefix = _getPrefixFromExpression(node)?.name;
276+
var elementName = element.name;
277+
// We cannot locate imports for unnamed elements.
278+
if (elementName == null) {
279+
return null;
280+
}
281+
282+
var import = _importsByPrefix[prefix ?? '']?.where((import) {
283+
// Check if this import is providing our element with the correct
284+
// prefix/name.
285+
var exportedElement = prefix != null
286+
? import.namespace.getPrefixed(prefix, elementName)
287+
: import.namespace.get(elementName);
288+
return exportedElement == element;
289+
}).firstOrNull;
290+
291+
// Extensions can be used without a prefix, so we can use any import that
292+
// brings in the extension.
293+
if (import == null && prefix == null && element is ExtensionElement) {
294+
import = _importsByPrefix.values
295+
.expand((imports) => imports)
296+
.where((import) =>
297+
// Because we don't know what prefix we're looking for (any is
298+
// allowed), use the imports own prefix when checking for the
299+
// element.
300+
import.namespace.getPrefixed(
301+
import.prefix?.element.name ?? '', elementName) ==
302+
element)
303+
.firstOrNull;
304+
}
305+
306+
return import;
307+
}
308+
252309
/// Return the prefix used in [node].
253310
PrefixElement? _getPrefixFromExpression(AstNode? node) {
254311
if (node is PrefixedIdentifier) {
@@ -291,8 +348,9 @@ class _ReferenceFinder extends RecursiveAstVisitor<void> {
291348
if (!element.isInterestingReference) {
292349
return;
293350
}
294-
recorder.recordReference(
295-
element, node.offset, _getPrefixFromExpression(prefixNode));
351+
352+
var import = _getImportForElement(prefixNode, element);
353+
recorder.recordReference(element, node.offset, import);
296354
}
297355
}
298356

pkg/analysis_server/test/src/services/refactoring/move_top_level_to_file_test.dart

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,37 @@ class A {}
186186
expect(content[newFilePath], expectedNewFileContent);
187187
}
188188

189+
Future<void> test_imports_declarationInSrc() async {
190+
var libFilePath = join(projectFolderPath, 'lib', 'a.dart');
191+
var srcFilePath = join(projectFolderPath, 'lib', 'src', 'a.dart');
192+
addSource(libFilePath, 'export "src/a.dart";');
193+
addSource(srcFilePath, 'class A {}');
194+
var originalSource = '''
195+
import 'package:test/a.dart';
196+
197+
A? staying;
198+
A? mov^ing;
199+
''';
200+
var modifiedSource = '''
201+
import 'package:test/a.dart';
202+
203+
A? staying;
204+
''';
205+
var declarationName = 'moving';
206+
var newFileName = 'moving.dart';
207+
var newFileContent = '''
208+
import 'package:test/a.dart';
209+
210+
A? moving;
211+
''';
212+
await _singleDeclaration(
213+
originalSource: originalSource,
214+
modifiedSource: modifiedSource,
215+
declarationName: declarationName,
216+
newFileName: newFileName,
217+
newFileContent: newFileContent);
218+
}
219+
189220
Future<void> test_imports_extensionMethod() async {
190221
var otherFilePath = '$projectFolderPath/lib/extensions.dart';
191222
var otherFileContent = '''
@@ -433,10 +464,8 @@ class A {}
433464
''';
434465
var movingDeclarationName = 'moving';
435466
var newFileName = 'moving.dart';
436-
// The prefix is not included because it's not used (the only use of
437-
// extensions.dart is the extension method).
438467
var newFileContent = '''
439-
import 'package:test/extensions.dart';
468+
import 'package:test/extensions.dart' as other;
440469
import 'package:test/main.dart';
441470
442471
void moving() {
@@ -479,10 +508,8 @@ class A {}
479508
''';
480509
var movingDeclarationName = 'moving';
481510
var newFileName = 'moving.dart';
482-
// The prefix is not included because it's not used (the only use of
483-
// extensions.dart is the extension method).
484511
var newFileContent = '''
485-
import 'package:test/extensions.dart';
512+
import 'package:test/extensions.dart' as other;
486513
import 'package:test/main.dart';
487514
488515
void moving() {

0 commit comments

Comments
 (0)