OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 library services.src.correction.fix; | |
6 | |
7 import 'dart:collection'; | |
8 | |
9 import 'package:analysis_services/correction/change.dart'; | |
10 import 'package:analysis_services/correction/fix.dart'; | |
11 import 'package:analysis_services/search/hierarchy.dart'; | |
12 import 'package:analysis_services/search/search_engine.dart'; | |
13 import 'package:analysis_services/src/correction/levenshtein.dart'; | |
14 import 'package:analysis_services/src/correction/name_suggestion.dart'; | |
15 import 'package:analysis_services/src/correction/source_buffer.dart'; | |
16 import 'package:analysis_services/src/correction/source_range.dart' as rf; | |
17 import 'package:analysis_services/src/correction/strings.dart'; | |
18 import 'package:analysis_services/src/correction/util.dart'; | |
19 import 'package:analyzer/src/generated/ast.dart'; | |
20 import 'package:analyzer/src/generated/element.dart'; | |
21 import 'package:analyzer/src/generated/engine.dart'; | |
22 import 'package:analyzer/src/generated/error.dart'; | |
23 import 'package:analyzer/src/generated/java_core.dart'; | |
24 import 'package:analyzer/src/generated/parser.dart'; | |
25 import 'package:analyzer/src/generated/scanner.dart'; | |
26 import 'package:analyzer/src/generated/sdk.dart'; | |
27 import 'package:analyzer/src/generated/source.dart'; | |
28 import 'package:analyzer/src/generated/utilities_dart.dart'; | |
29 import 'package:path/path.dart'; | |
30 | |
31 | |
32 /** | |
33 * A predicate is a one-argument function that returns a boolean value. | |
34 */ | |
35 typedef bool Predicate<E>(E argument); | |
36 | |
37 | |
38 /** | |
39 * The computer for Dart fixes. | |
40 */ | |
41 class FixProcessor { | |
42 static const int MAX_LEVENSHTEIN_DISTANCE = 3; | |
43 | |
44 final SearchEngine searchEngine; | |
45 final Source source; | |
46 final String file; | |
47 final CompilationUnit unit; | |
48 final AnalysisError error; | |
49 CompilationUnitElement unitElement; | |
50 Source unitSource; | |
51 LibraryElement unitLibraryElement; | |
52 String unitLibraryFile; | |
53 String unitLibraryFolder; | |
54 | |
55 final List<Edit> edits = <Edit>[]; | |
56 final Map<String, LinkedEditGroup> linkedPositionGroups = <String, | |
57 LinkedEditGroup>{}; | |
58 Position exitPosition = null; | |
59 final List<Fix> fixes = <Fix>[]; | |
60 | |
61 CorrectionUtils utils; | |
62 int errorOffset; | |
63 int errorLength; | |
64 int errorEnd; | |
65 AstNode node; | |
66 AstNode coveredNode; | |
67 | |
68 FixProcessor(this.searchEngine, this.source, this.file, this.unit, this.error) | |
69 { | |
70 unitElement = unit.element; | |
71 unitSource = unitElement.source; | |
72 unitLibraryElement = unitElement.library; | |
73 unitLibraryFile = unitLibraryElement.source.fullName; | |
74 unitLibraryFolder = dirname(unitLibraryFile); | |
75 } | |
76 | |
77 DartType get coreTypeBool => _getCoreType("bool"); | |
78 | |
79 /** | |
80 * Returns the EOL to use for this [CompilationUnit]. | |
81 */ | |
82 String get eol => utils.endOfLine; | |
83 | |
84 List<Fix> compute() { | |
85 utils = new CorrectionUtils(unit); | |
86 errorOffset = error.offset; | |
87 errorLength = error.length; | |
88 errorEnd = errorOffset + errorLength; | |
89 node = new NodeLocator.con1(errorOffset).searchWithin(unit); | |
90 coveredNode = new NodeLocator.con2( | |
91 errorOffset, | |
92 errorOffset + errorLength).searchWithin(unit); | |
93 // analyze ErrorCode | |
94 ErrorCode errorCode = error.errorCode; | |
95 if (errorCode == StaticWarningCode.UNDEFINED_CLASS_BOOLEAN) { | |
96 _addFix_boolInsteadOfBoolean(); | |
97 } | |
98 if (errorCode == | |
99 CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE) { | |
100 _addFix_replaceWithConstInstanceCreation(); | |
101 } | |
102 if (errorCode == | |
103 CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT) { | |
104 _addFix_createConstructorSuperExplicit(); | |
105 } | |
106 if (errorCode == | |
107 CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT) { | |
108 _addFix_createConstructorSuperImplicit(); | |
109 } | |
110 if (errorCode == | |
111 CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT) { | |
112 _addFix_createConstructorSuperExplicit(); | |
113 } | |
114 if (errorCode == HintCode.DIVISION_OPTIMIZATION) { | |
115 _addFix_useEffectiveIntegerDivision(); | |
116 } | |
117 if (errorCode == HintCode.TYPE_CHECK_IS_NOT_NULL) { | |
118 _addFix_isNotNull(); | |
119 } | |
120 if (errorCode == HintCode.TYPE_CHECK_IS_NULL) { | |
121 _addFix_isNull(); | |
122 } | |
123 if (errorCode == HintCode.UNNECESSARY_CAST) { | |
124 _addFix_removeUnnecessaryCast(); | |
125 } | |
126 if (errorCode == HintCode.UNUSED_IMPORT) { | |
127 _addFix_removeUnusedImport(); | |
128 } | |
129 if (errorCode == ParserErrorCode.EXPECTED_TOKEN) { | |
130 _addFix_insertSemicolon(); | |
131 } | |
132 if (errorCode == ParserErrorCode.GETTER_WITH_PARAMETERS) { | |
133 _addFix_removeParameters_inGetterDeclaration(); | |
134 } | |
135 if (errorCode == StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER) { | |
136 _addFix_makeEnclosingClassAbstract(); | |
137 } | |
138 if (errorCode == StaticWarningCode.EXTRA_POSITIONAL_ARGUMENTS) { | |
139 _addFix_createConstructor_insteadOfSyntheticDefault(); | |
140 } | |
141 if (errorCode == StaticWarningCode.NEW_WITH_UNDEFINED_CONSTRUCTOR) { | |
142 _addFix_createConstructor_named(); | |
143 } | |
144 if (errorCode == | |
145 StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE || | |
146 errorCode == | |
147 StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO || | |
148 errorCode == | |
149 StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE
|| | |
150 errorCode == | |
151 StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR |
| | |
152 errorCode == | |
153 StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_P
LUS) { | |
154 // make class abstract | |
155 _addFix_makeEnclosingClassAbstract(); | |
156 // implement methods | |
157 AnalysisErrorWithProperties errorWithProperties = | |
158 error as AnalysisErrorWithProperties; | |
159 Object property = | |
160 errorWithProperties.getProperty(ErrorProperty.UNIMPLEMENTED_METHODS); | |
161 List<ExecutableElement> missingOverrides = | |
162 property as List<ExecutableElement>; | |
163 _addFix_createMissingOverrides(missingOverrides); | |
164 _addFix_createNoSuchMethod(); | |
165 } | |
166 if (errorCode == StaticWarningCode.UNDEFINED_CLASS) { | |
167 _addFix_importLibrary_withType(); | |
168 _addFix_createClass(); | |
169 _addFix_undefinedClass_useSimilar(); | |
170 } | |
171 if (errorCode == StaticWarningCode.UNDEFINED_IDENTIFIER) { | |
172 _addFix_createFunction_forFunctionType(); | |
173 _addFix_importLibrary_withType(); | |
174 _addFix_importLibrary_withTopLevelVariable(); | |
175 } | |
176 if (errorCode == StaticTypeWarningCode.INSTANCE_ACCESS_TO_STATIC_MEMBER) { | |
177 _addFix_useStaticAccess_method(); | |
178 _addFix_useStaticAccess_property(); | |
179 } | |
180 if (errorCode == StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION) { | |
181 _addFix_removeParentheses_inGetterInvocation(); | |
182 } | |
183 if (errorCode == StaticTypeWarningCode.UNDEFINED_FUNCTION) { | |
184 _addFix_importLibrary_withFunction(); | |
185 _addFix_undefinedFunction_useSimilar(); | |
186 _addFix_undefinedFunction_create(); | |
187 } | |
188 if (errorCode == StaticTypeWarningCode.UNDEFINED_GETTER) { | |
189 _addFix_createFunction_forFunctionType(); | |
190 } | |
191 if (errorCode == HintCode.UNDEFINED_METHOD || | |
192 errorCode == StaticTypeWarningCode.UNDEFINED_METHOD) { | |
193 _addFix_undefinedMethod_useSimilar(); | |
194 _addFix_undefinedMethod_create(); | |
195 _addFix_undefinedFunction_create(); | |
196 } | |
197 // done | |
198 return fixes; | |
199 } | |
200 | |
201 void _addFix(FixKind kind, List args, {String fixFile}) { | |
202 if (fixFile == null) { | |
203 fixFile = file; | |
204 } | |
205 FileEdit fileEdit = new FileEdit(file); | |
206 fileEdit.addAll(edits); | |
207 // prepare Change | |
208 String message = formatList(kind.message, args); | |
209 Change change = new Change(message); | |
210 change.addFileEdit(fileEdit); | |
211 linkedPositionGroups.values.forEach( | |
212 (group) => change.addLinkedEditGroup(group)); | |
213 change.selection = exitPosition; | |
214 // add Fix | |
215 Fix fix = new Fix(kind, change); | |
216 fixes.add(fix); | |
217 // clear | |
218 edits.clear(); | |
219 linkedPositionGroups.clear(); | |
220 exitPosition = null; | |
221 } | |
222 | |
223 void _addFix_boolInsteadOfBoolean() { | |
224 SourceRange range = rf.rangeError(error); | |
225 _addReplaceEdit(range, "bool"); | |
226 _addFix(FixKind.REPLACE_BOOLEAN_WITH_BOOL, []); | |
227 } | |
228 | |
229 void _addFix_createClass() { | |
230 if (_mayBeTypeIdentifier(node)) { | |
231 String name = (node as SimpleIdentifier).name; | |
232 // prepare environment | |
233 CompilationUnitMember enclosingMember = | |
234 node.getAncestor((node) => node is CompilationUnitMember); | |
235 int offset = enclosingMember.end; | |
236 String prefix = ""; | |
237 // prepare source | |
238 SourceBuilder sb = new SourceBuilder(file, offset); | |
239 { | |
240 sb.append("${eol}${eol}"); | |
241 sb.append(prefix); | |
242 // "class" | |
243 sb.append("class "); | |
244 // append name | |
245 { | |
246 sb.startPosition("NAME"); | |
247 sb.append(name); | |
248 sb.endPosition(); | |
249 } | |
250 // no members | |
251 sb.append(" {"); | |
252 sb.append(eol); | |
253 sb.append("}"); | |
254 } | |
255 // insert source | |
256 _insertBuilder(sb); | |
257 _addLinkedPosition("NAME", rf.rangeNode(node)); | |
258 // add proposal | |
259 _addFix(FixKind.CREATE_CLASS, [name]); | |
260 } | |
261 } | |
262 | |
263 void _addFix_createConstructorSuperExplicit() { | |
264 ConstructorDeclaration targetConstructor = | |
265 node.parent as ConstructorDeclaration; | |
266 ClassDeclaration targetClassNode = | |
267 targetConstructor.parent as ClassDeclaration; | |
268 ClassElement targetClassElement = targetClassNode.element; | |
269 ClassElement superClassElement = targetClassElement.supertype.element; | |
270 // add proposals for all super constructors | |
271 List<ConstructorElement> superConstructors = superClassElement.constructors; | |
272 for (ConstructorElement superConstructor in superConstructors) { | |
273 String constructorName = superConstructor.name; | |
274 // skip private | |
275 if (Identifier.isPrivateName(constructorName)) { | |
276 continue; | |
277 } | |
278 // prepare SourceBuilder | |
279 SourceBuilder sb; | |
280 { | |
281 List<ConstructorInitializer> initializers = | |
282 targetConstructor.initializers; | |
283 if (initializers.isEmpty) { | |
284 int insertOffset = targetConstructor.parameters.end; | |
285 sb = new SourceBuilder(file, insertOffset); | |
286 sb.append(" : "); | |
287 } else { | |
288 ConstructorInitializer lastInitializer = | |
289 initializers[initializers.length - 1]; | |
290 int insertOffset = lastInitializer.end; | |
291 sb = new SourceBuilder(file, insertOffset); | |
292 sb.append(", "); | |
293 } | |
294 } | |
295 // add super constructor name | |
296 sb.append("super"); | |
297 if (!StringUtils.isEmpty(constructorName)) { | |
298 sb.append("."); | |
299 sb.append(constructorName); | |
300 } | |
301 // add arguments | |
302 sb.append("("); | |
303 bool firstParameter = true; | |
304 for (ParameterElement parameter in superConstructor.parameters) { | |
305 // skip non-required parameters | |
306 if (parameter.parameterKind != ParameterKind.REQUIRED) { | |
307 break; | |
308 } | |
309 // comma | |
310 if (firstParameter) { | |
311 firstParameter = false; | |
312 } else { | |
313 sb.append(", "); | |
314 } | |
315 // default value | |
316 DartType parameterType = parameter.type; | |
317 sb.startPosition(parameter.name); | |
318 sb.append(getDefaultValueCode(parameterType)); | |
319 sb.endPosition(); | |
320 } | |
321 sb.append(")"); | |
322 // insert proposal | |
323 _insertBuilder(sb); | |
324 // add proposal | |
325 String proposalName = _getConstructorProposalName(superConstructor); | |
326 _addFix(FixKind.ADD_SUPER_CONSTRUCTOR_INVOCATION, [proposalName]); | |
327 } | |
328 } | |
329 | |
330 void _addFix_createConstructorSuperImplicit() { | |
331 ClassDeclaration targetClassNode = node.parent as ClassDeclaration; | |
332 ClassElement targetClassElement = targetClassNode.element; | |
333 ClassElement superClassElement = targetClassElement.supertype.element; | |
334 String targetClassName = targetClassElement.name; | |
335 // add proposals for all super constructors | |
336 List<ConstructorElement> superConstructors = superClassElement.constructors; | |
337 for (ConstructorElement superConstructor in superConstructors) { | |
338 String constructorName = superConstructor.name; | |
339 // skip private | |
340 if (Identifier.isPrivateName(constructorName)) { | |
341 continue; | |
342 } | |
343 // prepare parameters and arguments | |
344 SourceBuilder parametersBuffer = new SourceBuilder.buffer(); | |
345 SourceBuilder argumentsBuffer = new SourceBuilder.buffer(); | |
346 bool firstParameter = true; | |
347 for (ParameterElement parameter in superConstructor.parameters) { | |
348 // skip non-required parameters | |
349 if (parameter.parameterKind != ParameterKind.REQUIRED) { | |
350 break; | |
351 } | |
352 // comma | |
353 if (firstParameter) { | |
354 firstParameter = false; | |
355 } else { | |
356 parametersBuffer.append(', '); | |
357 argumentsBuffer.append(', '); | |
358 } | |
359 // name | |
360 String parameterName = parameter.displayName; | |
361 if (parameterName.length > 1 && parameterName.startsWith('_')) { | |
362 parameterName = parameterName.substring(1); | |
363 } | |
364 // parameter & argument | |
365 _appendParameterSource(parametersBuffer, parameter.type, parameterName); | |
366 argumentsBuffer.append(parameterName); | |
367 } | |
368 // add proposal | |
369 _ConstructorLocation targetLocation = | |
370 _prepareNewConstructorLocation(targetClassNode); | |
371 SourceBuilder sb = new SourceBuilder(file, targetLocation._offset); | |
372 { | |
373 String indent = utils.getIndent(1); | |
374 sb.append(targetLocation._prefix); | |
375 sb.append(indent); | |
376 sb.append(targetClassName); | |
377 if (!constructorName.isEmpty) { | |
378 sb.startPosition('NAME'); | |
379 sb.append('.'); | |
380 sb.append(constructorName); | |
381 sb.endPosition(); | |
382 } | |
383 sb.append("("); | |
384 sb.append(parametersBuffer.toString()); | |
385 sb.append(') : super'); | |
386 if (!constructorName.isEmpty) { | |
387 sb.append('.'); | |
388 sb.append(constructorName); | |
389 } | |
390 sb.append('('); | |
391 sb.append(argumentsBuffer.toString()); | |
392 sb.append(');'); | |
393 sb.append(targetLocation._suffix); | |
394 } | |
395 _insertBuilder(sb); | |
396 // add proposal | |
397 String proposalName = _getConstructorProposalName(superConstructor); | |
398 _addFix(FixKind.CREATE_CONSTRUCTOR_SUPER, [proposalName]); | |
399 } | |
400 } | |
401 | |
402 void _addFix_createConstructor_insteadOfSyntheticDefault() { | |
403 TypeName typeName = null; | |
404 ConstructorName constructorName = null; | |
405 InstanceCreationExpression instanceCreation = null; | |
406 if (node is SimpleIdentifier) { | |
407 if (node.parent is TypeName) { | |
408 typeName = node.parent as TypeName; | |
409 if (typeName.name == node && typeName.parent is ConstructorName) { | |
410 constructorName = typeName.parent as ConstructorName; | |
411 // should be synthetic default constructor | |
412 { | |
413 ConstructorElement constructorElement = | |
414 constructorName.staticElement; | |
415 if (constructorElement == null || | |
416 !constructorElement.isDefaultConstructor || | |
417 !constructorElement.isSynthetic) { | |
418 return; | |
419 } | |
420 } | |
421 // prepare InstanceCreationExpression | |
422 if (constructorName.parent is InstanceCreationExpression) { | |
423 instanceCreation = constructorName.parent as | |
424 InstanceCreationExpression; | |
425 if (instanceCreation.constructorName != constructorName) { | |
426 return; | |
427 } | |
428 } | |
429 } | |
430 } | |
431 } | |
432 // do we have enough information? | |
433 if (instanceCreation == null) { | |
434 return; | |
435 } | |
436 // prepare target | |
437 DartType targetType = typeName.type; | |
438 if (targetType is! InterfaceType) { | |
439 return; | |
440 } | |
441 ClassElement targetElement = targetType.element as ClassElement; | |
442 String targetFile = targetElement.source.fullName; | |
443 ClassDeclaration targetClass = targetElement.node; | |
444 _ConstructorLocation targetLocation = | |
445 _prepareNewConstructorLocation(targetClass); | |
446 // build method source | |
447 SourceBuilder sb = new SourceBuilder(targetFile, targetLocation._offset); | |
448 { | |
449 String indent = " "; | |
450 sb.append(targetLocation._prefix); | |
451 sb.append(indent); | |
452 sb.append(targetElement.name); | |
453 _addFix_undefinedMethod_create_parameters( | |
454 sb, | |
455 instanceCreation.argumentList); | |
456 sb.append(") {${eol}${indent}}"); | |
457 sb.append(targetLocation._suffix); | |
458 } | |
459 // insert source | |
460 _insertBuilder(sb); | |
461 // add proposal | |
462 _addFix(FixKind.CREATE_CONSTRUCTOR, [constructorName], fixFile: targetFile); | |
463 } | |
464 | |
465 void _addFix_createConstructor_named() { | |
466 SimpleIdentifier name = null; | |
467 ConstructorName constructorName = null; | |
468 InstanceCreationExpression instanceCreation = null; | |
469 if (node is SimpleIdentifier) { | |
470 // name | |
471 name = node as SimpleIdentifier; | |
472 if (name.parent is ConstructorName) { | |
473 constructorName = name.parent as ConstructorName; | |
474 if (constructorName.name == name) { | |
475 // Type.name | |
476 if (constructorName.parent is InstanceCreationExpression) { | |
477 instanceCreation = constructorName.parent as | |
478 InstanceCreationExpression; | |
479 // new Type.name() | |
480 if (instanceCreation.constructorName != constructorName) { | |
481 return; | |
482 } | |
483 } | |
484 } | |
485 } | |
486 } | |
487 // do we have enough information? | |
488 if (instanceCreation == null) { | |
489 return; | |
490 } | |
491 // prepare target interface type | |
492 DartType targetType = constructorName.type.type; | |
493 if (targetType is! InterfaceType) { | |
494 return; | |
495 } | |
496 ClassElement targetElement = targetType.element as ClassElement; | |
497 String targetFile = targetElement.source.fullName; | |
498 ClassDeclaration targetClass = targetElement.node; | |
499 _ConstructorLocation targetLocation = | |
500 _prepareNewConstructorLocation(targetClass); | |
501 // build method source | |
502 SourceBuilder sb = new SourceBuilder(targetFile, targetLocation._offset); | |
503 { | |
504 String indent = " "; | |
505 sb.append(targetLocation._prefix); | |
506 sb.append(indent); | |
507 sb.append(targetElement.name); | |
508 sb.append("."); | |
509 // append name | |
510 { | |
511 sb.startPosition("NAME"); | |
512 sb.append(name.name); | |
513 sb.endPosition(); | |
514 } | |
515 _addFix_undefinedMethod_create_parameters( | |
516 sb, | |
517 instanceCreation.argumentList); | |
518 sb.append(") {${eol}${indent}}"); | |
519 sb.append(targetLocation._suffix); | |
520 } | |
521 // insert source | |
522 _insertBuilder(sb); | |
523 if (targetFile == file) { | |
524 _addLinkedPosition("NAME", rf.rangeNode(name)); | |
525 } | |
526 // add proposal | |
527 _addFix(FixKind.CREATE_CONSTRUCTOR, [constructorName], fixFile: targetFile); | |
528 } | |
529 | |
530 void _addFix_createFunction_forFunctionType() { | |
531 if (node is SimpleIdentifier) { | |
532 SimpleIdentifier nameNode = node as SimpleIdentifier; | |
533 // prepare argument expression (to get parameter) | |
534 ClassElement targetElement; | |
535 Expression argument; | |
536 { | |
537 Expression target = getQualifiedPropertyTarget(node); | |
538 if (target != null) { | |
539 DartType targetType = target.bestType; | |
540 if (targetType != null && targetType.element is ClassElement) { | |
541 targetElement = targetType.element as ClassElement; | |
542 argument = target.parent as Expression; | |
543 } else { | |
544 return; | |
545 } | |
546 } else { | |
547 ClassDeclaration enclosingClass = | |
548 node.getAncestor((node) => node is ClassDeclaration); | |
549 targetElement = enclosingClass != null ? | |
550 enclosingClass.element : | |
551 null; | |
552 argument = nameNode; | |
553 } | |
554 } | |
555 // should be argument of some invocation | |
556 ParameterElement parameterElement = argument.bestParameterElement; | |
557 if (parameterElement == null) { | |
558 return; | |
559 } | |
560 // should be parameter of function type | |
561 DartType parameterType = parameterElement.type; | |
562 if (parameterType is! FunctionType) { | |
563 return; | |
564 } | |
565 FunctionType functionType = parameterType as FunctionType; | |
566 // add proposal | |
567 if (targetElement != null) { | |
568 _addProposal_createFunction_method(targetElement, functionType); | |
569 } else { | |
570 _addProposal_createFunction_function(functionType); | |
571 } | |
572 } | |
573 } | |
574 | |
575 void | |
576 _addFix_createMissingOverrides(List<ExecutableElement> missingOverrides) { | |
577 // sort by name | |
578 missingOverrides.sort((Element firstElement, Element secondElement) { | |
579 return compareStrings( | |
580 firstElement.displayName, | |
581 secondElement.displayName); | |
582 }); | |
583 ClassDeclaration targetClass = node.parent as ClassDeclaration; | |
584 int insertOffset = targetClass.end - 1; | |
585 SourceBuilder sb = new SourceBuilder(file, insertOffset); | |
586 // add elements | |
587 bool isFirst = true; | |
588 for (ExecutableElement missingOverride in missingOverrides) { | |
589 if (!isFirst || !targetClass.members.isEmpty) { | |
590 sb.append(eol); | |
591 } | |
592 _addFix_createMissingOverrides_single(sb, targetClass, missingOverride); | |
593 isFirst = false; | |
594 } | |
595 // add proposal | |
596 exitPosition = new Position(file, insertOffset); | |
597 _insertBuilder(sb); | |
598 _addFix(FixKind.CREATE_MISSING_OVERRIDES, [missingOverrides.length]); | |
599 } | |
600 | |
601 void _addFix_createMissingOverrides_single(SourceBuilder sb, | |
602 ClassDeclaration targetClass, ExecutableElement missingOverride) { | |
603 // prepare environment | |
604 String prefix = utils.getIndent(1); | |
605 String prefix2 = utils.getIndent(2); | |
606 // may be property | |
607 ElementKind elementKind = missingOverride.kind; | |
608 bool isGetter = elementKind == ElementKind.GETTER; | |
609 bool isSetter = elementKind == ElementKind.SETTER; | |
610 bool isMethod = elementKind == ElementKind.METHOD; | |
611 bool isOperator = isMethod && (missingOverride as MethodElement).isOperator; | |
612 sb.append(prefix); | |
613 if (isGetter) { | |
614 sb.append('// TODO: implement ${missingOverride.displayName}'); | |
615 sb.append(eol); | |
616 sb.append(prefix); | |
617 } | |
618 // @override | |
619 { | |
620 sb.append('@override'); | |
621 sb.append(eol); | |
622 sb.append(prefix); | |
623 } | |
624 // return type | |
625 _appendType(sb, missingOverride.type.returnType); | |
626 if (isGetter) { | |
627 sb.append('get '); | |
628 } else if (isSetter) { | |
629 sb.append('set '); | |
630 } else if (isOperator) { | |
631 sb.append('operator '); | |
632 } | |
633 // name | |
634 sb.append(missingOverride.displayName); | |
635 // parameters + body | |
636 if (isGetter) { | |
637 sb.append(' => null;'); | |
638 } else { | |
639 List<ParameterElement> parameters = missingOverride.parameters; | |
640 _appendParameters(sb, parameters, _getDefaultValueMap(parameters)); | |
641 sb.append(' {'); | |
642 // TO-DO | |
643 sb.append(eol); | |
644 sb.append(prefix2); | |
645 sb.append('// TODO: implement ${missingOverride.displayName}'); | |
646 sb.append(eol); | |
647 // close method | |
648 sb.append(prefix); | |
649 sb.append('}'); | |
650 } | |
651 sb.append(eol); | |
652 } | |
653 | |
654 void _addFix_createNoSuchMethod() { | |
655 ClassDeclaration targetClass = node.parent as ClassDeclaration; | |
656 // prepare environment | |
657 String prefix = utils.getIndent(1); | |
658 int insertOffset = targetClass.end - 1; | |
659 // prepare source | |
660 SourceBuilder sb = new SourceBuilder(file, insertOffset); | |
661 { | |
662 // insert empty line before existing member | |
663 if (!targetClass.members.isEmpty) { | |
664 sb.append(eol); | |
665 } | |
666 // append method | |
667 sb.append(prefix); | |
668 sb.append( | |
669 "noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation)
;"); | |
670 sb.append(eol); | |
671 } | |
672 // done | |
673 _insertBuilder(sb); | |
674 exitPosition = new Position(file, insertOffset); | |
675 // add proposal | |
676 _addFix(FixKind.CREATE_NO_SUCH_METHOD, []); | |
677 } | |
678 | |
679 void _addFix_importLibrary(FixKind kind, String importPath) { | |
680 CompilationUnitElement libraryUnitElement = | |
681 unitLibraryElement.definingCompilationUnit; | |
682 CompilationUnit libraryUnit = libraryUnitElement.node; | |
683 // prepare new import location | |
684 int offset = 0; | |
685 String prefix; | |
686 String suffix; | |
687 { | |
688 // if no directives | |
689 prefix = ""; | |
690 suffix = eol; | |
691 CorrectionUtils libraryUtils = new CorrectionUtils(libraryUnit); | |
692 // after last directive in library | |
693 for (Directive directive in libraryUnit.directives) { | |
694 if (directive is LibraryDirective || directive is ImportDirective) { | |
695 offset = directive.end; | |
696 prefix = eol; | |
697 suffix = ""; | |
698 } | |
699 } | |
700 // if still beginning of file, skip shebang and line comments | |
701 if (offset == 0) { | |
702 CorrectionUtils_InsertDesc desc = libraryUtils.getInsertDescTop(); | |
703 offset = desc.offset; | |
704 prefix = desc.prefix; | |
705 suffix = "${desc.suffix}${eol}"; | |
706 } | |
707 } | |
708 // insert new import | |
709 String importSource = "${prefix}import '${importPath}';${suffix}"; | |
710 _addInsertEdit(offset, importSource); | |
711 // add proposal | |
712 _addFix(kind, [importPath], fixFile: libraryUnitElement.source.fullName); | |
713 } | |
714 | |
715 void _addFix_importLibrary_withElement(String name, ElementKind kind) { | |
716 // ignore if private | |
717 if (name.startsWith("_")) { | |
718 return; | |
719 } | |
720 | |
721 // may be there is an existing import, | |
722 // but it is with prefix and we don't use this prefix | |
723 for (ImportElement imp in unitLibraryElement.imports) { | |
724 // prepare element | |
725 LibraryElement libraryElement = imp.importedLibrary; | |
726 Element element = getExportedElement(libraryElement, name); | |
727 if (element == null) { | |
728 continue; | |
729 } | |
730 if (element is PropertyAccessorElement) { | |
731 element = (element as PropertyAccessorElement).variable; | |
732 } | |
733 if (element.kind != kind) { | |
734 continue; | |
735 } | |
736 // may be apply prefix | |
737 PrefixElement prefix = imp.prefix; | |
738 if (prefix != null) { | |
739 SourceRange range = rf.rangeStartLength(node, 0); | |
740 _addReplaceEdit(range, "${prefix.displayName}."); | |
741 _addFix( | |
742 FixKind.IMPORT_LIBRARY_PREFIX, | |
743 [libraryElement.displayName, prefix.displayName]); | |
744 continue; | |
745 } | |
746 // may be update "show" directive | |
747 List<NamespaceCombinator> combinators = imp.combinators; | |
748 if (combinators.length == 1 && combinators[0] is ShowElementCombinator) { | |
749 ShowElementCombinator showCombinator = | |
750 combinators[0] as ShowElementCombinator; | |
751 // prepare new set of names to show | |
752 Set<String> showNames = new SplayTreeSet<String>(); | |
753 showNames.addAll(showCombinator.shownNames); | |
754 showNames.add(name); | |
755 // prepare library name - unit name or 'dart:name' for SDK library | |
756 String libraryName = libraryElement.definingCompilationUnit.displayName; | |
757 if (libraryElement.isInSdk) { | |
758 libraryName = imp.uri; | |
759 } | |
760 // update library | |
761 String newShowCode = "show ${StringUtils.join(showNames, ", ")}"; | |
762 _addReplaceEdit(rf.rangeOffsetEnd(showCombinator), newShowCode); | |
763 _addFix( | |
764 FixKind.IMPORT_LIBRARY_SHOW, | |
765 [libraryName], | |
766 fixFile: unitLibraryFile); | |
767 // we support only one import without prefix | |
768 return; | |
769 } | |
770 } | |
771 // check SDK libraries | |
772 AnalysisContext context = unitLibraryElement.context; | |
773 { | |
774 DartSdk sdk = context.sourceFactory.dartSdk; | |
775 List<SdkLibrary> sdkLibraries = sdk.sdkLibraries; | |
776 for (SdkLibrary sdkLibrary in sdkLibraries) { | |
777 SourceFactory sdkSourceFactory = context.sourceFactory; | |
778 String libraryUri = 'dart:' + sdkLibrary.shortName; | |
779 Source librarySource = | |
780 sdkSourceFactory.resolveUri(unitSource, libraryUri); | |
781 // prepare LibraryElement | |
782 LibraryElement libraryElement = | |
783 context.getLibraryElement(librarySource); | |
784 if (libraryElement == null) { | |
785 continue; | |
786 } | |
787 // prepare exported Element | |
788 Element element = getExportedElement(libraryElement, name); | |
789 if (element == null) { | |
790 continue; | |
791 } | |
792 if (element is PropertyAccessorElement) { | |
793 element = (element as PropertyAccessorElement).variable; | |
794 } | |
795 if (element.kind != kind) { | |
796 continue; | |
797 } | |
798 // add import | |
799 _addFix_importLibrary(FixKind.IMPORT_LIBRARY_SDK, libraryUri); | |
800 } | |
801 } | |
802 // check project libraries | |
803 { | |
804 List<Source> librarySources = context.librarySources; | |
805 for (Source librarySource in librarySources) { | |
806 // we don't need SDK libraries here | |
807 if (librarySource.isInSystemLibrary) { | |
808 continue; | |
809 } | |
810 // prepare LibraryElement | |
811 LibraryElement libraryElement = | |
812 context.getLibraryElement(librarySource); | |
813 if (libraryElement == null) { | |
814 continue; | |
815 } | |
816 // prepare exported Element | |
817 Element element = getExportedElement(libraryElement, name); | |
818 if (element == null) { | |
819 continue; | |
820 } | |
821 if (element is PropertyAccessorElement) { | |
822 element = (element as PropertyAccessorElement).variable; | |
823 } | |
824 if (element.kind != kind) { | |
825 continue; | |
826 } | |
827 // prepare "library" file | |
828 String libraryFile = librarySource.fullName; | |
829 // may be "package:" URI | |
830 { | |
831 String libraryPackageUri = _findPackageUri(context, libraryFile); | |
832 if (libraryPackageUri != null) { | |
833 _addFix_importLibrary( | |
834 FixKind.IMPORT_LIBRARY_PROJECT, | |
835 libraryPackageUri); | |
836 continue; | |
837 } | |
838 } | |
839 // relative URI | |
840 String relativeFile = relative(libraryFile, from: unitLibraryFolder); | |
841 relativeFile = split(relativeFile).join('/'); | |
842 _addFix_importLibrary(FixKind.IMPORT_LIBRARY_PROJECT, relativeFile); | |
843 } | |
844 } | |
845 } | |
846 | |
847 void _addFix_importLibrary_withFunction() { | |
848 if (node is SimpleIdentifier && node.parent is MethodInvocation) { | |
849 MethodInvocation invocation = node.parent as MethodInvocation; | |
850 if (invocation.realTarget == null && invocation.methodName == node) { | |
851 String name = (node as SimpleIdentifier).name; | |
852 _addFix_importLibrary_withElement(name, ElementKind.FUNCTION); | |
853 } | |
854 } | |
855 } | |
856 | |
857 void _addFix_importLibrary_withTopLevelVariable() { | |
858 if (node is SimpleIdentifier) { | |
859 String name = (node as SimpleIdentifier).name; | |
860 _addFix_importLibrary_withElement(name, ElementKind.TOP_LEVEL_VARIABLE); | |
861 } | |
862 } | |
863 | |
864 void _addFix_importLibrary_withType() { | |
865 if (_mayBeTypeIdentifier(node)) { | |
866 String typeName = (node as SimpleIdentifier).name; | |
867 _addFix_importLibrary_withElement(typeName, ElementKind.CLASS); | |
868 } | |
869 } | |
870 | |
871 void _addFix_insertSemicolon() { | |
872 if (error.message.contains("';'")) { | |
873 int insertOffset = error.offset + error.length; | |
874 _addInsertEdit(insertOffset, ";"); | |
875 _addFix(FixKind.INSERT_SEMICOLON, []); | |
876 } | |
877 } | |
878 | |
879 void _addFix_isNotNull() { | |
880 if (coveredNode is IsExpression) { | |
881 IsExpression isExpression = coveredNode as IsExpression; | |
882 _addReplaceEdit( | |
883 rf.rangeEndEnd(isExpression.expression, isExpression), | |
884 " != null"); | |
885 _addFix(FixKind.USE_NOT_EQ_NULL, []); | |
886 } | |
887 } | |
888 | |
889 void _addFix_isNull() { | |
890 if (coveredNode is IsExpression) { | |
891 IsExpression isExpression = coveredNode as IsExpression; | |
892 _addReplaceEdit( | |
893 rf.rangeEndEnd(isExpression.expression, isExpression), | |
894 " == null"); | |
895 _addFix(FixKind.USE_EQ_EQ_NULL, []); | |
896 } | |
897 } | |
898 | |
899 void _addFix_makeEnclosingClassAbstract() { | |
900 ClassDeclaration enclosingClass = | |
901 node.getAncestor((node) => node is ClassDeclaration); | |
902 String className = enclosingClass.name.name; | |
903 _addInsertEdit(enclosingClass.classKeyword.offset, "abstract "); | |
904 _addFix(FixKind.MAKE_CLASS_ABSTRACT, [className]); | |
905 } | |
906 | |
907 void _addFix_removeParameters_inGetterDeclaration() { | |
908 if (node is SimpleIdentifier && node.parent is MethodDeclaration) { | |
909 MethodDeclaration method = node.parent as MethodDeclaration; | |
910 FunctionBody body = method.body; | |
911 if (method.name == node && body != null) { | |
912 _addReplaceEdit(rf.rangeEndStart(node, body), " "); | |
913 _addFix(FixKind.REMOVE_PARAMETERS_IN_GETTER_DECLARATION, []); | |
914 } | |
915 } | |
916 } | |
917 | |
918 void _addFix_removeParentheses_inGetterInvocation() { | |
919 if (node is SimpleIdentifier && node.parent is MethodInvocation) { | |
920 MethodInvocation invocation = node.parent as MethodInvocation; | |
921 if (invocation.methodName == node && invocation.target != null) { | |
922 _addRemoveEdit(rf.rangeEndEnd(node, invocation)); | |
923 _addFix(FixKind.REMOVE_PARENTHESIS_IN_GETTER_INVOCATION, []); | |
924 } | |
925 } | |
926 } | |
927 | |
928 void _addFix_removeUnnecessaryCast() { | |
929 if (coveredNode is! AsExpression) { | |
930 return; | |
931 } | |
932 AsExpression asExpression = coveredNode as AsExpression; | |
933 Expression expression = asExpression.expression; | |
934 int expressionPrecedence = getExpressionPrecedence(expression); | |
935 // remove 'as T' from 'e as T' | |
936 _addRemoveEdit(rf.rangeEndEnd(expression, asExpression)); | |
937 _removeEnclosingParentheses(asExpression, expressionPrecedence); | |
938 // done | |
939 _addFix(FixKind.REMOVE_UNNECASSARY_CAST, []); | |
940 } | |
941 | |
942 void _addFix_removeUnusedImport() { | |
943 // prepare ImportDirective | |
944 ImportDirective importDirective = | |
945 node.getAncestor((node) => node is ImportDirective); | |
946 if (importDirective == null) { | |
947 return; | |
948 } | |
949 // remove the whole line with import | |
950 _addRemoveEdit(utils.getLinesRange(rf.rangeNode(importDirective))); | |
951 // done | |
952 _addFix(FixKind.REMOVE_UNUSED_IMPORT, []); | |
953 } | |
954 | |
955 void _addFix_replaceWithConstInstanceCreation() { | |
956 if (coveredNode is InstanceCreationExpression) { | |
957 var instanceCreation = coveredNode as InstanceCreationExpression; | |
958 _addReplaceEdit(rf.rangeToken(instanceCreation.keyword), "const"); | |
959 _addFix(FixKind.USE_CONST, []); | |
960 } | |
961 } | |
962 | |
963 void _addFix_undefinedClass_useSimilar() { | |
964 if (_mayBeTypeIdentifier(node)) { | |
965 String name = (node as SimpleIdentifier).name; | |
966 _ClosestElementFinder finder = | |
967 new _ClosestElementFinder( | |
968 name, | |
969 (Element element) => element is ClassElement, | |
970 MAX_LEVENSHTEIN_DISTANCE); | |
971 // find closest element | |
972 { | |
973 // elements of this library | |
974 for (CompilationUnitElement unit in unitLibraryElement.units) { | |
975 finder._updateList(unit.types); | |
976 } | |
977 // elements from imports | |
978 for (ImportElement importElement in unitLibraryElement.imports) { | |
979 if (importElement.prefix == null) { | |
980 Map<String, Element> namespace = getImportNamespace(importElement); | |
981 finder._updateList(namespace.values); | |
982 } | |
983 } | |
984 } | |
985 // if we have close enough element, suggest to use it | |
986 if (finder._element != null) { | |
987 String closestName = finder._element.name; | |
988 _addReplaceEdit(rf.rangeNode(node), closestName); | |
989 // add proposal | |
990 if (closestName != null) { | |
991 _addFix(FixKind.CHANGE_TO, [closestName]); | |
992 } | |
993 } | |
994 } | |
995 } | |
996 | |
997 void _addFix_undefinedFunction_create() { | |
998 // should be the name of the invocation | |
999 if (node is SimpleIdentifier && node.parent is MethodInvocation) { | |
1000 } else { | |
1001 return; | |
1002 } | |
1003 String name = (node as SimpleIdentifier).name; | |
1004 MethodInvocation invocation = node.parent as MethodInvocation; | |
1005 // function invocation has no target | |
1006 Expression target = invocation.realTarget; | |
1007 if (target != null) { | |
1008 return; | |
1009 } | |
1010 // prepare environment | |
1011 int insertOffset; | |
1012 String sourcePrefix; | |
1013 AstNode enclosingMember = | |
1014 node.getAncestor((node) => node is CompilationUnitMember); | |
1015 insertOffset = enclosingMember.end; | |
1016 sourcePrefix = "${eol}${eol}"; | |
1017 // build method source | |
1018 SourceBuilder sb = new SourceBuilder(file, insertOffset); | |
1019 { | |
1020 sb.append(sourcePrefix); | |
1021 // append return type | |
1022 { | |
1023 DartType type = _inferReturnType(invocation); | |
1024 _appendType(sb, type, 'RETURN_TYPE'); | |
1025 } | |
1026 // append name | |
1027 { | |
1028 sb.startPosition("NAME"); | |
1029 sb.append(name); | |
1030 sb.endPosition(); | |
1031 } | |
1032 _addFix_undefinedMethod_create_parameters(sb, invocation.argumentList); | |
1033 sb.append(") {${eol}}"); | |
1034 } | |
1035 // insert source | |
1036 _insertBuilder(sb); | |
1037 _addLinkedPosition3('NAME', sb, rf.rangeNode(node)); | |
1038 // add proposal | |
1039 _addFix(FixKind.CREATE_FUNCTION, [name]); | |
1040 } | |
1041 | |
1042 void _addFix_undefinedFunction_useSimilar() { | |
1043 if (node is SimpleIdentifier) { | |
1044 String name = (node as SimpleIdentifier).name; | |
1045 _ClosestElementFinder finder = | |
1046 new _ClosestElementFinder( | |
1047 name, | |
1048 (Element element) => element is FunctionElement, | |
1049 MAX_LEVENSHTEIN_DISTANCE); | |
1050 // this library | |
1051 for (CompilationUnitElement unit in unitLibraryElement.units) { | |
1052 finder._updateList(unit.functions); | |
1053 } | |
1054 // imports | |
1055 for (ImportElement importElement in unitLibraryElement.imports) { | |
1056 if (importElement.prefix == null) { | |
1057 Map<String, Element> namespace = getImportNamespace(importElement); | |
1058 finder._updateList(namespace.values); | |
1059 } | |
1060 } | |
1061 // if we have close enough element, suggest to use it | |
1062 if (finder._element != null) { | |
1063 String closestName = finder._element.name; | |
1064 _addReplaceEdit(rf.rangeNode(node), closestName); | |
1065 _addFix(FixKind.CHANGE_TO, [closestName]); | |
1066 } | |
1067 } | |
1068 } | |
1069 | |
1070 void _addFix_undefinedMethod_create() { | |
1071 if (node is SimpleIdentifier && node.parent is MethodInvocation) { | |
1072 String name = (node as SimpleIdentifier).name; | |
1073 MethodInvocation invocation = node.parent as MethodInvocation; | |
1074 // prepare environment | |
1075 Source targetSource; | |
1076 String prefix; | |
1077 int insertOffset; | |
1078 String sourcePrefix; | |
1079 String sourceSuffix; | |
1080 bool staticModifier = false; | |
1081 Expression target = invocation.realTarget; | |
1082 if (target == null) { | |
1083 targetSource = source; | |
1084 ClassMember enclosingMember = | |
1085 node.getAncestor((node) => node is ClassMember); | |
1086 staticModifier = _inStaticContext(); | |
1087 prefix = utils.getNodePrefix(enclosingMember); | |
1088 insertOffset = enclosingMember.end; | |
1089 sourcePrefix = "${eol}${eol}"; | |
1090 sourceSuffix = ""; | |
1091 } else { | |
1092 // prepare target interface type | |
1093 DartType targetType = target.bestType; | |
1094 if (targetType is! InterfaceType) { | |
1095 return; | |
1096 } | |
1097 ClassElement targetElement = targetType.element as ClassElement; | |
1098 targetSource = targetElement.source; | |
1099 // may be static | |
1100 if (target is Identifier) { | |
1101 staticModifier = target.bestElement.kind == ElementKind.CLASS; | |
1102 } | |
1103 // prepare insert offset | |
1104 ClassDeclaration targetClass = targetElement.node; | |
1105 prefix = " "; | |
1106 insertOffset = targetClass.end - 1; | |
1107 if (targetClass.members.isEmpty) { | |
1108 sourcePrefix = ""; | |
1109 } else { | |
1110 sourcePrefix = eol; | |
1111 } | |
1112 sourceSuffix = eol; | |
1113 } | |
1114 String targetFile = targetSource.fullName; | |
1115 // build method source | |
1116 SourceBuilder sb = new SourceBuilder(targetFile, insertOffset); | |
1117 { | |
1118 sb.append(sourcePrefix); | |
1119 sb.append(prefix); | |
1120 // maybe "static" | |
1121 if (staticModifier) { | |
1122 sb.append("static "); | |
1123 } | |
1124 // append return type | |
1125 _appendType(sb, _inferReturnType(invocation), 'RETURN_TYPE'); | |
1126 // append name | |
1127 { | |
1128 sb.startPosition("NAME"); | |
1129 sb.append(name); | |
1130 sb.endPosition(); | |
1131 } | |
1132 _addFix_undefinedMethod_create_parameters(sb, invocation.argumentList); | |
1133 sb.append(") {${eol}${prefix}}"); | |
1134 sb.append(sourceSuffix); | |
1135 } | |
1136 // insert source | |
1137 _insertBuilder(sb); | |
1138 // add linked positions | |
1139 if (targetSource == source) { | |
1140 _addLinkedPosition3('NAME', sb, rf.rangeNode(node)); | |
1141 } | |
1142 // add proposal | |
1143 _addFix(FixKind.CREATE_METHOD, [name], fixFile: targetFile); | |
1144 } | |
1145 } | |
1146 | |
1147 void _addFix_undefinedMethod_create_parameters(SourceBuilder sb, | |
1148 ArgumentList argumentList) { | |
1149 // append parameters | |
1150 sb.append("("); | |
1151 Set<String> excluded = new Set(); | |
1152 List<Expression> arguments = argumentList.arguments; | |
1153 for (int i = 0; i < arguments.length; i++) { | |
1154 Expression argument = arguments[i]; | |
1155 // append separator | |
1156 if (i != 0) { | |
1157 sb.append(", "); | |
1158 } | |
1159 // append type name | |
1160 DartType type = argument.bestType; | |
1161 String typeSource = utils.getTypeSource(type); | |
1162 { | |
1163 sb.startPosition("TYPE${i}"); | |
1164 sb.append(typeSource); | |
1165 _addSuperTypeProposals(sb, new Set(), type); | |
1166 sb.endPosition(); | |
1167 } | |
1168 sb.append(" "); | |
1169 // append parameter name | |
1170 { | |
1171 List<String> suggestions = | |
1172 _getArgumentNameSuggestions(excluded, type, argument, i); | |
1173 String favorite = suggestions[0]; | |
1174 excluded.add(favorite); | |
1175 sb.startPosition("ARG${i}"); | |
1176 sb.append(favorite); | |
1177 sb.addSuggestions(LinkedEditSuggestionKind.PARAMETER, suggestions); | |
1178 sb.endPosition(); | |
1179 } | |
1180 } | |
1181 } | |
1182 | |
1183 void _addFix_undefinedMethod_useSimilar() { | |
1184 if (node is SimpleIdentifier && node.parent is MethodInvocation) { | |
1185 MethodInvocation invocation = node.parent as MethodInvocation; | |
1186 String name = (node as SimpleIdentifier).name; | |
1187 _ClosestElementFinder finder = | |
1188 new _ClosestElementFinder( | |
1189 name, | |
1190 (Element element) => element is MethodElement && !element.isOperat
or, | |
1191 MAX_LEVENSHTEIN_DISTANCE); | |
1192 // unqualified invocation | |
1193 Expression target = invocation.realTarget; | |
1194 if (target == null) { | |
1195 ClassDeclaration clazz = | |
1196 invocation.getAncestor((node) => node is ClassDeclaration); | |
1197 if (clazz != null) { | |
1198 ClassElement classElement = clazz.element; | |
1199 _updateFinderWithClassMembers(finder, classElement); | |
1200 } | |
1201 } else { | |
1202 DartType type = target.bestType; | |
1203 if (type is InterfaceType) { | |
1204 ClassElement classElement = type.element; | |
1205 _updateFinderWithClassMembers(finder, classElement); | |
1206 } | |
1207 } | |
1208 // if we have close enough element, suggest to use it | |
1209 if (finder._element != null) { | |
1210 String closestName = finder._element.name; | |
1211 _addReplaceEdit(rf.rangeNode(node), closestName); | |
1212 _addFix(FixKind.CHANGE_TO, [closestName]); | |
1213 } | |
1214 } | |
1215 } | |
1216 | |
1217 void _addFix_useEffectiveIntegerDivision() { | |
1218 for (AstNode n = node; n != null; n = n.parent) { | |
1219 if (n is MethodInvocation && | |
1220 n.offset == errorOffset && | |
1221 n.length == errorLength) { | |
1222 MethodInvocation invocation = n as MethodInvocation; | |
1223 Expression target = invocation.target; | |
1224 while (target is ParenthesizedExpression) { | |
1225 target = (target as ParenthesizedExpression).expression; | |
1226 } | |
1227 // replace "/" with "~/" | |
1228 BinaryExpression binary = target as BinaryExpression; | |
1229 _addReplaceEdit(rf.rangeToken(binary.operator), "~/"); | |
1230 // remove everything before and after | |
1231 _addRemoveEdit(rf.rangeStartStart(invocation, binary.leftOperand)); | |
1232 _addRemoveEdit(rf.rangeEndEnd(binary.rightOperand, invocation)); | |
1233 // add proposal | |
1234 _addFix(FixKind.USE_EFFECTIVE_INTEGER_DIVISION, []); | |
1235 // done | |
1236 break; | |
1237 } | |
1238 } | |
1239 } | |
1240 | |
1241 void _addFix_useStaticAccess_method() { | |
1242 if (node is SimpleIdentifier && node.parent is MethodInvocation) { | |
1243 MethodInvocation invocation = node.parent as MethodInvocation; | |
1244 if (invocation.methodName == node) { | |
1245 Expression target = invocation.target; | |
1246 String targetType = utils.getExpressionTypeSource(target); | |
1247 // replace "target" with class name | |
1248 SourceRange range = rf.rangeNode(target); | |
1249 _addReplaceEdit(range, targetType); | |
1250 // add proposal | |
1251 _addFix(FixKind.CHANGE_TO_STATIC_ACCESS, [targetType]); | |
1252 } | |
1253 } | |
1254 } | |
1255 | |
1256 void _addFix_useStaticAccess_property() { | |
1257 if (node is SimpleIdentifier) { | |
1258 if (node.parent is PrefixedIdentifier) { | |
1259 PrefixedIdentifier prefixed = node.parent as PrefixedIdentifier; | |
1260 if (prefixed.identifier == node) { | |
1261 Expression target = prefixed.prefix; | |
1262 String targetType = utils.getExpressionTypeSource(target); | |
1263 // replace "target" with class name | |
1264 SourceRange range = rf.rangeNode(target); | |
1265 _addReplaceEdit(range, targetType); | |
1266 // add proposal | |
1267 _addFix(FixKind.CHANGE_TO_STATIC_ACCESS, [targetType]); | |
1268 } | |
1269 } | |
1270 } | |
1271 } | |
1272 | |
1273 /** | |
1274 * Adds a new [Edit] to [edits]. | |
1275 */ | |
1276 void _addInsertEdit(int offset, String text) { | |
1277 Edit edit = new Edit(offset, 0, text); | |
1278 edits.add(edit); | |
1279 } | |
1280 | |
1281 /** | |
1282 * Adds a single linked position to [groupId]. | |
1283 */ | |
1284 void _addLinkedPosition(String groupId, SourceRange range) { | |
1285 Position position = new Position(file, range.offset); | |
1286 LinkedEditGroup group = _getLinkedPosition(groupId); | |
1287 group.addPosition(position, range.length); | |
1288 } | |
1289 | |
1290 /** | |
1291 * Adds a single linked position to [groupId]. | |
1292 */ | |
1293 void _addLinkedPosition3(String groupId, SourceBuilder sb, | |
1294 SourceRange range) { | |
1295 if (sb.offset < range.offset) { | |
1296 int delta = sb.length; | |
1297 range = range.getTranslated(delta); | |
1298 } | |
1299 _addLinkedPosition(groupId, range); | |
1300 } | |
1301 | |
1302 /** | |
1303 * Prepares proposal for creating function corresponding to the given [Functio
nType]. | |
1304 */ | |
1305 void _addProposal_createFunction(FunctionType functionType, String name, | |
1306 Source targetSource, int insertOffset, bool isStatic, String prefix, | |
1307 String sourcePrefix, String sourceSuffix) { | |
1308 // build method source | |
1309 String targetFile = targetSource.fullName; | |
1310 SourceBuilder sb = new SourceBuilder(targetFile, insertOffset); | |
1311 { | |
1312 sb.append(sourcePrefix); | |
1313 sb.append(prefix); | |
1314 // may be static | |
1315 if (isStatic) { | |
1316 sb.append("static "); | |
1317 } | |
1318 // append return type | |
1319 _appendType(sb, functionType.returnType, 'RETURN_TYPE'); | |
1320 // append name | |
1321 { | |
1322 sb.startPosition("NAME"); | |
1323 sb.append(name); | |
1324 sb.endPosition(); | |
1325 } | |
1326 // append parameters | |
1327 sb.append("("); | |
1328 List<ParameterElement> parameters = functionType.parameters; | |
1329 for (int i = 0; i < parameters.length; i++) { | |
1330 ParameterElement parameter = parameters[i]; | |
1331 // append separator | |
1332 if (i != 0) { | |
1333 sb.append(", "); | |
1334 } | |
1335 // append type name | |
1336 DartType type = parameter.type; | |
1337 if (!type.isDynamic) { | |
1338 String typeSource = utils.getTypeSource(type); | |
1339 { | |
1340 sb.startPosition("TYPE${i}"); | |
1341 sb.append(typeSource); | |
1342 _addSuperTypeProposals(sb, new Set(), type); | |
1343 sb.endPosition(); | |
1344 } | |
1345 sb.append(" "); | |
1346 } | |
1347 // append parameter name | |
1348 { | |
1349 sb.startPosition("ARG${i}"); | |
1350 sb.append(parameter.displayName); | |
1351 sb.endPosition(); | |
1352 } | |
1353 } | |
1354 sb.append(")"); | |
1355 // close method | |
1356 sb.append(" {${eol}${prefix}}"); | |
1357 sb.append(sourceSuffix); | |
1358 } | |
1359 // insert source | |
1360 _insertBuilder(sb); | |
1361 // add linked positions | |
1362 if (targetSource == source) { | |
1363 _addLinkedPosition3("NAME", sb, rf.rangeNode(node)); | |
1364 } | |
1365 } | |
1366 | |
1367 /** | |
1368 * Adds proposal for creating method corresponding to the given [FunctionType]
in the given | |
1369 * [ClassElement]. | |
1370 */ | |
1371 void _addProposal_createFunction_function(FunctionType functionType) { | |
1372 String name = (node as SimpleIdentifier).name; | |
1373 // prepare environment | |
1374 int insertOffset = unit.end; | |
1375 // prepare prefix | |
1376 String prefix = ""; | |
1377 String sourcePrefix = "${eol}"; | |
1378 String sourceSuffix = eol; | |
1379 _addProposal_createFunction( | |
1380 functionType, | |
1381 name, | |
1382 source, | |
1383 insertOffset, | |
1384 false, | |
1385 prefix, | |
1386 sourcePrefix, | |
1387 sourceSuffix); | |
1388 // add proposal | |
1389 _addFix(FixKind.CREATE_FUNCTION, [name], fixFile: file); | |
1390 } | |
1391 | |
1392 /** | |
1393 * Adds proposal for creating method corresponding to the given [FunctionType]
in the given | |
1394 * [ClassElement]. | |
1395 */ | |
1396 void _addProposal_createFunction_method(ClassElement targetClassElement, | |
1397 FunctionType functionType) { | |
1398 String name = (node as SimpleIdentifier).name; | |
1399 // prepare environment | |
1400 Source targetSource = targetClassElement.source; | |
1401 String targetFile = targetSource.fullName; | |
1402 // prepare insert offset | |
1403 ClassDeclaration targetClassNode = targetClassElement.node; | |
1404 int insertOffset = targetClassNode.end - 1; | |
1405 // prepare prefix | |
1406 String prefix = " "; | |
1407 String sourcePrefix; | |
1408 if (targetClassNode.members.isEmpty) { | |
1409 sourcePrefix = ""; | |
1410 } else { | |
1411 sourcePrefix = eol; | |
1412 } | |
1413 String sourceSuffix = eol; | |
1414 _addProposal_createFunction( | |
1415 functionType, | |
1416 name, | |
1417 targetSource, | |
1418 insertOffset, | |
1419 _inStaticContext(), | |
1420 prefix, | |
1421 sourcePrefix, | |
1422 sourceSuffix); | |
1423 // add proposal | |
1424 _addFix(FixKind.CREATE_METHOD, [name], fixFile: targetFile); | |
1425 } | |
1426 | |
1427 /** | |
1428 * Adds a new [Edit] to [edits]. | |
1429 */ | |
1430 void _addRemoveEdit(SourceRange range) { | |
1431 _addReplaceEdit(range, ''); | |
1432 } | |
1433 | |
1434 /** | |
1435 * Adds a new [Edit] to [edits]. | |
1436 */ | |
1437 void _addReplaceEdit(SourceRange range, String text) { | |
1438 Edit edit = new Edit(range.offset, range.length, text); | |
1439 edits.add(edit); | |
1440 } | |
1441 | |
1442 void _appendParameterSource(SourceBuilder sb, DartType type, String name) { | |
1443 String parameterSource = utils.getParameterSource(type, name); | |
1444 sb.append(parameterSource); | |
1445 } | |
1446 | |
1447 void _appendParameters(SourceBuilder sb, List<ParameterElement> parameters, | |
1448 Map<ParameterElement, String> defaultValueMap) { | |
1449 sb.append("("); | |
1450 bool firstParameter = true; | |
1451 bool sawNamed = false; | |
1452 bool sawPositional = false; | |
1453 for (ParameterElement parameter in parameters) { | |
1454 if (!firstParameter) { | |
1455 sb.append(", "); | |
1456 } else { | |
1457 firstParameter = false; | |
1458 } | |
1459 // may be optional | |
1460 ParameterKind parameterKind = parameter.parameterKind; | |
1461 if (parameterKind == ParameterKind.NAMED) { | |
1462 if (!sawNamed) { | |
1463 sb.append("{"); | |
1464 sawNamed = true; | |
1465 } | |
1466 } | |
1467 if (parameterKind == ParameterKind.POSITIONAL) { | |
1468 if (!sawPositional) { | |
1469 sb.append("["); | |
1470 sawPositional = true; | |
1471 } | |
1472 } | |
1473 // parameter | |
1474 _appendParameterSource(sb, parameter.type, parameter.name); | |
1475 // default value | |
1476 if (defaultValueMap != null) { | |
1477 String defaultSource = defaultValueMap[parameter]; | |
1478 if (defaultSource != null) { | |
1479 if (sawPositional) { | |
1480 sb.append(" = "); | |
1481 } else { | |
1482 sb.append(": "); | |
1483 } | |
1484 sb.append(defaultSource); | |
1485 } | |
1486 } | |
1487 } | |
1488 // close parameters | |
1489 if (sawNamed) { | |
1490 sb.append("}"); | |
1491 } | |
1492 if (sawPositional) { | |
1493 sb.append("]"); | |
1494 } | |
1495 sb.append(")"); | |
1496 } | |
1497 | |
1498 void _appendType(SourceBuilder sb, DartType type, [String groupId]) { | |
1499 if (type != null && !type.isDynamic) { | |
1500 String typeSource = utils.getTypeSource(type); | |
1501 if (groupId != null) { | |
1502 sb.startPosition(groupId); | |
1503 sb.append(typeSource); | |
1504 sb.endPosition(); | |
1505 } else { | |
1506 sb.append(typeSource); | |
1507 } | |
1508 sb.append(' '); | |
1509 } | |
1510 } | |
1511 | |
1512 /** | |
1513 * @return the string to display as the name of the given constructor in a pro
posal name. | |
1514 */ | |
1515 String _getConstructorProposalName(ConstructorElement constructor) { | |
1516 SourceBuilder proposalNameBuffer = new SourceBuilder.buffer(); | |
1517 proposalNameBuffer.append("super"); | |
1518 // may be named | |
1519 String constructorName = constructor.displayName; | |
1520 if (!constructorName.isEmpty) { | |
1521 proposalNameBuffer.append("."); | |
1522 proposalNameBuffer.append(constructorName); | |
1523 } | |
1524 // parameters | |
1525 _appendParameters(proposalNameBuffer, constructor.parameters, null); | |
1526 // done | |
1527 return proposalNameBuffer.toString(); | |
1528 } | |
1529 | |
1530 /** | |
1531 * Returns the [Type] with given name from the `dart:core` library. | |
1532 */ | |
1533 DartType _getCoreType(String name) { | |
1534 List<LibraryElement> libraries = unitLibraryElement.importedLibraries; | |
1535 for (LibraryElement library in libraries) { | |
1536 if (library.isDartCore) { | |
1537 ClassElement classElement = library.getType(name); | |
1538 if (classElement != null) { | |
1539 return classElement.type; | |
1540 } | |
1541 return null; | |
1542 } | |
1543 } | |
1544 return null; | |
1545 } | |
1546 | |
1547 Map<ParameterElement, String> | |
1548 _getDefaultValueMap(List<ParameterElement> parameters) { | |
1549 Map<ParameterElement, String> defaultSourceMap = {}; | |
1550 Map<Source, String> sourceContentMap = {}; | |
1551 for (ParameterElement parameter in parameters) { | |
1552 SourceRange valueRange = parameter.defaultValueRange; | |
1553 if (valueRange != null) { | |
1554 Source source = parameter.source; | |
1555 String sourceContent = sourceContentMap[source]; | |
1556 if (sourceContent == null) { | |
1557 sourceContent = getSourceContent(parameter.context, source); | |
1558 sourceContentMap[source] = sourceContent; | |
1559 } | |
1560 String valueSource = | |
1561 sourceContent.substring(valueRange.offset, valueRange.end); | |
1562 defaultSourceMap[parameter] = valueSource; | |
1563 } | |
1564 } | |
1565 return defaultSourceMap; | |
1566 } | |
1567 | |
1568 /** | |
1569 * Returns an existing or just added [LinkedEditGroup] with [groupId]. | |
1570 */ | |
1571 LinkedEditGroup _getLinkedPosition(String groupId) { | |
1572 LinkedEditGroup group = linkedPositionGroups[groupId]; | |
1573 if (group == null) { | |
1574 group = new LinkedEditGroup(groupId); | |
1575 linkedPositionGroups[groupId] = group; | |
1576 } | |
1577 return group; | |
1578 } | |
1579 | |
1580 /** | |
1581 * Returns `true` if [node] is in static context. | |
1582 */ | |
1583 bool _inStaticContext() { | |
1584 // constructor initializer cannot reference "this" | |
1585 if (node.getAncestor((node) => node is ConstructorInitializer) != null) { | |
1586 return true; | |
1587 } | |
1588 // field initializer cannot reference "this" | |
1589 if (node.getAncestor((node) => node is FieldDeclaration) != null) { | |
1590 return true; | |
1591 } | |
1592 // static method | |
1593 MethodDeclaration method = node.getAncestor((node) { | |
1594 return node is MethodDeclaration; | |
1595 }); | |
1596 return method != null && method.isStatic; | |
1597 } | |
1598 | |
1599 /** | |
1600 * Returns a possible return [Type], may be `null` if cannot be inferred. | |
1601 */ | |
1602 DartType _inferReturnType(MethodInvocation invocation) { | |
1603 AstNode parent = invocation.parent; | |
1604 // myFunction(); | |
1605 if (parent is ExpressionStatement) { | |
1606 return VoidTypeImpl.instance; | |
1607 } | |
1608 // return myFunction(); | |
1609 if (parent is ReturnStatement) { | |
1610 ExecutableElement executable = getEnclosingExecutableElement(invocation); | |
1611 return executable != null ? executable.returnType : null; | |
1612 } | |
1613 // int v = myFunction(); | |
1614 if (parent is VariableDeclaration) { | |
1615 VariableDeclaration variableDeclaration = parent; | |
1616 if (variableDeclaration.initializer == invocation) { | |
1617 VariableElement variableElement = variableDeclaration.element; | |
1618 if (variableElement != null) { | |
1619 return variableElement.type; | |
1620 } | |
1621 } | |
1622 } | |
1623 // v = myFunction(); | |
1624 if (parent is AssignmentExpression) { | |
1625 AssignmentExpression assignment = parent; | |
1626 if (assignment.rightHandSide == invocation) { | |
1627 if (assignment.operator.type == TokenType.EQ) { | |
1628 // v = myFunction(); | |
1629 Expression lhs = assignment.leftHandSide; | |
1630 if (lhs != null) { | |
1631 return lhs.bestType; | |
1632 } | |
1633 } else { | |
1634 // v += myFunction(); | |
1635 MethodElement method = assignment.bestElement; | |
1636 if (method != null) { | |
1637 List<ParameterElement> parameters = method.parameters; | |
1638 if (parameters.length == 1) { | |
1639 return parameters[0].type; | |
1640 } | |
1641 } | |
1642 } | |
1643 } | |
1644 } | |
1645 // v + myFunction(); | |
1646 if (parent is BinaryExpression) { | |
1647 BinaryExpression binary = parent; | |
1648 MethodElement method = binary.bestElement; | |
1649 if (method != null) { | |
1650 if (binary.rightOperand == invocation) { | |
1651 List<ParameterElement> parameters = method.parameters; | |
1652 return parameters.length == 1 ? parameters[0].type : null; | |
1653 } | |
1654 } | |
1655 } | |
1656 // foo( myFunction() ); | |
1657 if (parent is ArgumentList) { | |
1658 ParameterElement parameter = invocation.bestParameterElement; | |
1659 return parameter != null ? parameter.type : null; | |
1660 } | |
1661 // bool | |
1662 { | |
1663 // assert( myFunction() ); | |
1664 if (parent is AssertStatement) { | |
1665 AssertStatement statement = parent; | |
1666 if (statement.condition == invocation) { | |
1667 return coreTypeBool; | |
1668 } | |
1669 } | |
1670 // if ( myFunction() ) {} | |
1671 if (parent is IfStatement) { | |
1672 IfStatement statement = parent; | |
1673 if (statement.condition == invocation) { | |
1674 return coreTypeBool; | |
1675 } | |
1676 } | |
1677 // while ( myFunction() ) {} | |
1678 if (parent is WhileStatement) { | |
1679 WhileStatement statement = parent; | |
1680 if (statement.condition == invocation) { | |
1681 return coreTypeBool; | |
1682 } | |
1683 } | |
1684 // do {} while ( myFunction() ); | |
1685 if (parent is DoStatement) { | |
1686 DoStatement statement = parent; | |
1687 if (statement.condition == invocation) { | |
1688 return coreTypeBool; | |
1689 } | |
1690 } | |
1691 // !myFunction() | |
1692 if (parent is PrefixExpression) { | |
1693 PrefixExpression prefixExpression = parent; | |
1694 if (prefixExpression.operator.type == TokenType.BANG) { | |
1695 return coreTypeBool; | |
1696 } | |
1697 } | |
1698 // binary expression '&&' or '||' | |
1699 if (parent is BinaryExpression) { | |
1700 BinaryExpression binaryExpression = parent; | |
1701 TokenType operatorType = binaryExpression.operator.type; | |
1702 if (operatorType == TokenType.AMPERSAND_AMPERSAND || | |
1703 operatorType == TokenType.BAR_BAR) { | |
1704 return coreTypeBool; | |
1705 } | |
1706 } | |
1707 } | |
1708 // we don't know | |
1709 return null; | |
1710 } | |
1711 | |
1712 /** | |
1713 * Inserts the given [SourceBuilder] at its offset. | |
1714 */ | |
1715 void _insertBuilder(SourceBuilder builder) { | |
1716 String text = builder.toString(); | |
1717 _addInsertEdit(builder.offset, text); | |
1718 // add linked positions | |
1719 builder.linkedPositionGroups.forEach((LinkedEditGroup group) { | |
1720 LinkedEditGroup fixGroup = _getLinkedPosition(group.id); | |
1721 group.positions.forEach((Position position) { | |
1722 fixGroup.addPosition(position, group.length); | |
1723 }); | |
1724 group.suggestions.forEach((LinkedEditSuggestion suggestion) { | |
1725 fixGroup.addSuggestion(suggestion); | |
1726 }); | |
1727 }); | |
1728 } | |
1729 | |
1730 // void _addLinkedPositionProposal(String group, | |
1731 // LinkedPositionProposal proposal) { | |
1732 // List<LinkedPositionProposal> nodeProposals = linkedPositionProposals[group
]; | |
1733 // if (nodeProposals == null) { | |
1734 // nodeProposals = <LinkedPositionProposal>[]; | |
1735 // linkedPositionProposals[group] = nodeProposals; | |
1736 // } | |
1737 // nodeProposals.add(proposal); | |
1738 // } | |
1739 | |
1740 // /** | |
1741 // * Returns `true` if the given [ClassMember] is a part of a static method or | |
1742 // * a field initializer. | |
1743 // */ | |
1744 // bool _inStaticMemberContext2(ClassMember member) { | |
1745 // if (member is MethodDeclaration) { | |
1746 // return member.isStatic; | |
1747 // } | |
1748 // // field initializer cannot reference "this" | |
1749 // if (member is FieldDeclaration) { | |
1750 // return true; | |
1751 // } | |
1752 // return false; | |
1753 // } | |
1754 | |
1755 _ConstructorLocation | |
1756 _prepareNewConstructorLocation(ClassDeclaration classDeclaration) { | |
1757 List<ClassMember> members = classDeclaration.members; | |
1758 // find the last field/constructor | |
1759 ClassMember lastFieldOrConstructor = null; | |
1760 for (ClassMember member in members) { | |
1761 if (member is FieldDeclaration || member is ConstructorDeclaration) { | |
1762 lastFieldOrConstructor = member; | |
1763 } else { | |
1764 break; | |
1765 } | |
1766 } | |
1767 // after the field/constructor | |
1768 if (lastFieldOrConstructor != null) { | |
1769 return new _ConstructorLocation( | |
1770 "${eol}${eol}", | |
1771 lastFieldOrConstructor.end, | |
1772 ""); | |
1773 } | |
1774 // at the beginning of the class | |
1775 String suffix = members.isEmpty ? "" : eol; | |
1776 return new _ConstructorLocation( | |
1777 eol, | |
1778 classDeclaration.leftBracket.end, | |
1779 suffix); | |
1780 } | |
1781 | |
1782 /** | |
1783 * Removes any [ParenthesizedExpression] enclosing [expr]. | |
1784 * | |
1785 * [exprPrecedence] - the effective precedence of [expr]. | |
1786 */ | |
1787 void _removeEnclosingParentheses(Expression expr, int exprPrecedence) { | |
1788 while (expr.parent is ParenthesizedExpression) { | |
1789 ParenthesizedExpression parenthesized = | |
1790 expr.parent as ParenthesizedExpression; | |
1791 if (getExpressionParentPrecedence(parenthesized) > exprPrecedence) { | |
1792 break; | |
1793 } | |
1794 _addRemoveEdit(rf.rangeToken(parenthesized.leftParenthesis)); | |
1795 _addRemoveEdit(rf.rangeToken(parenthesized.rightParenthesis)); | |
1796 expr = parenthesized; | |
1797 } | |
1798 } | |
1799 | |
1800 void _updateFinderWithClassMembers(_ClosestElementFinder finder, | |
1801 ClassElement clazz) { | |
1802 if (clazz != null) { | |
1803 List<Element> members = getMembers(clazz); | |
1804 finder._updateList(members); | |
1805 } | |
1806 } | |
1807 | |
1808 static void _addSuperTypeProposals(SourceBuilder sb, | |
1809 Set<DartType> alreadyAdded, DartType type) { | |
1810 if (type != null && | |
1811 !alreadyAdded.contains(type) && | |
1812 type.element is ClassElement) { | |
1813 alreadyAdded.add(type); | |
1814 ClassElement element = type.element as ClassElement; | |
1815 sb.addSuggestion(LinkedEditSuggestionKind.TYPE, element.name); | |
1816 _addSuperTypeProposals(sb, alreadyAdded, element.supertype); | |
1817 for (InterfaceType interfaceType in element.interfaces) { | |
1818 _addSuperTypeProposals(sb, alreadyAdded, interfaceType); | |
1819 } | |
1820 } | |
1821 } | |
1822 | |
1823 /** | |
1824 * Attempts to convert the given absolute path into a "package" URI. | |
1825 * | |
1826 * [context] - the [AnalysisContext] to work in. | |
1827 * [path] - the absolute path, not `null`. | |
1828 * | |
1829 * Returns the "package" URI, may be `null`. | |
1830 */ | |
1831 static String _findPackageUri(AnalysisContext context, String path) { | |
1832 // Source fileSource = new FileBasedSource.con1(path); | |
1833 Source fileSource = new NonExistingSource(path, UriKind.FILE_URI); | |
1834 Uri uri = context.sourceFactory.restoreUri(fileSource); | |
1835 if (uri == null) { | |
1836 return null; | |
1837 } | |
1838 return uri.toString(); | |
1839 } | |
1840 | |
1841 /** | |
1842 * @return the suggestions for given [Type] and [DartExpression], not empty. | |
1843 */ | |
1844 static List<String> _getArgumentNameSuggestions(Set<String> excluded, | |
1845 DartType type, Expression expression, int index) { | |
1846 List<String> suggestions = | |
1847 getVariableNameSuggestionsForExpression(type, expression, excluded); | |
1848 if (suggestions.length != 0) { | |
1849 return suggestions; | |
1850 } | |
1851 return <String>["arg${index}"]; | |
1852 } | |
1853 | |
1854 /** | |
1855 * Returns `true` if [node] is a type name. | |
1856 */ | |
1857 static bool _mayBeTypeIdentifier(AstNode node) { | |
1858 if (node is SimpleIdentifier) { | |
1859 AstNode parent = node.parent; | |
1860 if (parent is TypeName) { | |
1861 return true; | |
1862 } | |
1863 if (parent is MethodInvocation) { | |
1864 return parent.realTarget == node; | |
1865 } | |
1866 if (parent is PrefixedIdentifier) { | |
1867 return parent.prefix == node; | |
1868 } | |
1869 } | |
1870 return false; | |
1871 } | |
1872 } | |
1873 | |
1874 /** | |
1875 * Helper for finding [Element] with name closest to the given. | |
1876 */ | |
1877 class _ClosestElementFinder { | |
1878 final String _targetName; | |
1879 final Predicate<Element> _predicate; | |
1880 | |
1881 Element _element = null; | |
1882 int _distance; | |
1883 | |
1884 _ClosestElementFinder(this._targetName, this._predicate, this._distance); | |
1885 | |
1886 void _update(Element element) { | |
1887 if (_predicate(element)) { | |
1888 int memberDistance = levenshtein(element.name, _targetName, _distance); | |
1889 if (memberDistance < _distance) { | |
1890 _element = element; | |
1891 _distance = memberDistance; | |
1892 } | |
1893 } | |
1894 } | |
1895 | |
1896 void _updateList(Iterable<Element> elements) { | |
1897 for (Element element in elements) { | |
1898 _update(element); | |
1899 } | |
1900 } | |
1901 } | |
1902 | |
1903 /** | |
1904 * Describes the location for a newly created [ConstructorDeclaration]. | |
1905 */ | |
1906 class _ConstructorLocation { | |
1907 final String _prefix; | |
1908 final int _offset; | |
1909 final String _suffix; | |
1910 | |
1911 _ConstructorLocation(this._prefix, this._offset, this._suffix); | |
1912 } | |
OLD | NEW |