| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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 library services.src.refactoring.rename; | 5 library services.src.refactoring.rename; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | |
| 9 | 8 |
| 10 import 'package:analysis_server/src/protocol.dart' hide Element; | 9 import 'package:analysis_server/src/protocol.dart' hide Element; |
| 11 import 'package:analysis_server/src/services/correction/source_range.dart'; | 10 import 'package:analysis_server/src/services/correction/source_range.dart'; |
| 12 import 'package:analysis_server/src/services/correction/status.dart'; | 11 import 'package:analysis_server/src/services/correction/status.dart'; |
| 13 import 'package:analysis_server/src/services/refactoring/refactoring.dart'; | 12 import 'package:analysis_server/src/services/refactoring/refactoring.dart'; |
| 14 import 'package:analysis_server/src/services/refactoring/refactoring_internal.da
rt'; | 13 import 'package:analysis_server/src/services/refactoring/refactoring_internal.da
rt'; |
| 15 import 'package:analysis_server/src/services/search/search_engine.dart'; | 14 import 'package:analysis_server/src/services/search/search_engine.dart'; |
| 16 import 'package:analyzer/src/generated/element.dart'; | 15 import 'package:analyzer/src/generated/element.dart'; |
| 17 import 'package:analyzer/src/generated/engine.dart'; | 16 import 'package:analyzer/src/generated/engine.dart'; |
| 18 import 'package:analyzer/src/generated/source.dart'; | 17 import 'package:analyzer/src/generated/source.dart'; |
| 19 | 18 |
| 20 | 19 |
| 21 /** | 20 /** |
| 22 * Returns the [Edit] to replace the given [SearchMatch] reference. | |
| 23 */ | |
| 24 SourceEdit createReferenceEdit(SourceReference reference, String newText, | |
| 25 {String id}) { | |
| 26 return new SourceEdit.range(reference.range, newText, id: id); | |
| 27 } | |
| 28 | |
| 29 | |
| 30 /** | |
| 31 * Returns the file containing declaration of the given [Element]. | |
| 32 */ | |
| 33 String getElementFile(Element element) { | |
| 34 return element.source.fullName; | |
| 35 } | |
| 36 | |
| 37 | |
| 38 /** | |
| 39 * When a [Source] (a file) is used in more than one context, [SearchEngine] | |
| 40 * will return separate [SearchMatch]s for each context. But in rename | |
| 41 * refactorings we want to update each [Source] only once. | |
| 42 */ | |
| 43 List<SourceReference> getSourceReferences(List<SearchMatch> matches) { | |
| 44 var uniqueReferences = new HashMap<SourceReference, SourceReference>(); | |
| 45 for (SearchMatch match in matches) { | |
| 46 Element element = match.element; | |
| 47 String file = getElementFile(element); | |
| 48 SourceRange range = match.sourceRange; | |
| 49 SourceReference newReference = | |
| 50 new SourceReference(file, range, element, match.isResolved, match.isQual
ified); | |
| 51 SourceReference oldReference = uniqueReferences[newReference]; | |
| 52 if (oldReference == null) { | |
| 53 uniqueReferences[newReference] = newReference; | |
| 54 oldReference = newReference; | |
| 55 } | |
| 56 } | |
| 57 return uniqueReferences.keys.toList(); | |
| 58 } | |
| 59 | |
| 60 | |
| 61 /** | |
| 62 * Returns `true` if two given [Element]s are [LocalElement]s and have | 21 * Returns `true` if two given [Element]s are [LocalElement]s and have |
| 63 * intersecting with visibility ranges. | 22 * intersecting with visibility ranges. |
| 64 */ | 23 */ |
| 65 bool haveIntersectingRanges(LocalElement localElement, Element element) { | 24 bool haveIntersectingRanges(LocalElement localElement, Element element) { |
| 66 if (element is! LocalElement) { | 25 if (element is! LocalElement) { |
| 67 return false; | 26 return false; |
| 68 } | 27 } |
| 69 LocalElement localElement2 = element as LocalElement; | 28 LocalElement localElement2 = element as LocalElement; |
| 70 Source localSource = localElement.source; | 29 Source localSource = localElement.source; |
| 71 Source localSource2 = localElement2.source; | 30 Source localSource2 = localElement2.source; |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 149 * An abstract implementation of [RenameRefactoring]. | 108 * An abstract implementation of [RenameRefactoring]. |
| 150 */ | 109 */ |
| 151 abstract class RenameRefactoringImpl extends RefactoringImpl implements | 110 abstract class RenameRefactoringImpl extends RefactoringImpl implements |
| 152 RenameRefactoring { | 111 RenameRefactoring { |
| 153 final SearchEngine searchEngine; | 112 final SearchEngine searchEngine; |
| 154 final Element element; | 113 final Element element; |
| 155 final AnalysisContext context; | 114 final AnalysisContext context; |
| 156 final String elementKindName; | 115 final String elementKindName; |
| 157 final String oldName; | 116 final String oldName; |
| 158 | 117 |
| 118 SourceChange change; |
| 119 |
| 159 String newName; | 120 String newName; |
| 160 | 121 |
| 161 RenameRefactoringImpl(SearchEngine searchEngine, Element element) | 122 RenameRefactoringImpl(SearchEngine searchEngine, Element element) |
| 162 : searchEngine = searchEngine, | 123 : searchEngine = searchEngine, |
| 163 element = element, | 124 element = element, |
| 164 context = element.context, | 125 context = element.context, |
| 165 elementKindName = element.kind.displayName, | 126 elementKindName = element.kind.displayName, |
| 166 oldName = _getDisplayName(element); | 127 oldName = _getDisplayName(element); |
| 167 | 128 |
| 168 /** | 129 /** |
| 169 * Adds the "Update declaration" [Edit] to [change]. | 130 * Adds a [SourceEdit] to update [element] name to [change]. |
| 170 */ | 131 */ |
| 171 void addDeclarationEdit(SourceChange change, Element element) { | 132 void addDeclarationEdit(Element element) { |
| 172 if (element != null) { | 133 if (element != null) { |
| 173 SourceEdit edit = | 134 SourceRange range = rangeElementName(element); |
| 174 new SourceEdit.range(rangeElementName(element), newName); | 135 SourceEdit edit = new SourceEdit.range(range, newName); |
| 175 change.addElementEdit(element, edit); | 136 change.addElementEdit(element, edit); |
| 176 } | 137 } |
| 177 } | 138 } |
| 178 | 139 |
| 179 /** | 140 /** |
| 180 * Adds an "Update reference" [Edit] to [change]. | 141 * Adds [SourceEdit]s to update [matches] to [change]. |
| 181 */ | 142 */ |
| 182 void addReferenceEdit(SourceChange change, SourceReference reference) { | 143 void addReferenceEdits(List<SearchMatch> matches) { |
| 183 SourceEdit edit = createReferenceEdit(reference, newName); | 144 List<SourceReference> references = getSourceReferences(matches); |
| 184 change.addElementEdit(reference.element, edit); | 145 for (SourceReference reference in references) { |
| 146 reference.addEdit(change, newName); |
| 147 } |
| 185 } | 148 } |
| 186 | 149 |
| 187 @override | 150 @override |
| 188 Future<RefactoringStatus> checkInitialConditions() { | 151 Future<RefactoringStatus> checkInitialConditions() { |
| 189 var result = new RefactoringStatus(); | 152 RefactoringStatus result = new RefactoringStatus(); |
| 190 return new Future.value(result); | 153 return new Future.value(result); |
| 191 } | 154 } |
| 192 | 155 |
| 193 @override | 156 @override |
| 194 RefactoringStatus checkNewName() { | 157 RefactoringStatus checkNewName() { |
| 195 RefactoringStatus result = new RefactoringStatus(); | 158 RefactoringStatus result = new RefactoringStatus(); |
| 196 if (newName == oldName) { | 159 if (newName == oldName) { |
| 197 result.addFatalError( | 160 result.addFatalError( |
| 198 "The new name must be different than the current name."); | 161 "The new name must be different than the current name."); |
| 199 } | 162 } |
| 200 return result; | 163 return result; |
| 201 } | 164 } |
| 202 | 165 |
| 203 @override | 166 @override |
| 167 Future<SourceChange> createChange() { |
| 168 change = new SourceChange(refactoringName); |
| 169 return fillChange().then((_) => change); |
| 170 } |
| 171 |
| 172 /** |
| 173 * Adds individual edits to [change]. |
| 174 */ |
| 175 Future fillChange(); |
| 176 |
| 177 @override |
| 204 bool requiresPreview() { | 178 bool requiresPreview() { |
| 205 return false; | 179 return false; |
| 206 } | 180 } |
| 207 | 181 |
| 208 static String _getDisplayName(Element element) { | 182 static String _getDisplayName(Element element) { |
| 209 if (element is ImportElement) { | 183 if (element is ImportElement) { |
| 210 PrefixElement prefix = element.prefix; | 184 PrefixElement prefix = element.prefix; |
| 211 if (prefix != null) { | 185 if (prefix != null) { |
| 212 return prefix.displayName; | 186 return prefix.displayName; |
| 213 } | 187 } |
| 214 } | 188 } |
| 215 return element.displayName; | 189 return element.displayName; |
| 216 } | 190 } |
| 217 } | 191 } |
| 218 | |
| 219 | |
| 220 /** | |
| 221 * The [SourceRange] in some [Source]. | |
| 222 */ | |
| 223 class SourceReference { | |
| 224 final String file; | |
| 225 final SourceRange range; | |
| 226 final Element element; | |
| 227 final bool isResolved; | |
| 228 final bool isQualified; | |
| 229 | |
| 230 SourceReference(this.file, this.range, this.element, this.isResolved, | |
| 231 this.isQualified); | |
| 232 | |
| 233 @override | |
| 234 int get hashCode { | |
| 235 int hash = file.hashCode; | |
| 236 hash = ((hash << 16) & 0xFFFFFFFF) + range.hashCode; | |
| 237 return hash; | |
| 238 } | |
| 239 | |
| 240 @override | |
| 241 bool operator ==(Object other) { | |
| 242 if (identical(other, this)) { | |
| 243 return true; | |
| 244 } | |
| 245 if (other is SourceReference) { | |
| 246 return other.file == file && other.range == range; | |
| 247 } | |
| 248 return false; | |
| 249 } | |
| 250 | |
| 251 @override | |
| 252 String toString() => '${file}@${range}'; | |
| 253 } | |
| OLD | NEW |