Support for optional ImportPrefixGenerator to generate prefixes for new imports.
Only one import is generated for each library.
If there is already an import for a library, new prefix is not asked.
If a prefix was generated once, it is not possible to force another
import with a different prefix.
R=brianwilkerson@google.com
Change-Id: I1676a1953002060b5e7155ce90abf87b3bfe270e
Reviewed-on: https://dart-review.googlesource.com/55326
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
index 06e9aaa..d90184f 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
@@ -42,8 +42,14 @@
@override
Future<Null> addFileEdit(
- String path, void buildFileEdit(DartFileEditBuilder builder)) =>
- super.addFileEdit(path, (builder) => buildFileEdit(builder));
+ String path, void buildFileEdit(DartFileEditBuilder builder),
+ {ImportPrefixGenerator importPrefixGenerator}) {
+ return super.addFileEdit(path, (builder) {
+ DartFileEditBuilderImpl dartBuilder = builder;
+ dartBuilder.importPrefixGenerator = importPrefixGenerator;
+ buildFileEdit(dartBuilder);
+ });
+ }
@override
Future<DartFileEditBuilderImpl> createFileEditBuilder(String path) async {
@@ -87,13 +93,6 @@
*/
String getIndent(int level) => ' ' * level;
- /**
- * Arrange to have an import added for the given [library].
- */
- void importLibrary(Uri library) {
- dartFileEditBuilder.importLibrary(library);
- }
-
@override
void writeClassDeclaration(String name,
{Iterable<DartType> interfaces,
@@ -595,7 +594,7 @@
write('.');
}
} else {
- importLibrary(definingLibrary.source.uri);
+ _importLibrary(definingLibrary.source.uri);
}
}
@@ -612,8 +611,11 @@
_EnclosingElementFinder finder = new _EnclosingElementFinder();
finder.find(dartFileEditBuilder.unit, offset);
String typeSource = _getTypeSource(
- type, finder.enclosingClass, finder.enclosingExecutable,
- methodBeingCopied: methodBeingCopied);
+ type,
+ finder.enclosingClass,
+ finder.enclosingExecutable,
+ methodBeingCopied: methodBeingCopied,
+ );
if (typeSource.isNotEmpty && typeSource != 'dynamic') {
if (groupName != null) {
addLinkedEdit(groupName, (LinkedEditBuilder builder) {
@@ -1021,7 +1023,11 @@
buffer.write(".");
}
} else {
- importLibrary(definingLibrary.source.uri);
+ _LibraryToImport import = _importLibrary(definingLibrary.source.uri);
+ if (import.prefix != null) {
+ buffer.write(import.prefix);
+ buffer.write('.');
+ }
}
}
// append simple name
@@ -1168,6 +1174,13 @@
}
return type;
}
+
+ /**
+ * Arrange to have an import added for the library with the given [uri].
+ */
+ _LibraryToImport _importLibrary(Uri uri) {
+ return dartFileEditBuilder._importLibrary(uri);
+ }
}
/**
@@ -1181,10 +1194,15 @@
CompilationUnit unit;
/**
- * The set of absolute or relative URIs of the libraries that need to be
- * imported in order to make visible the names used in generated code.
+ * The optional generator of prefixes for new imports.
*/
- Set<String> librariesToImport = new Set<String>();
+ ImportPrefixGenerator importPrefixGenerator;
+
+ /**
+ * A mapping from libraries that need to be imported in order to make visible
+ * the names used in generated code, to information about these imports.
+ */
+ Map<Uri, _LibraryToImport> librariesToImport = {};
/**
* Initialize a newly created builder to build a source file edit within the
@@ -1230,23 +1248,20 @@
CompilationUnitElement definingUnitElement =
libraryElement.definingCompilationUnit;
if (definingUnitElement == unitElement) {
- _addLibraryImports(librariesToImport);
+ _addLibraryImports(librariesToImport.values);
} else {
await (changeBuilder as DartChangeBuilder).addFileEdit(
definingUnitElement.source.fullName, (DartFileEditBuilder builder) {
(builder as DartFileEditBuilderImpl)
- ._addLibraryImports(librariesToImport);
+ ._addLibraryImports(librariesToImport.values);
});
}
}
}
@override
- String importLibrary(Uri library) {
- LibraryElement targetLibrary = unit.element.library;
- String uriText = _getLibrarySourceUri(targetLibrary, library);
- librariesToImport.add(uriText);
- return uriText;
+ String importLibrary(Uri uri) {
+ return _importLibrary(uri).uriText;
}
@override
@@ -1276,9 +1291,10 @@
}
/**
- * Adds edits ensure that all the [libraries] are imported.
+ * Adds edits ensure that all the [imports] are imported into the given
+ * [targetLibrary].
*/
- void _addLibraryImports(Set<String> libraries) {
+ void _addLibraryImports(Iterable<_LibraryToImport> imports) {
// Prepare information about existing imports.
LibraryDirective libraryDirective;
List<ImportDirective> importDirectives = <ImportDirective>[];
@@ -1290,21 +1306,31 @@
}
}
- // Prepare all URIs to import.
- List<String> uriList = libraries.toList();
- uriList.sort((a, b) => a.compareTo(b));
+ // Sort imports by URIs.
+ List<_LibraryToImport> importList = imports.toList();
+ importList.sort((a, b) => a.uriText.compareTo(b.uriText));
+
+ void writeImport(EditBuilder builder, _LibraryToImport import) {
+ builder.write("import '");
+ builder.write(import.uriText);
+ builder.write("'");
+ if (import.prefix != null) {
+ builder.write(' as ');
+ builder.write(import.prefix);
+ }
+ builder.write(';');
+ }
// Insert imports: between existing imports.
if (importDirectives.isNotEmpty) {
- for (String importUri in uriList) {
- bool isDart = importUri.startsWith('dart:');
- bool isPackage = importUri.startsWith('package:');
+ for (var import in importList) {
+ bool isDart = import.uriText.startsWith('dart:');
+ bool isPackage = import.uriText.startsWith('package:');
bool inserted = false;
void insert(
{ImportDirective prev,
ImportDirective next,
- String uri,
bool trailingNewLine: false}) {
LineInfo lineInfo = unit.lineInfo;
if (prev != null) {
@@ -1319,16 +1345,13 @@
}
addInsertion(offset, (EditBuilder builder) {
builder.writeln();
- builder.write("import '");
- builder.write(uri);
- builder.write("';");
+ writeImport(builder, import);
});
} else {
int offset = next.offset;
addInsertion(offset, (EditBuilder builder) {
- builder.write("import '");
- builder.write(uri);
- builder.writeln("';");
+ writeImport(builder, import);
+ builder.writeln();
if (trailingNewLine) {
builder.writeln();
}
@@ -1349,14 +1372,13 @@
bool isExistingPackage = existingUri.startsWith('package:');
bool isExistingRelative = !existingUri.contains(':');
- bool isNewBeforeExisting = importUri.compareTo(existingUri) < 0;
+ bool isNewBeforeExisting = import.uriText.compareTo(existingUri) < 0;
if (isDart) {
if (!isExistingDart || isNewBeforeExisting) {
insert(
prev: lastExistingDart,
next: existingImport,
- uri: importUri,
trailingNewLine: !isExistingDart);
break;
}
@@ -1365,13 +1387,12 @@
insert(
prev: lastExistingPackage,
next: existingImport,
- uri: importUri,
trailingNewLine: isExistingRelative);
break;
}
} else {
if (!isExistingDart && !isExistingPackage && isNewBeforeExisting) {
- insert(next: existingImport, uri: importUri);
+ insert(next: existingImport);
break;
}
}
@@ -1397,9 +1418,7 @@
}
}
builder.writeln();
- builder.write("import '");
- builder.write(importUri);
- builder.write("';");
+ writeImport(builder, import);
});
}
}
@@ -1408,18 +1427,17 @@
// Insert imports: after the library directive.
if (libraryDirective != null) {
- for (int i = 0; i < uriList.length; i++) {
- String importUri = uriList[i];
- addInsertion(libraryDirective.end, (EditBuilder builder) {
+ addInsertion(libraryDirective.end, (EditBuilder builder) {
+ for (int i = 0; i < importList.length; i++) {
+ var import = importList[i];
if (i == 0) {
builder.writeln();
}
builder.writeln();
- builder.write("import '");
- builder.write(importUri);
- builder.writeln("';");
- });
- }
+ writeImport(builder, import);
+ builder.writeln();
+ }
+ });
return;
}
@@ -1432,17 +1450,16 @@
} else {
offset = unit.end;
}
- for (int i = 0; i < uriList.length; i++) {
- String importUri = uriList[i];
- addInsertion(offset, (EditBuilder builder) {
- builder.write("import '");
- builder.write(importUri);
- builder.writeln("';");
- if (i == uriList.length - 1 && insertEmptyLineAfter) {
+ addInsertion(offset, (EditBuilder builder) {
+ for (int i = 0; i < importList.length; i++) {
+ var import = importList[i];
+ writeImport(builder, import);
+ builder.writeln();
+ if (i == importList.length - 1 && insertEmptyLineAfter) {
builder.writeln();
}
- });
- }
+ }
+ });
}
/**
@@ -1460,6 +1477,22 @@
}
/**
+ * Arrange to have an import added for the library with the given [uri].
+ */
+ _LibraryToImport _importLibrary(Uri uri) {
+ var import = librariesToImport[uri];
+ if (import == null) {
+ LibraryElement targetLibrary = unit.element.library;
+ String uriText = _getLibrarySourceUri(targetLibrary, uri);
+ String prefix =
+ importPrefixGenerator != null ? importPrefixGenerator(uri) : null;
+ import = new _LibraryToImport(uriText, prefix);
+ librariesToImport[uri] = import;
+ }
+ return import;
+ }
+
+ /**
* Create an edit to replace the return type of the innermost function
* containing the given [node] with the type `Future`. The [typeProvider] is
* used to check the current return type, because if it is already `Future` no
@@ -1622,3 +1655,23 @@
return null;
}
}
+
+/**
+ * Information about a new library to import.
+ */
+class _LibraryToImport {
+ final String uriText;
+ final String prefix;
+
+ _LibraryToImport(this.uriText, this.prefix);
+
+ @override
+ int get hashCode => uriText.hashCode;
+
+ @override
+ bool operator ==(other) {
+ return other is _LibraryToImport &&
+ other.uriText == uriText &&
+ other.prefix == prefix;
+ }
+}
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
index 3f24874..4cd16ae 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
@@ -14,6 +14,11 @@
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
/**
+ * The optional generator for prefix that should be used for new imports.
+ */
+typedef ImportPrefixGenerator = String Function(Uri);
+
+/**
* A [ChangeBuilder] used to build changes in Dart files.
*
* Clients may not extend, implement or mix-in this class.
@@ -24,9 +29,18 @@
*/
factory DartChangeBuilder(AnalysisSession session) = DartChangeBuilderImpl;
+ /**
+ * Use the [buildFileEdit] function to create a collection of edits to the
+ * file with the given [path]. The edits will be added to the source change
+ * that is being built.
+ *
+ * If [importPrefixGenerator] is provided, it will be asked to generate an
+ * import prefix for every newly imported library.
+ */
@override
Future<Null> addFileEdit(
- String path, void buildFileEdit(DartFileEditBuilder builder));
+ String path, void buildFileEdit(DartFileEditBuilder builder),
+ {ImportPrefixGenerator importPrefixGenerator});
}
/**
@@ -305,12 +319,12 @@
FunctionBody body, TypeProvider typeProvider);
/**
- * Arrange to have an import added for the given [library].
+ * Arrange to have an import added for the library with the given [uri].
*
* Returns the text of the URI that will be used in the import directive.
* It can be different than the given [Uri].
*/
- String importLibrary(Uri library);
+ String importLibrary(Uri uri);
/**
* Optionally create an edit to replace the given [typeAnnotation] with the
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
index d0dfbc4..82999a6 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
@@ -1821,6 +1821,56 @@
expect(edit.replacement, equalsIgnoringWhitespace(''));
}
+ test_writeType_prefixGenerator() async {
+ String aPath = provider.convertPath('/a.dart');
+ String bPath = provider.convertPath('/b.dart');
+
+ addSource(aPath, r'''
+class A1 {}
+class A2 {}
+''');
+ addSource(bPath, r'''
+class B {}
+''');
+
+ String path = provider.convertPath('/test.dart');
+ String content = '';
+ addSource(path, content);
+
+ ClassElement a1 = await _getClassElement(aPath, 'A1');
+ ClassElement a2 = await _getClassElement(aPath, 'A2');
+ ClassElement b = await _getClassElement(bPath, 'B');
+
+ int nextPrefixIndex = 0;
+ String prefixGenerator(_) {
+ return '_prefix${nextPrefixIndex++}';
+ }
+
+ var builder = new DartChangeBuilder(session);
+ await builder.addFileEdit(path, (builder) {
+ builder.addInsertion(content.length - 1, (builder) {
+ builder.writeType(a1.type);
+ builder.write(' a1; ');
+
+ builder.writeType(a2.type);
+ builder.write(' a2; ');
+
+ builder.writeType(b.type);
+ builder.write(' b;');
+ });
+ }, importPrefixGenerator: prefixGenerator);
+ List<SourceEdit> edits = getEdits(builder);
+ expect(edits, hasLength(2));
+ expect(
+ edits[0].replacement,
+ equalsIgnoringWhitespace(
+ "import 'a.dart' as _prefix0; import 'b.dart' as _prefix1;"));
+ expect(
+ edits[1].replacement,
+ equalsIgnoringWhitespace(
+ '_prefix0.A1 a1; _prefix0.A2 a2; _prefix1.B b;'));
+ }
+
test_writeType_required_dynamic() async {
String path = provider.convertPath('/test.dart');
String content = 'class A {}';