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 |