OLD | NEW |
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'; | 5 import 'dart:async'; |
6 | 6 |
7 import 'package:analysis_server/protocol/protocol_generated.dart' | |
8 hide Element, ElementKind; | |
9 import 'package:analysis_server/src/services/correction/name_suggestion.dart'; | |
10 import 'package:analysis_server/src/services/correction/util.dart'; | |
11 import 'package:analyzer/dart/ast/ast.dart'; | 7 import 'package:analyzer/dart/ast/ast.dart'; |
12 import 'package:analyzer/dart/ast/token.dart'; | 8 import 'package:analyzer/dart/ast/token.dart'; |
13 import 'package:analyzer/dart/element/element.dart'; | 9 import 'package:analyzer/dart/element/element.dart'; |
14 import 'package:analyzer/dart/element/type.dart'; | 10 import 'package:analyzer/dart/element/type.dart'; |
15 import 'package:analyzer/src/dart/analysis/driver.dart'; | 11 import 'package:analyzer/src/dart/analysis/driver.dart'; |
16 import 'package:analyzer/src/dart/ast/utilities.dart'; | 12 import 'package:analyzer/src/dart/ast/utilities.dart'; |
17 import 'package:analyzer/src/generated/resolver.dart'; | 13 import 'package:analyzer/src/generated/resolver.dart'; |
18 import 'package:analyzer/src/generated/source.dart'; | 14 import 'package:analyzer/src/generated/source.dart'; |
19 import 'package:analyzer/src/generated/utilities_dart.dart'; | 15 import 'package:analyzer/src/generated/utilities_dart.dart'; |
| 16 import 'package:analyzer_plugin/protocol/protocol_generated.dart' |
| 17 hide Element, ElementKind; |
20 import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_core
.dart'; | 18 import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_core
.dart'; |
| 19 import 'package:analyzer_plugin/src/utilities/string_utilities.dart'; |
21 import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dar
t'; | 20 import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dar
t'; |
22 import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dar
t'; | 21 import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dar
t'; |
23 import 'package:analyzer_plugin/utilities/range_factory.dart'; | 22 import 'package:analyzer_plugin/utilities/range_factory.dart'; |
| 23 import 'package:charcode/ascii.dart'; |
| 24 import 'package:path/path.dart' as path; |
24 | 25 |
25 /** | 26 /** |
26 * A [ChangeBuilder] used to build changes in Dart files. | 27 * A [ChangeBuilder] used to build changes in Dart files. |
27 */ | 28 */ |
28 class DartChangeBuilderImpl extends ChangeBuilderImpl | 29 class DartChangeBuilderImpl extends ChangeBuilderImpl |
29 implements DartChangeBuilder { | 30 implements DartChangeBuilder { |
30 /** | 31 /** |
31 * The analysis driver in which the files being edited were analyzed. | 32 * The analysis driver in which the files being edited were analyzed. |
32 */ | 33 */ |
33 final AnalysisDriver driver; | 34 final AnalysisDriver driver; |
34 | 35 |
35 /** | 36 /** |
36 * Initialize a newly created change builder. | 37 * Initialize a newly created change builder. |
37 */ | 38 */ |
38 DartChangeBuilderImpl(this.driver); | 39 DartChangeBuilderImpl(this.driver); |
39 | 40 |
40 @override | 41 @override |
41 Future<DartFileEditBuilderImpl> createFileEditBuilder( | 42 Future<DartFileEditBuilderImpl> createFileEditBuilder( |
42 String path, int fileStamp) async { | 43 String path, int fileStamp) async { |
43 AnalysisResult result = await driver.getResult(path); | 44 AnalysisResult result = await driver.getResult(path); |
44 return new DartFileEditBuilderImpl(this, path, fileStamp, result.unit); | 45 return new DartFileEditBuilderImpl(this, path, fileStamp, result.unit); |
45 } | 46 } |
46 } | 47 } |
47 | 48 |
48 /** | 49 /** |
49 * An [EditBuilder] used to build edits in Dart files. | 50 * An [EditBuilder] used to build edits in Dart files. |
50 */ | 51 */ |
51 class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder { | 52 class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder { |
| 53 List<String> _KNOWN_METHOD_NAME_PREFIXES = ['get', 'is', 'to']; |
| 54 |
52 /** | 55 /** |
53 * Initialize a newly created builder to build a source edit. | 56 * Initialize a newly created builder to build a source edit. |
54 */ | 57 */ |
55 DartEditBuilderImpl( | 58 DartEditBuilderImpl( |
56 DartFileEditBuilderImpl sourceFileEditBuilder, int offset, int length) | 59 DartFileEditBuilderImpl sourceFileEditBuilder, int offset, int length) |
57 : super(sourceFileEditBuilder, offset, length); | 60 : super(sourceFileEditBuilder, offset, length); |
58 | 61 |
59 DartFileEditBuilderImpl get dartFileEditBuilder => fileEditBuilder; | 62 DartFileEditBuilderImpl get dartFileEditBuilder => fileEditBuilder; |
60 | 63 |
61 @override | 64 @override |
(...skipping 474 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
536 write(prefix); | 539 write(prefix); |
537 } | 540 } |
538 first = false; | 541 first = false; |
539 } else { | 542 } else { |
540 write(', '); | 543 write(', '); |
541 } | 544 } |
542 writeType(type); | 545 writeType(type); |
543 } | 546 } |
544 } | 547 } |
545 | 548 |
| 549 /** |
| 550 * Adds [toAdd] items which are not excluded. |
| 551 */ |
| 552 void _addAll( |
| 553 Set<String> excluded, Set<String> result, Iterable<String> toAdd) { |
| 554 for (String item in toAdd) { |
| 555 // add name based on "item", but not "excluded" |
| 556 for (int suffix = 1;; suffix++) { |
| 557 // prepare name, just "item" or "item2", "item3", etc |
| 558 String name = item; |
| 559 if (suffix > 1) { |
| 560 name += suffix.toString(); |
| 561 } |
| 562 // add once found not excluded |
| 563 if (!excluded.contains(name)) { |
| 564 result.add(name); |
| 565 break; |
| 566 } |
| 567 } |
| 568 } |
| 569 } |
| 570 |
| 571 /** |
| 572 * Adds to [result] either [c] or the first ASCII character after it. |
| 573 */ |
| 574 void _addSingleCharacterName( |
| 575 Set<String> excluded, Set<String> result, int c) { |
| 576 while (c < $z) { |
| 577 String name = new String.fromCharCode(c); |
| 578 // may be done |
| 579 if (!excluded.contains(name)) { |
| 580 result.add(name); |
| 581 break; |
| 582 } |
| 583 // next character |
| 584 c = c + 1; |
| 585 } |
| 586 } |
| 587 |
546 void _addSuperTypeProposals( | 588 void _addSuperTypeProposals( |
547 LinkedEditBuilder builder, DartType type, Set<DartType> alreadyAdded) { | 589 LinkedEditBuilder builder, DartType type, Set<DartType> alreadyAdded) { |
548 if (type != null && | 590 if (type != null && |
549 type.element is ClassElement && | 591 type.element is ClassElement && |
550 alreadyAdded.add(type)) { | 592 alreadyAdded.add(type)) { |
551 ClassElement element = type.element as ClassElement; | 593 ClassElement element = type.element as ClassElement; |
552 builder.addSuggestion(LinkedEditSuggestionKind.TYPE, element.name); | 594 builder.addSuggestion(LinkedEditSuggestionKind.TYPE, element.name); |
553 _addSuperTypeProposals(builder, element.supertype, alreadyAdded); | 595 _addSuperTypeProposals(builder, element.supertype, alreadyAdded); |
554 for (InterfaceType interfaceType in element.interfaces) { | 596 for (InterfaceType interfaceType in element.interfaces) { |
555 _addSuperTypeProposals(builder, interfaceType, alreadyAdded); | 597 _addSuperTypeProposals(builder, interfaceType, alreadyAdded); |
556 } | 598 } |
557 } | 599 } |
558 } | 600 } |
559 | 601 |
| 602 String _getBaseNameFromExpression(Expression expression) { |
| 603 String name = null; |
| 604 // e as Type |
| 605 if (expression is AsExpression) { |
| 606 AsExpression asExpression = expression as AsExpression; |
| 607 expression = asExpression.expression; |
| 608 } |
| 609 // analyze expressions |
| 610 if (expression is SimpleIdentifier) { |
| 611 SimpleIdentifier node = expression; |
| 612 return node.name; |
| 613 } else if (expression is PrefixedIdentifier) { |
| 614 PrefixedIdentifier node = expression; |
| 615 return node.identifier.name; |
| 616 } else if (expression is PropertyAccess) { |
| 617 PropertyAccess node = expression; |
| 618 return node.propertyName.name; |
| 619 } else if (expression is MethodInvocation) { |
| 620 name = expression.methodName.name; |
| 621 } else if (expression is InstanceCreationExpression) { |
| 622 InstanceCreationExpression creation = expression; |
| 623 ConstructorName constructorName = creation.constructorName; |
| 624 TypeName typeName = constructorName.type; |
| 625 if (typeName != null) { |
| 626 Identifier typeNameIdentifier = typeName.name; |
| 627 // new ClassName() |
| 628 if (typeNameIdentifier is SimpleIdentifier) { |
| 629 return typeNameIdentifier.name; |
| 630 } |
| 631 // new prefix.name(); |
| 632 if (typeNameIdentifier is PrefixedIdentifier) { |
| 633 PrefixedIdentifier prefixed = typeNameIdentifier; |
| 634 // new prefix.ClassName() |
| 635 if (prefixed.prefix.staticElement is PrefixElement) { |
| 636 return prefixed.identifier.name; |
| 637 } |
| 638 // new ClassName.constructorName() |
| 639 return prefixed.prefix.name; |
| 640 } |
| 641 } |
| 642 } |
| 643 // strip known prefixes |
| 644 if (name != null) { |
| 645 for (int i = 0; i < _KNOWN_METHOD_NAME_PREFIXES.length; i++) { |
| 646 String prefix = _KNOWN_METHOD_NAME_PREFIXES[i]; |
| 647 if (name.startsWith(prefix)) { |
| 648 if (name == prefix) { |
| 649 return null; |
| 650 } else if (isUpperCase(name.codeUnitAt(prefix.length))) { |
| 651 return name.substring(prefix.length); |
| 652 } |
| 653 } |
| 654 } |
| 655 } |
| 656 // done |
| 657 return name; |
| 658 } |
| 659 |
| 660 String _getBaseNameFromLocationInParent(Expression expression) { |
| 661 // value in named expression |
| 662 if (expression.parent is NamedExpression) { |
| 663 NamedExpression namedExpression = expression.parent as NamedExpression; |
| 664 if (namedExpression.expression == expression) { |
| 665 return namedExpression.name.label.name; |
| 666 } |
| 667 } |
| 668 // positional argument |
| 669 ParameterElement parameter = expression.propagatedParameterElement; |
| 670 if (parameter == null) { |
| 671 parameter = expression.staticParameterElement; |
| 672 } |
| 673 if (parameter != null) { |
| 674 return parameter.displayName; |
| 675 } |
| 676 |
| 677 // unknown |
| 678 return null; |
| 679 } |
| 680 |
| 681 /** |
| 682 * Returns all variants of names by removing leading words one by one. |
| 683 */ |
| 684 List<String> _getCamelWordCombinations(String name) { |
| 685 List<String> result = []; |
| 686 List<String> parts = getCamelWords(name); |
| 687 for (int i = 0; i < parts.length; i++) { |
| 688 String s1 = parts[i].toLowerCase(); |
| 689 String s2 = parts.skip(i + 1).join(); |
| 690 String suggestion = '$s1$s2'; |
| 691 result.add(suggestion); |
| 692 } |
| 693 return result; |
| 694 } |
| 695 |
560 /** | 696 /** |
561 * Return the import element used to import the given [element] into the given | 697 * Return the import element used to import the given [element] into the given |
562 * [library], or `null` if the element was not imported, such as when the | 698 * [library], or `null` if the element was not imported, such as when the |
563 * element is declared in the same library. | 699 * element is declared in the same library. |
564 */ | 700 */ |
565 ImportElement _getImportElement(Element element, LibraryElement library) { | 701 ImportElement _getImportElement(Element element, LibraryElement library) { |
566 for (ImportElement imp in library.imports) { | 702 for (ImportElement importElement in library.imports) { |
567 Map<String, Element> definedNames = getImportNamespace(imp); | 703 Map<String, Element> definedNames = _getImportNamespace(importElement); |
568 if (definedNames.containsValue(element)) { | 704 if (definedNames.containsValue(element)) { |
569 return imp; | 705 return importElement; |
570 } | 706 } |
571 } | 707 } |
572 return null; | 708 return null; |
573 } | 709 } |
574 | 710 |
575 /** | 711 /** |
| 712 * Return the namespace added by the given import [element]. |
| 713 */ |
| 714 Map<String, Element> _getImportNamespace(ImportElement element) { |
| 715 NamespaceBuilder builder = new NamespaceBuilder(); |
| 716 Namespace namespace = builder.createImportNamespaceForDirective(element); |
| 717 return namespace.definedNames; |
| 718 } |
| 719 |
| 720 /** |
576 * Return a list containing the suggested names for a parameter with the given | 721 * Return a list containing the suggested names for a parameter with the given |
577 * [type] whose value in one location is computed by the given [expression]. | 722 * [type] whose value in one location is computed by the given [expression]. |
578 * The list will not contain any names in the set of [excluded] names. The | 723 * The list will not contain any names in the set of [excluded] names. The |
579 * [index] is the index of the argument, used to create a name if no better | 724 * [index] is the index of the argument, used to create a name if no better |
580 * name could be created. The first name in the list will be the best name. | 725 * name could be created. The first name in the list will be the best name. |
581 */ | 726 */ |
582 List<String> _getParameterNameSuggestions( | 727 List<String> _getParameterNameSuggestions( |
583 Set<String> usedNames, DartType type, Expression expression, int index) { | 728 Set<String> usedNames, DartType type, Expression expression, int index) { |
584 List<String> suggestions = | 729 List<String> suggestions = |
585 getVariableNameSuggestionsForExpression(type, expression, usedNames); | 730 _getVariableNameSuggestionsForExpression(type, expression, usedNames); |
586 if (suggestions.length != 0) { | 731 if (suggestions.length != 0) { |
587 return suggestions; | 732 return suggestions; |
588 } | 733 } |
589 // TODO(brianwilkerson) Verify that the name below is not in the set of used
names. | 734 // TODO(brianwilkerson) Verify that the name below is not in the set of used
names. |
590 return <String>['param$index']; | 735 return <String>['param$index']; |
591 } | 736 } |
592 | 737 |
593 /** | 738 /** |
594 * Return the source for the parameter with the given [type] and [name]. | 739 * Return the source for the parameter with the given [type] and [name]. |
595 */ | 740 */ |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
731 } | 876 } |
732 } | 877 } |
733 sb.write(">"); | 878 sb.write(">"); |
734 } | 879 } |
735 } | 880 } |
736 // done | 881 // done |
737 return sb.toString(); | 882 return sb.toString(); |
738 } | 883 } |
739 | 884 |
740 /** | 885 /** |
| 886 * Returns possible names for a variable with the given expected type and |
| 887 * expression assigned. |
| 888 */ |
| 889 List<String> _getVariableNameSuggestionsForExpression(DartType expectedType, |
| 890 Expression assignedExpression, Set<String> excluded) { |
| 891 Set<String> res = new Set(); |
| 892 // use expression |
| 893 if (assignedExpression != null) { |
| 894 String nameFromExpression = |
| 895 _getBaseNameFromExpression(assignedExpression); |
| 896 if (nameFromExpression != null) { |
| 897 nameFromExpression = removeStart(nameFromExpression, '_'); |
| 898 _addAll(excluded, res, _getCamelWordCombinations(nameFromExpression)); |
| 899 } |
| 900 String nameFromParent = |
| 901 _getBaseNameFromLocationInParent(assignedExpression); |
| 902 if (nameFromParent != null) { |
| 903 _addAll(excluded, res, _getCamelWordCombinations(nameFromParent)); |
| 904 } |
| 905 } |
| 906 // use type |
| 907 if (expectedType != null && !expectedType.isDynamic) { |
| 908 String typeName = expectedType.name; |
| 909 if ('int' == typeName) { |
| 910 _addSingleCharacterName(excluded, res, $i); |
| 911 } else if ('double' == typeName) { |
| 912 _addSingleCharacterName(excluded, res, $d); |
| 913 } else if ('String' == typeName) { |
| 914 _addSingleCharacterName(excluded, res, $s); |
| 915 } else { |
| 916 _addAll(excluded, res, _getCamelWordCombinations(typeName)); |
| 917 } |
| 918 res.remove(typeName); |
| 919 } |
| 920 // done |
| 921 return new List.from(res); |
| 922 } |
| 923 |
| 924 /** |
741 * Checks if [type] is visible in either the [enclosingExecutable] or | 925 * Checks if [type] is visible in either the [enclosingExecutable] or |
742 * [enclosingClass]. | 926 * [enclosingClass]. |
743 */ | 927 */ |
744 bool _isTypeVisible(DartType type, ClassElement enclosingClass, | 928 bool _isTypeVisible(DartType type, ClassElement enclosingClass, |
745 ExecutableElement enclosingExecutable) { | 929 ExecutableElement enclosingExecutable) { |
746 if (type is TypeParameterType) { | 930 if (type is TypeParameterType) { |
747 TypeParameterElement parameterElement = type.element; | 931 TypeParameterElement parameterElement = type.element; |
748 Element parameterParent = parameterElement.enclosingElement; | 932 Element parameterParent = parameterElement.enclosingElement; |
749 // TODO(brianwilkerson) This needs to compare the parameterParent with | 933 // TODO(brianwilkerson) This needs to compare the parameterParent with |
750 // each of the parents of the enclosingElement. (That means that we only | 934 // each of the parents of the enclosingElement. (That means that we only |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
799 _replaceReturnTypeWithFuture(body, typeProvider); | 983 _replaceReturnTypeWithFuture(body, typeProvider); |
800 } | 984 } |
801 | 985 |
802 @override | 986 @override |
803 DartEditBuilderImpl createEditBuilder(int offset, int length) { | 987 DartEditBuilderImpl createEditBuilder(int offset, int length) { |
804 return new DartEditBuilderImpl(this, offset, length); | 988 return new DartEditBuilderImpl(this, offset, length); |
805 } | 989 } |
806 | 990 |
807 @override | 991 @override |
808 void finalize() { | 992 void finalize() { |
809 addLibraryImports( | 993 _addLibraryImports( |
810 changeBuilder.sourceChange, unit.element.library, librariesToImport); | 994 changeBuilder.sourceChange, unit.element.library, librariesToImport); |
811 } | 995 } |
812 | 996 |
813 // /** | |
814 // * Return the content of the file being edited. | |
815 // */ | |
816 // String getContent() { | |
817 // if (_content == null) { | |
818 // CompilationUnitElement unitElement = unit.element; | |
819 // AnalysisContext context = unitElement.context; | |
820 // if (context == null) { | |
821 // throw new CancelCorrectionException(); | |
822 // } | |
823 // _content = context.getContents(unitElement.source).data; | |
824 // } | |
825 // return _content; | |
826 // } | |
827 | |
828 @override | 997 @override |
829 void importLibraries(Iterable<Source> libraries) { | 998 void importLibraries(Iterable<Source> libraries) { |
830 librariesToImport.addAll(libraries); | 999 librariesToImport.addAll(libraries); |
831 } | 1000 } |
832 | 1001 |
833 @override | 1002 @override |
834 void replaceTypeWithFuture( | 1003 void replaceTypeWithFuture( |
835 TypeAnnotation typeAnnotation, TypeProvider typeProvider) { | 1004 TypeAnnotation typeAnnotation, TypeProvider typeProvider) { |
836 InterfaceType futureType = typeProvider.futureType; | 1005 InterfaceType futureType = typeProvider.futureType; |
837 // | 1006 // |
838 // Check whether the type needs to be replaced. | 1007 // Check whether the type needs to be replaced. |
839 // | 1008 // |
840 DartType type = typeAnnotation?.type; | 1009 DartType type = typeAnnotation?.type; |
841 if (type == null || | 1010 if (type == null || |
842 type.isDynamic || | 1011 type.isDynamic || |
843 type is InterfaceType && type.element == futureType.element) { | 1012 type is InterfaceType && type.element == futureType.element) { |
844 return; | 1013 return; |
845 } | 1014 } |
846 futureType = futureType.instantiate(<DartType>[type]); | 1015 futureType = futureType.instantiate(<DartType>[type]); |
847 // prepare code for the types | 1016 // prepare code for the types |
848 addReplacement(range.node(typeAnnotation), (EditBuilder builder) { | 1017 addReplacement(range.node(typeAnnotation), (EditBuilder builder) { |
849 if (!(builder as DartEditBuilder).writeType(futureType)) { | 1018 if (!(builder as DartEditBuilder).writeType(futureType)) { |
850 builder.write('void'); | 1019 builder.write('void'); |
851 } | 1020 } |
852 }); | 1021 }); |
853 } | 1022 } |
854 | 1023 |
| 1024 /** |
| 1025 * Adds edits to the given [change] that ensure that all the [libraries] are |
| 1026 * imported into the given [targetLibrary]. |
| 1027 */ |
| 1028 void _addLibraryImports(SourceChange change, LibraryElement targetLibrary, |
| 1029 Set<Source> libraries) { |
| 1030 // Prepare information about existing imports. |
| 1031 LibraryDirective libraryDirective; |
| 1032 List<ImportDirective> importDirectives = <ImportDirective>[]; |
| 1033 for (Directive directive in unit.directives) { |
| 1034 if (directive is LibraryDirective) { |
| 1035 libraryDirective = directive; |
| 1036 } else if (directive is ImportDirective) { |
| 1037 importDirectives.add(directive); |
| 1038 } |
| 1039 } |
| 1040 |
| 1041 // Prepare all URIs to import. |
| 1042 List<String> uriList = libraries |
| 1043 .map((library) => _getLibrarySourceUri(targetLibrary, library)) |
| 1044 .toList(); |
| 1045 uriList.sort((a, b) => a.compareTo(b)); |
| 1046 |
| 1047 // Insert imports: between existing imports. |
| 1048 if (importDirectives.isNotEmpty) { |
| 1049 bool isFirstPackage = true; |
| 1050 for (String importUri in uriList) { |
| 1051 bool inserted = false; |
| 1052 bool isPackage = importUri.startsWith('package:'); |
| 1053 bool isAfterDart = false; |
| 1054 for (ImportDirective existingImport in importDirectives) { |
| 1055 if (existingImport.uriContent.startsWith('dart:')) { |
| 1056 isAfterDart = true; |
| 1057 } |
| 1058 if (existingImport.uriContent.startsWith('package:')) { |
| 1059 isFirstPackage = false; |
| 1060 } |
| 1061 if (importUri.compareTo(existingImport.uriContent) < 0) { |
| 1062 addInsertion(existingImport.offset, (EditBuilder builder) { |
| 1063 builder.write("import '"); |
| 1064 builder.write(importUri); |
| 1065 builder.writeln("';"); |
| 1066 }); |
| 1067 inserted = true; |
| 1068 break; |
| 1069 } |
| 1070 } |
| 1071 if (!inserted) { |
| 1072 addInsertion(importDirectives.last.end, (EditBuilder builder) { |
| 1073 if (isPackage && isFirstPackage && isAfterDart) { |
| 1074 builder.writeln(); |
| 1075 } |
| 1076 builder.writeln(); |
| 1077 builder.write("import '"); |
| 1078 builder.write(importUri); |
| 1079 builder.write("';"); |
| 1080 }); |
| 1081 } |
| 1082 if (isPackage) { |
| 1083 isFirstPackage = false; |
| 1084 } |
| 1085 } |
| 1086 return; |
| 1087 } |
| 1088 |
| 1089 // Insert imports: after the library directive. |
| 1090 if (libraryDirective != null) { |
| 1091 for (int i = 0; i < uriList.length; i++) { |
| 1092 String importUri = uriList[i]; |
| 1093 addInsertion(libraryDirective.end, (EditBuilder builder) { |
| 1094 if (i == 0) { |
| 1095 builder.writeln(); |
| 1096 } |
| 1097 builder.writeln(); |
| 1098 builder.write("import '"); |
| 1099 builder.write(importUri); |
| 1100 builder.writeln("';"); |
| 1101 }); |
| 1102 } |
| 1103 return; |
| 1104 } |
| 1105 |
| 1106 // If still at the beginning of the file, skip shebang and line comments. |
| 1107 _InsertionDescription desc = _getInsertDescTop(); |
| 1108 int offset = desc.offset; |
| 1109 for (int i = 0; i < uriList.length; i++) { |
| 1110 String importUri = uriList[i]; |
| 1111 addInsertion(offset, (EditBuilder builder) { |
| 1112 if (i == 0 && desc.insertEmptyLineBefore) { |
| 1113 builder.writeln(); |
| 1114 } |
| 1115 builder.write("import '"); |
| 1116 builder.write(importUri); |
| 1117 builder.writeln("';"); |
| 1118 if (i == uriList.length - 1 && desc.insertEmptyLineAfter) { |
| 1119 builder.writeln(); |
| 1120 } |
| 1121 }); |
| 1122 } |
| 1123 } |
| 1124 |
| 1125 /** |
| 1126 * Returns a [InsertDesc] describing where to insert a new directive or a |
| 1127 * top-level declaration at the top of the file. |
| 1128 */ |
| 1129 _InsertionDescription _getInsertDescTop() { |
| 1130 // skip leading line comments |
| 1131 int offset = 0; |
| 1132 bool insertEmptyLineBefore = false; |
| 1133 bool insertEmptyLineAfter = false; |
| 1134 String source = unit.element.context.getContents(unit.element.source).data; |
| 1135 var lineInfo = unit.lineInfo; |
| 1136 // skip hash-bang |
| 1137 if (offset < source.length - 2) { |
| 1138 String linePrefix = _getText(source, offset, 2); |
| 1139 if (linePrefix == "#!") { |
| 1140 insertEmptyLineBefore = true; |
| 1141 offset = lineInfo.getOffsetOfLineAfter(offset); |
| 1142 // skip empty lines to first line comment |
| 1143 int emptyOffset = offset; |
| 1144 while (emptyOffset < source.length - 2) { |
| 1145 int nextLineOffset = lineInfo.getOffsetOfLineAfter(emptyOffset); |
| 1146 String line = source.substring(emptyOffset, nextLineOffset); |
| 1147 if (line.trim().isEmpty) { |
| 1148 emptyOffset = nextLineOffset; |
| 1149 continue; |
| 1150 } else if (line.startsWith("//")) { |
| 1151 offset = emptyOffset; |
| 1152 break; |
| 1153 } else { |
| 1154 break; |
| 1155 } |
| 1156 } |
| 1157 } |
| 1158 } |
| 1159 // skip line comments |
| 1160 while (offset < source.length - 2) { |
| 1161 String linePrefix = _getText(source, offset, 2); |
| 1162 if (linePrefix == "//") { |
| 1163 insertEmptyLineBefore = true; |
| 1164 offset = lineInfo.getOffsetOfLineAfter(offset); |
| 1165 } else { |
| 1166 break; |
| 1167 } |
| 1168 } |
| 1169 // determine if empty line is required after |
| 1170 int currentLine = lineInfo.getLocation(offset).lineNumber; |
| 1171 if (currentLine < lineInfo.lineCount) { |
| 1172 int nextLineOffset = lineInfo.getOffsetOfLine(currentLine + 1); |
| 1173 String insertLine = source.substring(offset, nextLineOffset); |
| 1174 if (!insertLine.trim().isEmpty) { |
| 1175 insertEmptyLineAfter = true; |
| 1176 } |
| 1177 } |
| 1178 return new _InsertionDescription( |
| 1179 offset, insertEmptyLineBefore, insertEmptyLineAfter); |
| 1180 } |
| 1181 |
| 1182 // /** |
| 1183 // * Return the content of the file being edited. |
| 1184 // */ |
| 1185 // String getContent() { |
| 1186 // if (_content == null) { |
| 1187 // CompilationUnitElement unitElement = unit.element; |
| 1188 // AnalysisContext context = unitElement.context; |
| 1189 // if (context == null) { |
| 1190 // throw new CancelCorrectionException(); |
| 1191 // } |
| 1192 // _content = context.getContents(unitElement.source).data; |
| 1193 // } |
| 1194 // return _content; |
| 1195 // } |
| 1196 |
| 1197 /** |
| 1198 * Computes the best URI to import [what] into [from]. |
| 1199 */ |
| 1200 String _getLibrarySourceUri(LibraryElement from, Source what) { |
| 1201 String whatPath = what.fullName; |
| 1202 // check if an absolute URI (such as 'dart:' or 'package:') |
| 1203 Uri whatUri = what.uri; |
| 1204 String whatUriScheme = whatUri.scheme; |
| 1205 if (whatUriScheme != '' && whatUriScheme != 'file') { |
| 1206 return whatUri.toString(); |
| 1207 } |
| 1208 // compute a relative URI |
| 1209 String fromFolder = path.dirname(from.source.fullName); |
| 1210 String relativeFile = path.relative(whatPath, from: fromFolder); |
| 1211 return path.split(relativeFile).join('/'); |
| 1212 } |
| 1213 |
| 1214 /** |
| 1215 * Returns the text of the given range in the unit. |
| 1216 */ |
| 1217 String _getText(String content, int offset, int length) { |
| 1218 return content.substring(offset, offset + length); |
| 1219 } |
| 1220 |
855 // /** | 1221 // /** |
856 // * Returns the text of the given [AstNode] in the unit. | 1222 // * Returns the text of the given [AstNode] in the unit. |
857 // */ | 1223 // */ |
858 // String _getNodeText(AstNode node) { | 1224 // String _getNodeText(AstNode node) { |
859 // return _getText(node.offset, node.length); | 1225 // return _getText(node.offset, node.length); |
860 // } | 1226 // } |
861 // | 1227 // |
862 // /** | 1228 // /** |
863 // * Returns the text of the given range in the unit. | 1229 // * Returns the text of the given range in the unit. |
864 // */ | 1230 // */ |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
933 enclosingExecutable = node.element; | 1299 enclosingExecutable = node.element; |
934 } else if (node is MethodDeclaration) { | 1300 } else if (node is MethodDeclaration) { |
935 enclosingExecutable = node.element; | 1301 enclosingExecutable = node.element; |
936 } else if (node is FunctionDeclaration) { | 1302 } else if (node is FunctionDeclaration) { |
937 enclosingExecutable = node.element; | 1303 enclosingExecutable = node.element; |
938 } | 1304 } |
939 node = node.parent; | 1305 node = node.parent; |
940 } | 1306 } |
941 } | 1307 } |
942 } | 1308 } |
| 1309 |
| 1310 class _InsertionDescription { |
| 1311 final int offset; |
| 1312 final bool insertEmptyLineBefore; |
| 1313 final bool insertEmptyLineAfter; |
| 1314 _InsertionDescription( |
| 1315 this.offset, this.insertEmptyLineBefore, this.insertEmptyLineAfter); |
| 1316 } |
OLD | NEW |