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_unit_member; | |
6 | |
7 import 'dart:async'; | |
8 | |
9 import 'package:analysis_services/correction/change.dart'; | |
10 import 'package:analysis_services/correction/status.dart'; | |
11 import 'package:analysis_services/refactoring/refactoring.dart'; | |
12 import 'package:analysis_services/search/search_engine.dart'; | |
13 import 'package:analysis_services/src/correction/util.dart'; | |
14 import 'package:analysis_services/src/refactoring/naming_conventions.dart'; | |
15 import 'package:analysis_services/src/refactoring/rename.dart'; | |
16 import 'package:analyzer/src/generated/element.dart'; | |
17 import 'package:analyzer/src/generated/java_core.dart'; | |
18 import 'package:analysis_services/search/element_visitors.dart'; | |
19 | |
20 | |
21 /** | |
22 * A [Refactoring] for renaming compilation unit member [Element]s. | |
23 */ | |
24 class RenameUnitMemberRefactoringImpl extends RenameRefactoringImpl { | |
25 RenameUnitMemberRefactoringImpl(SearchEngine searchEngine, Element element) | |
26 : super(searchEngine, element); | |
27 | |
28 @override | |
29 String get refactoringName { | |
30 if (element is FunctionElement) { | |
31 return "Rename Top-Level Function"; | |
32 } | |
33 if (element is FunctionTypeAliasElement) { | |
34 return "Rename Function Type Alias"; | |
35 } | |
36 if (element is TopLevelVariableElement) { | |
37 return "Rename Top-Level Variable"; | |
38 } | |
39 return "Rename Class"; | |
40 } | |
41 | |
42 @override | |
43 Future<RefactoringStatus> checkFinalConditions() { | |
44 return new RenameUnitMemberValidator( | |
45 searchEngine, | |
46 element, | |
47 element.kind, | |
48 newName, | |
49 true).validate(); | |
50 } | |
51 | |
52 @override | |
53 RefactoringStatus checkNewName() { | |
54 RefactoringStatus result = super.checkNewName(); | |
55 if (element is TopLevelVariableElement) { | |
56 TopLevelVariableElement variable = element as TopLevelVariableElement; | |
57 if (variable.isConst) { | |
58 result.addStatus(validateConstantName(newName)); | |
59 } else { | |
60 result.addStatus(validateVariableName(newName)); | |
61 } | |
62 } | |
63 if (element is FunctionElement) { | |
64 result.addStatus(validateFunctionName(newName)); | |
65 } | |
66 if (element is FunctionTypeAliasElement) { | |
67 result.addStatus(validateFunctionTypeAliasName(newName)); | |
68 } | |
69 if (element is ClassElement) { | |
70 result.addStatus(validateClassName(newName)); | |
71 } | |
72 return result; | |
73 } | |
74 | |
75 @override | |
76 Future<Change> createChange() { | |
77 Change change = new Change(refactoringName); | |
78 // prepare elements | |
79 List<Element> elements = []; | |
80 if (element is PropertyInducingElement && element.isSynthetic) { | |
81 PropertyInducingElement property = element as PropertyInducingElement; | |
82 PropertyAccessorElement getter = property.getter; | |
83 PropertyAccessorElement setter = property.setter; | |
84 if (getter != null) { | |
85 elements.add(getter); | |
86 } | |
87 if (setter != null) { | |
88 elements.add(setter); | |
89 } | |
90 } else { | |
91 elements.add(element); | |
92 } | |
93 // update each element | |
94 return Future.forEach(elements, (Element element) { | |
95 // update declaration | |
96 addDeclarationEdit(change, element); | |
97 // schedule updating references | |
98 return searchEngine.searchReferences(element).then((refMatches) { | |
99 List<SourceReference> references = getSourceReferences(refMatches); | |
100 for (SourceReference reference in references) { | |
101 addReferenceEdit(change, reference); | |
102 } | |
103 }); | |
104 }).then((_) { | |
105 return change; | |
106 }); | |
107 } | |
108 } | |
109 | |
110 | |
111 /** | |
112 * Helper to check if renaming or creating [Element] with given name will cause
any problems. | |
113 */ | |
114 class RenameUnitMemberValidator { | |
115 final SearchEngine searchEngine; | |
116 final Element element; | |
117 final ElementKind elementKind; | |
118 final String newName; | |
119 final bool forRename; | |
120 | |
121 final RefactoringStatus result = new RefactoringStatus(); | |
122 | |
123 RenameUnitMemberValidator(this.searchEngine, this.element, this.elementKind, | |
124 this.newName, this.forRename); | |
125 | |
126 Future<RefactoringStatus> validate() { | |
127 _validateWillConflict(); | |
128 List<Future> futures = <Future>[]; | |
129 if (forRename) { | |
130 futures.add(_validateWillBeShadowed()); | |
131 } | |
132 futures.add(_validateWillShadow()); | |
133 return Future.wait(futures).then((_) { | |
134 return result; | |
135 }); | |
136 } | |
137 | |
138 /** | |
139 * Returns `true` if [element] is visible at the given [SearchMatch]. | |
140 */ | |
141 bool _isVisibleAt(Element element, SearchMatch at) { | |
142 LibraryElement library = at.element.library; | |
143 // may be the same library | |
144 if (element.library == library) { | |
145 return true; | |
146 } | |
147 // check imports | |
148 for (ImportElement importElement in library.imports) { | |
149 // ignore if imported with prefix | |
150 if (importElement.prefix != null) { | |
151 continue; | |
152 } | |
153 // check imported elements | |
154 if (getImportNamespace(importElement).containsValue(element)) { | |
155 return true; | |
156 } | |
157 } | |
158 // no, it is not visible | |
159 return false; | |
160 } | |
161 | |
162 /** | |
163 * Validates if any usage of [element] renamed to [newName] will be shadowed. | |
164 */ | |
165 Future _validateWillBeShadowed() { | |
166 return searchEngine.searchReferences(element).then((references) { | |
167 for (SearchMatch reference in references) { | |
168 Element refElement = reference.element; | |
169 ClassElement refClass = | |
170 refElement.getAncestor((e) => e is ClassElement); | |
171 if (refClass != null) { | |
172 visitChildren(refClass, (shadow) { | |
173 if (hasDisplayName(shadow, newName)) { | |
174 String message = | |
175 format( | |
176 "Reference to renamed {0} will be shadowed by {1} '{2}'.", | |
177 getElementKindName(element), | |
178 getElementKindName(shadow), | |
179 getElementQualifiedName(shadow)); | |
180 result.addError( | |
181 message, | |
182 new RefactoringStatusContext.forElement(shadow)); | |
183 } | |
184 }); | |
185 } | |
186 } | |
187 }); | |
188 } | |
189 | |
190 /** | |
191 * Validates if [element] renamed to [newName] will conflict with another | |
192 * top-level [Element] in the same library. | |
193 */ | |
194 void _validateWillConflict() { | |
195 LibraryElement library = element.getAncestor((e) => e is LibraryElement); | |
196 visitLibraryTopLevelElements(library, (element) { | |
197 if (hasDisplayName(element, newName)) { | |
198 String message = | |
199 format( | |
200 "Library already declares {0} with name '{1}'.", | |
201 getElementKindName(element), | |
202 newName); | |
203 result.addError( | |
204 message, | |
205 new RefactoringStatusContext.forElement(element)); | |
206 } | |
207 }); | |
208 } | |
209 | |
210 /** | |
211 * Validates if renamed [element] will shadow any [Element] named [newName]. | |
212 */ | |
213 Future _validateWillShadow() { | |
214 return searchEngine.searchMemberDeclarations(newName).then((declarations) { | |
215 return Future.forEach(declarations, (SearchMatch declaration) { | |
216 Element member = declaration.element; | |
217 ClassElement declaringClass = member.enclosingElement; | |
218 return searchEngine.searchReferences(member).then((memberReferences) { | |
219 for (SearchMatch memberReference in memberReferences) { | |
220 Element refElement = memberReference.element; | |
221 // cannot be shadowed if qualified | |
222 if (memberReference.isQualified) { | |
223 continue; | |
224 } | |
225 // cannot be shadowed if declared in the same class as reference | |
226 ClassElement refClass = | |
227 refElement.getAncestor((e) => e is ClassElement); | |
228 if (refClass == declaringClass) { | |
229 continue; | |
230 } | |
231 // ignore if not visitble | |
232 if (!_isVisibleAt(element, memberReference)) { | |
233 continue; | |
234 } | |
235 // OK, reference will be shadowed be the element being renamed | |
236 String message = | |
237 format( | |
238 forRename ? | |
239 "Renamed {0} will shadow {1} '{2}'." : | |
240 "Created {0} will shadow {1} '{2}'.", | |
241 getElementKindName(element), | |
242 getElementKindName(member), | |
243 getElementQualifiedName(member)); | |
244 result.addError( | |
245 message, | |
246 new RefactoringStatusContext.forMatch(memberReference)); | |
247 } | |
248 }); | |
249 }); | |
250 }); | |
251 } | |
252 } | |
OLD | NEW |