| 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.refactoring.rename; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 | |
| 10 import 'package:analysis_services/correction/change.dart'; | |
| 11 import 'package:analysis_services/correction/status.dart'; | |
| 12 import 'package:analysis_services/refactoring/refactoring.dart'; | |
| 13 import 'package:analysis_services/search/search_engine.dart'; | |
| 14 import 'package:analysis_services/src/correction/source_range.dart'; | |
| 15 import 'package:analysis_services/src/refactoring/refactoring.dart'; | |
| 16 import 'package:analyzer/src/generated/element.dart'; | |
| 17 import 'package:analyzer/src/generated/engine.dart'; | |
| 18 import 'package:analyzer/src/generated/source.dart'; | |
| 19 | |
| 20 | |
| 21 /** | |
| 22 * Returns the [Edit] to replace the given [SearchMatch] reference. | |
| 23 */ | |
| 24 Edit createReferenceEdit(SourceReference reference, String newText) { | |
| 25 return new Edit.range(reference.range, newText); | |
| 26 } | |
| 27 | |
| 28 | |
| 29 /** | |
| 30 * Returns the file containing declaration of the given [Element]. | |
| 31 */ | |
| 32 String getElementFile(Element element) { | |
| 33 return element.source.fullName; | |
| 34 } | |
| 35 | |
| 36 | |
| 37 /** | |
| 38 * When a [Source] (a file) is used in more than one context, [SearchEngine] | |
| 39 * will return separate [SearchMatch]s for each context. But in rename | |
| 40 * refactorings we want to update each [Source] only once. | |
| 41 */ | |
| 42 List<SourceReference> getSourceReferences(List<SearchMatch> matches) { | |
| 43 var uniqueReferences = new HashMap<SourceReference, SourceReference>(); | |
| 44 for (SearchMatch match in matches) { | |
| 45 Element element = match.element; | |
| 46 String file = getElementFile(element); | |
| 47 SourceRange range = match.sourceRange; | |
| 48 SourceReference newReference = | |
| 49 new SourceReference(file, range, element, match.isResolved, match.isQual
ified); | |
| 50 SourceReference oldReference = uniqueReferences[newReference]; | |
| 51 if (oldReference == null) { | |
| 52 uniqueReferences[newReference] = newReference; | |
| 53 oldReference = newReference; | |
| 54 } | |
| 55 } | |
| 56 return uniqueReferences.keys.toList(); | |
| 57 } | |
| 58 | |
| 59 | |
| 60 /** | |
| 61 * Returns `true` if two given [Element]s are [LocalElement]s and have | |
| 62 * intersecting with visibility ranges. | |
| 63 */ | |
| 64 bool haveIntersectingRanges(LocalElement localElement, Element element) { | |
| 65 if (element is! LocalElement) { | |
| 66 return false; | |
| 67 } | |
| 68 LocalElement localElement2 = element as LocalElement; | |
| 69 Source localSource = localElement.source; | |
| 70 Source localSource2 = localElement2.source; | |
| 71 SourceRange localRange = localElement.visibleRange; | |
| 72 SourceRange localRange2 = localElement2.visibleRange; | |
| 73 return localSource2 == localSource && | |
| 74 localRange != null && | |
| 75 localRange2 != null && | |
| 76 localRange2.intersects(localRange); | |
| 77 } | |
| 78 | |
| 79 | |
| 80 /** | |
| 81 * Checks if [element] is defined in the library containing [source]. | |
| 82 */ | |
| 83 bool isDefinedInLibrary(Element element, AnalysisContext context, Source source) | |
| 84 { | |
| 85 // should be the same AnalysisContext | |
| 86 if (!isInContext(element, context)) { | |
| 87 return false; | |
| 88 } | |
| 89 // private elements are visible only in their library | |
| 90 List<Source> librarySourcesOfSource = context.getLibrariesContaining(source); | |
| 91 Source librarySourceOfElement = element.library.source; | |
| 92 return librarySourcesOfSource.contains(librarySourceOfElement); | |
| 93 } | |
| 94 | |
| 95 | |
| 96 /** | |
| 97 * Checks if the given [Element] is in the given [AnalysisContext]. | |
| 98 */ | |
| 99 bool isInContext(Element element, AnalysisContext context) { | |
| 100 AnalysisContext elementContext = element.context; | |
| 101 if (elementContext == context) { | |
| 102 return true; | |
| 103 } | |
| 104 if (context is InstrumentedAnalysisContextImpl) { | |
| 105 return elementContext == context.basis; | |
| 106 } | |
| 107 return false; | |
| 108 } | |
| 109 | |
| 110 | |
| 111 /** | |
| 112 * Checks if the given unqualified [SearchMatch] intersects with visibility | |
| 113 * range of [localElement]. | |
| 114 */ | |
| 115 bool isReferenceInLocalRange(LocalElement localElement, SearchMatch reference) { | |
| 116 if (reference.isQualified) { | |
| 117 return false; | |
| 118 } | |
| 119 Source localSource = localElement.source; | |
| 120 Source referenceSource = reference.element.source; | |
| 121 SourceRange localRange = localElement.visibleRange; | |
| 122 SourceRange referenceRange = reference.sourceRange; | |
| 123 return referenceSource == localSource && | |
| 124 referenceRange.intersects(localRange); | |
| 125 } | |
| 126 | |
| 127 | |
| 128 /** | |
| 129 * Checks if [element] is visible in the library containing [source]. | |
| 130 */ | |
| 131 bool isVisibleInLibrary(Element element, AnalysisContext context, Source source) | |
| 132 { | |
| 133 // should be the same AnalysisContext | |
| 134 if (!isInContext(element, context)) { | |
| 135 return false; | |
| 136 } | |
| 137 // public elements are always visible | |
| 138 if (element.isPublic) { | |
| 139 return true; | |
| 140 } | |
| 141 // private elements are visible only in their library | |
| 142 return isDefinedInLibrary(element, context, source); | |
| 143 } | |
| 144 | |
| 145 | |
| 146 | |
| 147 /** | |
| 148 * An abstract implementation of [RenameRefactoring]. | |
| 149 */ | |
| 150 abstract class RenameRefactoringImpl extends RefactoringImpl implements | |
| 151 RenameRefactoring { | |
| 152 final SearchEngine searchEngine; | |
| 153 final Element element; | |
| 154 final AnalysisContext context; | |
| 155 final String oldName; | |
| 156 | |
| 157 String newName; | |
| 158 | |
| 159 RenameRefactoringImpl(SearchEngine searchEngine, Element element) | |
| 160 : searchEngine = searchEngine, | |
| 161 element = element, | |
| 162 context = element.context, | |
| 163 oldName = _getDisplayName(element); | |
| 164 | |
| 165 /** | |
| 166 * Adds the "Update declaration" [Edit] to [change]. | |
| 167 */ | |
| 168 void addDeclarationEdit(Change change, Element element) { | |
| 169 if (element != null) { | |
| 170 String file = getElementFile(element); | |
| 171 Edit edit = new Edit.range(rangeElementName(element), newName); | |
| 172 change.addEdit(file, edit); | |
| 173 } | |
| 174 } | |
| 175 | |
| 176 /** | |
| 177 * Adds an "Update reference" [Edit] to [change]. | |
| 178 */ | |
| 179 void addReferenceEdit(Change change, SourceReference reference) { | |
| 180 Edit edit = createReferenceEdit(reference, newName); | |
| 181 change.addEdit(reference.file, edit); | |
| 182 } | |
| 183 | |
| 184 @override | |
| 185 Future<RefactoringStatus> checkInitialConditions() { | |
| 186 var result = new RefactoringStatus(); | |
| 187 return new Future.value(result); | |
| 188 } | |
| 189 | |
| 190 @override | |
| 191 RefactoringStatus checkNewName() { | |
| 192 RefactoringStatus result = new RefactoringStatus(); | |
| 193 if (newName == oldName) { | |
| 194 result.addFatalError( | |
| 195 "The new name must be different than the current name."); | |
| 196 } | |
| 197 return result; | |
| 198 } | |
| 199 | |
| 200 @override | |
| 201 bool requiresPreview() { | |
| 202 return false; | |
| 203 } | |
| 204 | |
| 205 static String _getDisplayName(Element element) { | |
| 206 if (element is ImportElement) { | |
| 207 PrefixElement prefix = element.prefix; | |
| 208 if (prefix != null) { | |
| 209 return prefix.displayName; | |
| 210 } | |
| 211 } | |
| 212 return element.displayName; | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 | |
| 217 /** | |
| 218 * The [SourceRange] in some [Source]. | |
| 219 */ | |
| 220 class SourceReference { | |
| 221 final String file; | |
| 222 final SourceRange range; | |
| 223 final Element element; | |
| 224 final bool isResolved; | |
| 225 final bool isQualified; | |
| 226 | |
| 227 SourceReference(this.file, this.range, this.element, this.isResolved, | |
| 228 this.isQualified); | |
| 229 | |
| 230 @override | |
| 231 int get hashCode { | |
| 232 int hash = file.hashCode; | |
| 233 hash = ((hash << 16) & 0xFFFFFFFF) + range.hashCode; | |
| 234 return hash; | |
| 235 } | |
| 236 | |
| 237 @override | |
| 238 bool operator ==(Object other) { | |
| 239 if (identical(other, this)) { | |
| 240 return true; | |
| 241 } | |
| 242 if (other is SourceReference) { | |
| 243 return other.file == file && other.range == range; | |
| 244 } | |
| 245 return false; | |
| 246 } | |
| 247 | |
| 248 @override | |
| 249 String toString() => '${file}@${range}'; | |
| 250 } | |
| OLD | NEW |