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

Side by Side 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, 4 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 import 'dart:async';
6
5 import 'package:analysis_server/protocol/protocol_generated.dart'; 7 import 'package:analysis_server/protocol/protocol_generated.dart';
6 import 'package:analyzer/dart/analysis/results.dart'; 8 import 'package:analyzer/dart/analysis/results.dart';
7 import 'package:analyzer/dart/analysis/session.dart'; 9 import 'package:analyzer/dart/ast/ast.dart';
10 import 'package:analyzer/dart/ast/ast_factory.dart';
11 import 'package:analyzer/dart/ast/token.dart';
8 import 'package:analyzer/dart/element/element.dart'; 12 import 'package:analyzer/dart/element/element.dart';
9 import 'package:analyzer_plugin/protocol/protocol_common.dart'; 13 import 'package:analyzer/file_system/file_system.dart';
14 import 'package:analyzer/src/dart/ast/ast_factory.dart';
15 import 'package:analyzer/src/dart/ast/token.dart';
16 import 'package:analyzer/src/dart/resolver/scope.dart';
17 import 'package:analyzer/src/generated/source.dart';
18 import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
10 import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dar t'; 19 import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dar t';
20 import 'package:analyzer_plugin/utilities/range_factory.dart';
21 import 'package:front_end/src/base/syntactic_entity.dart';
22 import 'package:path/src/context.dart';
11 23
12 /** 24 /**
13 * An object used to compute the edits required to ensure that a list of 25 * An object used to compute a set of edits to add imports to a given library in
14 * elements is imported into a given library. 26 * order to make a given set of elements visible.
27 *
28 * This is used to implement the `edit.importElements` request.
15 */ 29 */
16 class ImportElementsComputer { 30 class ImportElementsComputer {
17 /** 31 /**
18 * The analysis session used to compute the unit. 32 * The resource provider used to access the file system.
19 */ 33 */
20 final AnalysisSession session; 34 final ResourceProvider resourceProvider;
21 35
22 /** 36 /**
23 * The library element representing the library to which the imports are to be 37 * The resolution result associated with the defining compilation unit of the
24 * added. 38 * library to which imports might be added.
25 */ 39 */
26 final LibraryElement libraryElement; 40 final ResolveResult libraryResult;
27 41
28 /** 42 /**
29 * The path of the defining compilation unit of the library. 43 * Initialize a newly created builder.
30 */ 44 */
31 final String path; 45 ImportElementsComputer(this.resourceProvider, this.libraryResult);
32 46
33 /** 47 /**
34 * The elements that are to be imported into the library. 48 * Create the edits that will cause the list of [importedElements] to be
35 */ 49 * imported into the library at the given [path].
36 final List<ImportedElements> elements; 50 */
37 51 Future<SourceChange> createEdits(
38 /** 52 List<ImportedElements> importedElementsList) async {
39 * Initialize a newly created computer to compute the edits required to ensure 53 List<ImportedElements> filteredImportedElements =
40 * that the given list of [elements] is imported into a given [library]. 54 _filterImportedElements(importedElementsList);
41 */ 55 LibraryElement libraryElement = libraryResult.libraryElement;
42 ImportElementsComputer(ResolveResult result, this.path, this.elements) 56 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.
43 : session = result.session, 57 List<ImportDirective> existingImports = <ImportDirective>[];
44 libraryElement = result.libraryElement; 58 for (var directive in libraryResult.unit.directives) {
45 59 if (directive is ImportDirective) {
46 /** 60 existingImports.add(directive);
47 * Compute and return the list of edits. 61 }
48 */ 62 }
49 List<SourceEdit> compute() { 63
50 DartChangeBuilder builder = new DartChangeBuilder(session); 64 DartChangeBuilder builder = new DartChangeBuilder(libraryResult.session);
51 builder.addFileEdit(path, (DartFileEditBuilder builder) { 65 await builder.addFileEdit(libraryResult.path,
52 // TODO(brianwilkerson) Implement this. 66 (DartFileEditBuilder builder) {
67 for (ImportedElements importedElements in filteredImportedElements) {
68 List<ImportDirective> matchingImports =
69 _findMatchingImports(existingImports, importedElements);
70 if (matchingImports.isEmpty) {
71 //
72 // The required library is not being imported with a matching prefix,
73 // so we need to add an import.
74 //
75 File importedFile = resourceProvider.getFile(importedElements.path);
76 Uri uri = sourceFactory.restoreUri(importedFile.createSource());
77 Source importedSource = importedFile.createSource(uri);
78 String importUri =
79 _getLibrarySourceUri(libraryElement, importedSource);
80 int offset = _offsetForInsertion(importUri);
81 builder.addInsertion(offset, (DartEditBuilder builder) {
82 builder.writeln();
83 builder.write("import '");
84 builder.write(importUri);
85 builder.write("'");
86 if (importedElements.prefix.isNotEmpty) {
87 builder.write(' as ');
88 builder.write(importedElements.prefix);
89 }
90 builder.write(';');
91 });
92 } else {
93 //
94 // There are some imports of the library with a matching prefix. We
95 // need to determine whether the names are already visible or whether
96 // we need to make edits to make them visible.
97 //
98 // Compute the edits that need to be made.
99 //
100 Map<ImportDirective, _ImportUpdate> updateMap =
101 <ImportDirective, _ImportUpdate>{};
102 for (String requiredName in importedElements.elements) {
103 _computeUpdate(updateMap, matchingImports, requiredName);
104 }
105 //
106 // Apply the edits.
107 //
108 for (ImportDirective directive in updateMap.keys) {
109 _ImportUpdate update = updateMap[directive];
110 List<String> namesToUnhide = update.namesToUnhide;
111 List<String> namesToShow = update.namesToShow;
112 namesToShow.sort();
113 NodeList<Combinator> combinators = directive.combinators;
114 int combinatorCount = combinators.length;
115 for (int combinatorIndex = 0;
116 combinatorIndex < combinatorCount;
117 combinatorIndex++) {
118 Combinator combinator = combinators[combinatorIndex];
119 if (combinator is HideCombinator && namesToUnhide.isNotEmpty) {
120 NodeList<SimpleIdentifier> hiddenNames = combinator.hiddenNames;
121 int nameCount = hiddenNames.length;
122 int first = -1;
123 for (int nameIndex = 0; nameIndex < nameCount; nameIndex++) {
124 if (namesToUnhide.contains(hiddenNames[nameIndex].name)) {
125 if (first < 0) {
126 first = nameIndex;
127 }
128 } else {
129 if (first >= 0) {
130 // Remove a range of names.
131 builder.addDeletion(range.startStart(
132 hiddenNames[first], hiddenNames[nameIndex]));
133 first = -1;
134 }
135 }
136 }
137 if (first == 0) {
138 // Remove the whole combinator.
139 if (combinatorIndex == 0) {
140 if (combinatorCount > 1) {
141 builder.addDeletion(range.startStart(
142 combinator, combinators[combinatorIndex + 1]));
143 } else {
144 SyntacticEntity precedingNode = directive.prefix ??
145 directive.deferredKeyword ??
146 directive.uri;
147 if (precedingNode == null) {
148 builder.addDeletion(range.node(combinator));
149 } else {
150 builder.addDeletion(
151 range.endEnd(precedingNode, combinator));
152 }
153 }
154 } else {
155 builder.addDeletion(range.endEnd(
156 combinators[combinatorIndex - 1], combinator));
157 }
158 } else if (first > 0) {
159 // Remove a range of names that includes the last name.
160 builder.addDeletion(range.endEnd(
161 hiddenNames[first - 1], hiddenNames[nameCount - 1]));
162 }
163 } else if (combinator is ShowCombinator &&
164 namesToShow.isNotEmpty) {
165 // TODO(brianwilkerson) Add the names in alphabetic order.
166 builder.addInsertion(combinator.shownNames.last.end,
167 (DartEditBuilder builder) {
168 for (String nameToShow in namesToShow) {
169 builder.write(', ');
170 builder.write(nameToShow);
171 }
172 });
173 }
174 }
175 }
176 }
177 }
53 }); 178 });
54 return <SourceEdit>[]; // builder.sourceChange 179 return builder.sourceChange;
180 }
181
182 /**
183 * Choose the import for which the least amount of work is required,
184 * preferring to do no work in there is an import that already makes the name
185 * visible, and preferring to remove hide combinators rather than add show
186 * combinators.
187 *
188 * The name is visible without needing any changes if:
189 * - there is an import with no combinators,
190 * - there is an import with only hide combinators and none of them hide the
191 * name,
192 * - there is an import that shows the name and doesn't subsequently hide the
193 * name.
194 */
195 void _computeUpdate(Map<ImportDirective, _ImportUpdate> updateMap,
196 List<ImportDirective> matchingImports, String requiredName) {
197 /**
198 * Return `true` if the [requiredName] is in the given list of [names].
199 */
200 bool nameIn(NodeList<SimpleIdentifier> names) {
201 for (SimpleIdentifier name in names) {
202 if (name.name == requiredName) {
203 return true;
204 }
205 }
206 return false;
207 }
208
209 ImportDirective preferredDirective = null;
210 int bestEditCount = -1;
211 bool deleteHide = false;
212 bool addShow = false;
213
214 for (ImportDirective directive in matchingImports) {
215 NodeList<Combinator> combinators = directive.combinators;
216 if (combinators.isEmpty) {
217 return;
218 }
219 bool hasHide = false;
220 bool needsShow = false;
221 int editCount = 0;
222 for (Combinator combinator in combinators) {
223 if (combinator is HideCombinator) {
224 if (nameIn(combinator.hiddenNames)) {
225 hasHide = true;
226 editCount++;
227 }
228 } else if (combinator is ShowCombinator) {
229 if (needsShow || !nameIn(combinator.shownNames)) {
230 needsShow = true;
231 editCount++;
232 }
233 }
234 }
235 if (editCount == 0) {
236 return;
237 } else if (bestEditCount < 0 || editCount < bestEditCount) {
238 preferredDirective = directive;
239 bestEditCount = editCount;
240 deleteHide = hasHide;
241 addShow = needsShow;
242 }
243 }
244
245 _ImportUpdate update = updateMap.putIfAbsent(
246 preferredDirective, () => new _ImportUpdate(preferredDirective));
247 if (deleteHide) {
248 update.unhide(requiredName);
249 }
250 if (addShow) {
251 update.show(requiredName);
252 }
253 }
254
255 /**
256 * Filter the given list of imported elements ([originalList]) so that only
257 * the names that are not already defined still remain. Names that are already
258 * defined are removed even if they might not resolve to the same name as in
259 * the original source.
260 */
261 List<ImportedElements> _filterImportedElements(
262 List<ImportedElements> originalList) {
263 LibraryElement libraryElement = libraryResult.libraryElement;
264 LibraryScope libraryScope = new LibraryScope(libraryElement);
265 AstFactory factory = new AstFactoryImpl();
266 List<ImportedElements> filteredList = <ImportedElements>[];
267 for (ImportedElements elements in originalList) {
268 List<String> originalElements = elements.elements;
269 List<String> filteredElements = originalElements.toList();
270 for (String name in originalElements) {
271 Identifier identifier = factory
272 .simpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1));
273 if (elements.prefix.isNotEmpty) {
274 SimpleIdentifier prefix = factory.simpleIdentifier(
275 new StringToken(TokenType.IDENTIFIER, elements.prefix, -1));
276 Token period = new SimpleToken(TokenType.PERIOD, -1);
277 identifier = factory.prefixedIdentifier(prefix, period, identifier);
278 }
279 Element element = libraryScope.lookup(identifier, libraryElement);
280 if (element != null) {
281 filteredElements.remove(name);
282 }
283 }
284 if (originalElements.length == filteredElements.length) {
285 filteredList.add(elements);
286 } else if (filteredElements.isNotEmpty) {
287 filteredList.add(new ImportedElements(
288 elements.path, elements.prefix, filteredElements));
289 }
290 }
291 return filteredList;
292 }
293
294 /**
295 * Return all of the import elements in the list of [existingImports] that
296 * match the given specification of [importedElements], or an empty list if
297 * there are no such imports.
298 */
299 List<ImportDirective> _findMatchingImports(
300 List<ImportDirective> existingImports,
301 ImportedElements importedElements) {
302 List<ImportDirective> matchingImports = <ImportDirective>[];
303 for (ImportDirective existingImport in existingImports) {
304 if (_matches(existingImport, importedElements)) {
305 matchingImports.add(existingImport);
306 }
307 }
308 return matchingImports;
309 }
310
311 /**
312 * Computes the best URI to import [what] into [from].
313 *
314 * Copied from DartFileEditBuilderImpl.
315 */
316 String _getLibrarySourceUri(LibraryElement from, Source what) {
317 String whatPath = what.fullName;
318 // check if an absolute URI (such as 'dart:' or 'package:')
319 Uri whatUri = what.uri;
320 String whatUriScheme = whatUri.scheme;
321 if (whatUriScheme != '' && whatUriScheme != 'file') {
322 return whatUri.toString();
323 }
324 // compute a relative URI
325 Context context = resourceProvider.pathContext;
326 String fromFolder = context.dirname(from.source.fullName);
327 String relativeFile = context.relative(whatPath, from: fromFolder);
328 return context.split(relativeFile).join('/');
329 }
330
331 /**
332 * Return `true` if the given [import] matches the given specification of
333 * [importedElements]. They will match if they import the same library using
334 * the same prefix.
335 */
336 bool _matches(ImportDirective import, ImportedElements importedElements) {
337 return (import.element as ImportElement).importedLibrary.source.fullName ==
338 importedElements.path &&
339 (import.prefix?.name ?? '') == importedElements.prefix;
340 }
341
342 /**
343 * Return the offset at which an import of the given [importUri] should be
344 * inserted.
345 *
346 * Partially copied from DartFileEditBuilderImpl.
347 */
348 int _offsetForInsertion(String importUri) {
349 // TODO(brianwilkerson) Fix this to find the right location.
350 // See DartFileEditBuilderImpl._addLibraryImports for inspiration.
351 CompilationUnit unit = libraryResult.unit;
352 LibraryDirective libraryDirective;
353 List<ImportDirective> importDirectives = <ImportDirective>[];
354 for (Directive directive in unit.directives) {
355 if (directive is LibraryDirective) {
356 libraryDirective = directive;
357 } else if (directive is ImportDirective) {
358 importDirectives.add(directive);
359 }
360 }
361 if (importDirectives.isEmpty) {
362 if (libraryDirective == null) {
363 return 0;
364 }
365 return libraryDirective.end;
366 }
367 return importDirectives.last.end;
55 } 368 }
56 } 369 }
370
371 /**
372 * Information about how a given import directive needs to be updated in order
373 * to make the required names visible.
374 */
375 class _ImportUpdate {
376 /**
377 * The import directive to be updated.
378 */
379 final ImportDirective import;
380
381 /**
382 * The list of names that are currently hidden that need to not be hidden.
383 */
384 final List<String> namesToUnhide = <String>[];
385
386 /**
387 * The list of names that need to be added to show clauses.
388 */
389 final List<String> namesToShow = <String>[];
390
391 /**
392 * Initialize a newly created information holder to hold information about
393 * updates to the given [import].
394 */
395 _ImportUpdate(this.import);
396
397 /**
398 * Record that the given [name] needs to be added to show combinators.
399 */
400 void show(String name) {
401 namesToShow.add(name);
402 }
403
404 /**
405 * Record that the given [name] needs to be removed from hide combinators.
406 */
407 void unhide(String name) {
408 namesToUnhide.add(name);
409 }
410 }
OLDNEW
« 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