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_class_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/hierarchy.dart'; | |
13 import 'package:analysis_services/search/search_engine.dart'; | |
14 import 'package:analysis_services/src/correction/util.dart'; | |
15 import 'package:analysis_services/src/refactoring/naming_conventions.dart'; | |
16 import 'package:analysis_services/src/refactoring/rename.dart'; | |
17 import 'package:analyzer/src/generated/element.dart'; | |
18 import 'package:analyzer/src/generated/java_core.dart'; | |
19 | |
20 | |
21 /** | |
22 * A [Refactoring] for renaming class member [Element]s. | |
23 */ | |
24 class RenameClassMemberRefactoringImpl extends RenameRefactoringImpl { | |
25 _RenameClassMemberValidator _validator; | |
26 | |
27 RenameClassMemberRefactoringImpl(SearchEngine searchEngine, Element element) | |
28 : super(searchEngine, element); | |
29 | |
30 @override | |
31 String get refactoringName { | |
32 if (element is TypeParameterElement) { | |
33 return "Rename Type Parameter"; | |
34 } | |
35 if (element is FieldElement) { | |
36 return "Rename Field"; | |
37 } | |
38 return "Rename Method"; | |
39 } | |
40 | |
41 @override | |
42 Future<RefactoringStatus> checkFinalConditions() { | |
43 _validator = new _RenameClassMemberValidator( | |
44 searchEngine, | |
45 element, | |
46 newName); | |
47 return _validator.validate(); | |
48 } | |
49 | |
50 @override | |
51 Future<RefactoringStatus> checkInitialConditions() { | |
52 RefactoringStatus result = new RefactoringStatus(); | |
53 if (element is MethodElement && (element as MethodElement).isOperator) { | |
54 result.addFatalError('Cannot rename operator.'); | |
55 } | |
56 return new Future.value(result); | |
57 } | |
58 | |
59 @override | |
60 RefactoringStatus checkNewName() { | |
61 RefactoringStatus result = super.checkNewName(); | |
62 if (element is FieldElement) { | |
63 FieldElement fieldElement = element as FieldElement; | |
64 if (fieldElement.isStatic && fieldElement.isConst) { | |
65 result.addStatus(validateConstantName(newName)); | |
66 } else { | |
67 result.addStatus(validateFieldName(newName)); | |
68 } | |
69 } | |
70 if (element is MethodElement) { | |
71 result.addStatus(validateMethodName(newName)); | |
72 } | |
73 return result; | |
74 } | |
75 | |
76 @override | |
77 Future<Change> createChange() { | |
78 Change change = new Change(refactoringName); | |
79 // update declarations | |
80 for (Element renameElement in _validator.elements) { | |
81 if (renameElement.isSynthetic && renameElement is FieldElement) { | |
82 addDeclarationEdit(change, renameElement.getter); | |
83 addDeclarationEdit(change, renameElement.setter); | |
84 } else { | |
85 addDeclarationEdit(change, renameElement); | |
86 } | |
87 } | |
88 // update references | |
89 List<SourceReference> references = | |
90 getSourceReferences(_validator.references); | |
91 for (SourceReference reference in references) { | |
92 addReferenceEdit(change, reference); | |
93 } | |
94 // potential matches | |
95 return searchEngine.searchMemberReferences(oldName).then((nameMatches) { | |
96 List<SourceReference> nameRefs = getSourceReferences(nameMatches); | |
97 for (SourceReference reference in nameRefs) { | |
98 // ignore resolved reference, we have already updated it | |
99 if (reference.isResolved) { | |
100 continue; | |
101 } | |
102 // check the element being renamed is accessible | |
103 { | |
104 LibraryElement whereLibrary = reference.element.library; | |
105 if (!element.isAccessibleIn(whereLibrary)) { | |
106 continue; | |
107 } | |
108 } | |
109 // add edit | |
110 Edit edit = createReferenceEdit(reference, newName); | |
111 _markEditAsPotential(edit); | |
112 change.addEdit(reference.file, edit); | |
113 } | |
114 }).then((_) => change); | |
115 } | |
116 | |
117 void _markEditAsPotential(Edit edit) { | |
118 String id = potentialEditIds.length.toString(); | |
119 potentialEditIds.add(id); | |
120 edit.id = id; | |
121 } | |
122 } | |
123 | |
124 | |
125 /** | |
126 * Helper to check if renaming of an [Element] to the given name will cause any | |
127 * problems. | |
128 */ | |
129 class _RenameClassMemberValidator { | |
130 final SearchEngine searchEngine; | |
131 final Element element; | |
132 final String oldName; | |
133 final String newName; | |
134 | |
135 Set<Element> elements = new Set(); | |
136 List<SearchMatch> references = []; | |
137 | |
138 _RenameClassMemberValidator(this.searchEngine, Element element, this.newName) | |
139 : element = element, | |
140 oldName = element.displayName; | |
141 | |
142 Future<RefactoringStatus> validate() { | |
143 RefactoringStatus result = new RefactoringStatus(); | |
144 ClassElement elementClass = element.enclosingElement; | |
145 // check if there is a member with "newName" in the same ClassElement | |
146 for (Element newNameMember in getChildren(elementClass, newName)) { | |
147 result.addError( | |
148 format( | |
149 "Class '{0}' already declares {1} with name '{2}'.", | |
150 elementClass.displayName, | |
151 getElementKindName(newNameMember), | |
152 newName), | |
153 new RefactoringStatusContext.forElement(newNameMember)); | |
154 } | |
155 // do chained computations | |
156 Set<ClassElement> superClasses = getSuperClasses(elementClass); | |
157 Set<ClassElement> subClasses; | |
158 return _prepareReferences().then((_) { | |
159 return getSubClasses(searchEngine, elementClass).then((_subs) { | |
160 subClasses = _subs; | |
161 }); | |
162 }).then((_) { | |
163 // check shadowing in hierarchy | |
164 return searchEngine.searchElementDeclarations(newName).then((decls) { | |
165 for (SearchMatch decl in decls) { | |
166 Element nameElement = getSyntheticAccessorVariable(decl.element); | |
167 Element nameClass = nameElement.enclosingElement; | |
168 // renamed Element shadows member of superclass | |
169 if (superClasses.contains(nameClass)) { | |
170 result.addError( | |
171 format( | |
172 "Renamed {0} will shadow {1} '{2}'.", | |
173 getElementKindName(element), | |
174 getElementKindName(nameElement), | |
175 getElementQualifiedName(nameElement)), | |
176 new RefactoringStatusContext.forElement(nameElement)); | |
177 } | |
178 // renamed Element is shadowed by member of subclass | |
179 if (subClasses.contains(nameClass)) { | |
180 result.addError( | |
181 format( | |
182 "Renamed {0} will be shadowed by {1} '{2}'.", | |
183 getElementKindName(element), | |
184 getElementKindName(nameElement), | |
185 getElementQualifiedName(nameElement)), | |
186 new RefactoringStatusContext.forElement(nameElement)); | |
187 } | |
188 // renamed Element is shadowed by local | |
189 if (nameElement is LocalElement) { | |
190 LocalElement localElement = nameElement; | |
191 ClassElement enclosingClass = | |
192 nameElement.getAncestor((element) => element is ClassElement); | |
193 if (enclosingClass == elementClass || | |
194 subClasses.contains(enclosingClass)) { | |
195 for (SearchMatch reference in references) { | |
196 if (isReferenceInLocalRange(localElement, reference)) { | |
197 result.addError( | |
198 format( | |
199 "Usage of renamed {0} will be shadowed by {1} '{2}'.", | |
200 getElementKindName(element), | |
201 getElementKindName(localElement), | |
202 localElement.displayName), | |
203 new RefactoringStatusContext.forMatch(reference)); | |
204 } | |
205 } | |
206 } | |
207 } | |
208 } | |
209 }); | |
210 }).then((_) => result); | |
211 } | |
212 | |
213 /** | |
214 * Fills [elements] with [Element]s to rename. | |
215 */ | |
216 Future _prepareElements() { | |
217 if (element is ClassMemberElement) { | |
218 return getHierarchyMembers( | |
219 searchEngine, | |
220 element).then((Set<Element> elements) { | |
221 this.elements = elements; | |
222 }); | |
223 } else { | |
224 elements = new Set.from([element]); | |
225 return new Future.value(); | |
226 } | |
227 } | |
228 | |
229 /** | |
230 * Fills [references] with all references to [elements]. | |
231 */ | |
232 Future _prepareReferences() { | |
233 return _prepareElements().then((_) { | |
234 return Future.forEach(elements, (Element element) { | |
235 return searchEngine.searchReferences(element).then((references) { | |
236 this.references.addAll(references); | |
237 }); | |
238 }); | |
239 }); | |
240 } | |
241 } | |
OLD | NEW |