Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2275)

Unified Diff: pkg/analysis_server/lib/src/computer/import_elements_computer.dart

Issue 2986073002: Partial support for intelligent paste operation (Closed)
Patch Set: fix errors Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | pkg/analysis_server/lib/src/edit/edit_domain.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/analysis_server/lib/src/computer/import_elements_computer.dart
diff --git a/pkg/analysis_server/lib/src/computer/import_elements_computer.dart b/pkg/analysis_server/lib/src/computer/import_elements_computer.dart
index 0826e39c2702e9860ee72fa1ba56a71fb69ffe97..f9328f4351017bd7e3b8ac8be9cae3af4abaf707 100644
--- a/pkg/analysis_server/lib/src/computer/import_elements_computer.dart
+++ b/pkg/analysis_server/lib/src/computer/import_elements_computer.dart
@@ -2,55 +2,409 @@
// 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 'dart:async';
+
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analyzer/dart/analysis/results.dart';
-import 'package:analyzer/dart/analysis/session.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/ast_factory.dart';
+import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
-import 'package:analyzer_plugin/protocol/protocol_common.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/dart/ast/ast_factory.dart';
+import 'package:analyzer/src/dart/ast/token.dart';
+import 'package:analyzer/src/dart/resolver/scope.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+import 'package:front_end/src/base/syntactic_entity.dart';
+import 'package:path/src/context.dart';
/**
- * An object used to compute the edits required to ensure that a list of
- * elements is imported into a given library.
+ * An object used to compute a set of edits to add imports to a given library in
+ * order to make a given set of elements visible.
+ *
+ * This is used to implement the `edit.importElements` request.
*/
class ImportElementsComputer {
/**
- * The analysis session used to compute the unit.
+ * The resource provider used to access the file system.
*/
- final AnalysisSession session;
+ final ResourceProvider resourceProvider;
/**
- * The library element representing the library to which the imports are to be
- * added.
+ * The resolution result associated with the defining compilation unit of the
+ * library to which imports might be added.
*/
- final LibraryElement libraryElement;
+ final ResolveResult libraryResult;
/**
- * The path of the defining compilation unit of the library.
+ * Initialize a newly created builder.
*/
- final String path;
+ ImportElementsComputer(this.resourceProvider, this.libraryResult);
/**
- * The elements that are to be imported into the library.
+ * Create the edits that will cause the list of [importedElements] to be
+ * imported into the library at the given [path].
*/
- final List<ImportedElements> elements;
+ Future<SourceChange> createEdits(
+ List<ImportedElements> importedElementsList) async {
+ List<ImportedElements> filteredImportedElements =
+ _filterImportedElements(importedElementsList);
+ LibraryElement libraryElement = libraryResult.libraryElement;
+ SourceFactory sourceFactory = libraryElement.context.sourceFactory;
scheglov 2017/07/27 20:54:15 It might be worth eventually to expose SourceFacto
Brian Wilkerson 2017/07/27 20:55:33 I agree. I'll try to tackle that soon.
+ List<ImportDirective> existingImports = <ImportDirective>[];
+ for (var directive in libraryResult.unit.directives) {
+ if (directive is ImportDirective) {
+ existingImports.add(directive);
+ }
+ }
+
+ DartChangeBuilder builder = new DartChangeBuilder(libraryResult.session);
+ await builder.addFileEdit(libraryResult.path,
+ (DartFileEditBuilder builder) {
+ for (ImportedElements importedElements in filteredImportedElements) {
+ List<ImportDirective> matchingImports =
+ _findMatchingImports(existingImports, importedElements);
+ if (matchingImports.isEmpty) {
+ //
+ // The required library is not being imported with a matching prefix,
+ // so we need to add an import.
+ //
+ File importedFile = resourceProvider.getFile(importedElements.path);
+ Uri uri = sourceFactory.restoreUri(importedFile.createSource());
+ Source importedSource = importedFile.createSource(uri);
+ String importUri =
+ _getLibrarySourceUri(libraryElement, importedSource);
+ int offset = _offsetForInsertion(importUri);
+ builder.addInsertion(offset, (DartEditBuilder builder) {
+ builder.writeln();
+ builder.write("import '");
+ builder.write(importUri);
+ builder.write("'");
+ if (importedElements.prefix.isNotEmpty) {
+ builder.write(' as ');
+ builder.write(importedElements.prefix);
+ }
+ builder.write(';');
+ });
+ } else {
+ //
+ // There are some imports of the library with a matching prefix. We
+ // need to determine whether the names are already visible or whether
+ // we need to make edits to make them visible.
+ //
+ // Compute the edits that need to be made.
+ //
+ Map<ImportDirective, _ImportUpdate> updateMap =
+ <ImportDirective, _ImportUpdate>{};
+ for (String requiredName in importedElements.elements) {
+ _computeUpdate(updateMap, matchingImports, requiredName);
+ }
+ //
+ // Apply the edits.
+ //
+ for (ImportDirective directive in updateMap.keys) {
+ _ImportUpdate update = updateMap[directive];
+ List<String> namesToUnhide = update.namesToUnhide;
+ List<String> namesToShow = update.namesToShow;
+ namesToShow.sort();
+ NodeList<Combinator> combinators = directive.combinators;
+ int combinatorCount = combinators.length;
+ for (int combinatorIndex = 0;
+ combinatorIndex < combinatorCount;
+ combinatorIndex++) {
+ Combinator combinator = combinators[combinatorIndex];
+ if (combinator is HideCombinator && namesToUnhide.isNotEmpty) {
+ NodeList<SimpleIdentifier> hiddenNames = combinator.hiddenNames;
+ int nameCount = hiddenNames.length;
+ int first = -1;
+ for (int nameIndex = 0; nameIndex < nameCount; nameIndex++) {
+ if (namesToUnhide.contains(hiddenNames[nameIndex].name)) {
+ if (first < 0) {
+ first = nameIndex;
+ }
+ } else {
+ if (first >= 0) {
+ // Remove a range of names.
+ builder.addDeletion(range.startStart(
+ hiddenNames[first], hiddenNames[nameIndex]));
+ first = -1;
+ }
+ }
+ }
+ if (first == 0) {
+ // Remove the whole combinator.
+ if (combinatorIndex == 0) {
+ if (combinatorCount > 1) {
+ builder.addDeletion(range.startStart(
+ combinator, combinators[combinatorIndex + 1]));
+ } else {
+ SyntacticEntity precedingNode = directive.prefix ??
+ directive.deferredKeyword ??
+ directive.uri;
+ if (precedingNode == null) {
+ builder.addDeletion(range.node(combinator));
+ } else {
+ builder.addDeletion(
+ range.endEnd(precedingNode, combinator));
+ }
+ }
+ } else {
+ builder.addDeletion(range.endEnd(
+ combinators[combinatorIndex - 1], combinator));
+ }
+ } else if (first > 0) {
+ // Remove a range of names that includes the last name.
+ builder.addDeletion(range.endEnd(
+ hiddenNames[first - 1], hiddenNames[nameCount - 1]));
+ }
+ } else if (combinator is ShowCombinator &&
+ namesToShow.isNotEmpty) {
+ // TODO(brianwilkerson) Add the names in alphabetic order.
+ builder.addInsertion(combinator.shownNames.last.end,
+ (DartEditBuilder builder) {
+ for (String nameToShow in namesToShow) {
+ builder.write(', ');
+ builder.write(nameToShow);
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+ });
+ return builder.sourceChange;
+ }
/**
- * Initialize a newly created computer to compute the edits required to ensure
- * that the given list of [elements] is imported into a given [library].
+ * Choose the import for which the least amount of work is required,
+ * preferring to do no work in there is an import that already makes the name
+ * visible, and preferring to remove hide combinators rather than add show
+ * combinators.
+ *
+ * The name is visible without needing any changes if:
+ * - there is an import with no combinators,
+ * - there is an import with only hide combinators and none of them hide the
+ * name,
+ * - there is an import that shows the name and doesn't subsequently hide the
+ * name.
*/
- ImportElementsComputer(ResolveResult result, this.path, this.elements)
- : session = result.session,
- libraryElement = result.libraryElement;
+ void _computeUpdate(Map<ImportDirective, _ImportUpdate> updateMap,
+ List<ImportDirective> matchingImports, String requiredName) {
+ /**
+ * Return `true` if the [requiredName] is in the given list of [names].
+ */
+ bool nameIn(NodeList<SimpleIdentifier> names) {
+ for (SimpleIdentifier name in names) {
+ if (name.name == requiredName) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ImportDirective preferredDirective = null;
+ int bestEditCount = -1;
+ bool deleteHide = false;
+ bool addShow = false;
+
+ for (ImportDirective directive in matchingImports) {
+ NodeList<Combinator> combinators = directive.combinators;
+ if (combinators.isEmpty) {
+ return;
+ }
+ bool hasHide = false;
+ bool needsShow = false;
+ int editCount = 0;
+ for (Combinator combinator in combinators) {
+ if (combinator is HideCombinator) {
+ if (nameIn(combinator.hiddenNames)) {
+ hasHide = true;
+ editCount++;
+ }
+ } else if (combinator is ShowCombinator) {
+ if (needsShow || !nameIn(combinator.shownNames)) {
+ needsShow = true;
+ editCount++;
+ }
+ }
+ }
+ if (editCount == 0) {
+ return;
+ } else if (bestEditCount < 0 || editCount < bestEditCount) {
+ preferredDirective = directive;
+ bestEditCount = editCount;
+ deleteHide = hasHide;
+ addShow = needsShow;
+ }
+ }
+
+ _ImportUpdate update = updateMap.putIfAbsent(
+ preferredDirective, () => new _ImportUpdate(preferredDirective));
+ if (deleteHide) {
+ update.unhide(requiredName);
+ }
+ if (addShow) {
+ update.show(requiredName);
+ }
+ }
/**
- * Compute and return the list of edits.
+ * Filter the given list of imported elements ([originalList]) so that only
+ * the names that are not already defined still remain. Names that are already
+ * defined are removed even if they might not resolve to the same name as in
+ * the original source.
*/
- List<SourceEdit> compute() {
- DartChangeBuilder builder = new DartChangeBuilder(session);
- builder.addFileEdit(path, (DartFileEditBuilder builder) {
- // TODO(brianwilkerson) Implement this.
- });
- return <SourceEdit>[]; // builder.sourceChange
+ List<ImportedElements> _filterImportedElements(
+ List<ImportedElements> originalList) {
+ LibraryElement libraryElement = libraryResult.libraryElement;
+ LibraryScope libraryScope = new LibraryScope(libraryElement);
+ AstFactory factory = new AstFactoryImpl();
+ List<ImportedElements> filteredList = <ImportedElements>[];
+ for (ImportedElements elements in originalList) {
+ List<String> originalElements = elements.elements;
+ List<String> filteredElements = originalElements.toList();
+ for (String name in originalElements) {
+ Identifier identifier = factory
+ .simpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1));
+ if (elements.prefix.isNotEmpty) {
+ SimpleIdentifier prefix = factory.simpleIdentifier(
+ new StringToken(TokenType.IDENTIFIER, elements.prefix, -1));
+ Token period = new SimpleToken(TokenType.PERIOD, -1);
+ identifier = factory.prefixedIdentifier(prefix, period, identifier);
+ }
+ Element element = libraryScope.lookup(identifier, libraryElement);
+ if (element != null) {
+ filteredElements.remove(name);
+ }
+ }
+ if (originalElements.length == filteredElements.length) {
+ filteredList.add(elements);
+ } else if (filteredElements.isNotEmpty) {
+ filteredList.add(new ImportedElements(
+ elements.path, elements.prefix, filteredElements));
+ }
+ }
+ return filteredList;
+ }
+
+ /**
+ * Return all of the import elements in the list of [existingImports] that
+ * match the given specification of [importedElements], or an empty list if
+ * there are no such imports.
+ */
+ List<ImportDirective> _findMatchingImports(
+ List<ImportDirective> existingImports,
+ ImportedElements importedElements) {
+ List<ImportDirective> matchingImports = <ImportDirective>[];
+ for (ImportDirective existingImport in existingImports) {
+ if (_matches(existingImport, importedElements)) {
+ matchingImports.add(existingImport);
+ }
+ }
+ return matchingImports;
+ }
+
+ /**
+ * Computes the best URI to import [what] into [from].
+ *
+ * Copied from DartFileEditBuilderImpl.
+ */
+ String _getLibrarySourceUri(LibraryElement from, Source what) {
+ String whatPath = what.fullName;
+ // check if an absolute URI (such as 'dart:' or 'package:')
+ Uri whatUri = what.uri;
+ String whatUriScheme = whatUri.scheme;
+ if (whatUriScheme != '' && whatUriScheme != 'file') {
+ return whatUri.toString();
+ }
+ // compute a relative URI
+ Context context = resourceProvider.pathContext;
+ String fromFolder = context.dirname(from.source.fullName);
+ String relativeFile = context.relative(whatPath, from: fromFolder);
+ return context.split(relativeFile).join('/');
+ }
+
+ /**
+ * Return `true` if the given [import] matches the given specification of
+ * [importedElements]. They will match if they import the same library using
+ * the same prefix.
+ */
+ bool _matches(ImportDirective import, ImportedElements importedElements) {
+ return (import.element as ImportElement).importedLibrary.source.fullName ==
+ importedElements.path &&
+ (import.prefix?.name ?? '') == importedElements.prefix;
+ }
+
+ /**
+ * Return the offset at which an import of the given [importUri] should be
+ * inserted.
+ *
+ * Partially copied from DartFileEditBuilderImpl.
+ */
+ int _offsetForInsertion(String importUri) {
+ // TODO(brianwilkerson) Fix this to find the right location.
+ // See DartFileEditBuilderImpl._addLibraryImports for inspiration.
+ CompilationUnit unit = libraryResult.unit;
+ LibraryDirective libraryDirective;
+ List<ImportDirective> importDirectives = <ImportDirective>[];
+ for (Directive directive in unit.directives) {
+ if (directive is LibraryDirective) {
+ libraryDirective = directive;
+ } else if (directive is ImportDirective) {
+ importDirectives.add(directive);
+ }
+ }
+ if (importDirectives.isEmpty) {
+ if (libraryDirective == null) {
+ return 0;
+ }
+ return libraryDirective.end;
+ }
+ return importDirectives.last.end;
+ }
+}
+
+/**
+ * Information about how a given import directive needs to be updated in order
+ * to make the required names visible.
+ */
+class _ImportUpdate {
+ /**
+ * The import directive to be updated.
+ */
+ final ImportDirective import;
+
+ /**
+ * The list of names that are currently hidden that need to not be hidden.
+ */
+ final List<String> namesToUnhide = <String>[];
+
+ /**
+ * The list of names that need to be added to show clauses.
+ */
+ final List<String> namesToShow = <String>[];
+
+ /**
+ * Initialize a newly created information holder to hold information about
+ * updates to the given [import].
+ */
+ _ImportUpdate(this.import);
+
+ /**
+ * Record that the given [name] needs to be added to show combinators.
+ */
+ void show(String name) {
+ namesToShow.add(name);
+ }
+
+ /**
+ * Record that the given [name] needs to be removed from hide combinators.
+ */
+ void unhide(String name) {
+ namesToUnhide.add(name);
}
}
« no previous file with comments | « no previous file | pkg/analysis_server/lib/src/edit/edit_domain.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698