OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, 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 analyzer.src.task.incremental_element_builder; | |
6 | |
7 import 'dart:collection'; | |
8 | |
9 import 'package:analyzer/src/generated/ast.dart'; | |
10 import 'package:analyzer/src/generated/element.dart'; | |
11 import 'package:analyzer/src/generated/resolver.dart'; | |
12 import 'package:analyzer/src/generated/scanner.dart'; | |
13 import 'package:analyzer/src/generated/source.dart'; | |
14 | |
15 /** | |
16 * The change of a single [CompilationUnitElement]. | |
17 */ | |
18 class CompilationUnitElementDelta { | |
19 /** | |
20 * One or more directives were added/removed. | |
21 */ | |
22 bool hasDirectiveChange = false; | |
23 | |
24 /** | |
25 * The list of added top-level element. | |
26 */ | |
27 final List<Element> addedDeclarations = <Element>[]; | |
28 | |
29 /** | |
30 * The list of removed top-level elements. | |
31 */ | |
32 final List<Element> removedDeclarations = <Element>[]; | |
33 } | |
34 | |
35 /** | |
36 * Incrementally updates the existing [unitElement] and builds elements for | |
37 * the [newUnit]. | |
38 */ | |
39 class IncrementalCompilationUnitElementBuilder { | |
40 final Source unitSource; | |
41 final Source librarySource; | |
42 final CompilationUnit oldUnit; | |
43 final CompilationUnitElementImpl unitElement; | |
44 final CompilationUnit newUnit; | |
45 final ElementHolder holder = new ElementHolder(); | |
46 | |
47 /** | |
48 * The change between element models of [oldUnit] and [newUnit]. | |
49 */ | |
50 final CompilationUnitElementDelta unitDelta = | |
51 new CompilationUnitElementDelta(); | |
52 | |
53 factory IncrementalCompilationUnitElementBuilder( | |
54 CompilationUnit oldUnit, CompilationUnit newUnit) { | |
55 CompilationUnitElementImpl unitElement = oldUnit.element; | |
56 return new IncrementalCompilationUnitElementBuilder._(unitElement.source, | |
57 unitElement.librarySource, oldUnit, newUnit, unitElement); | |
58 } | |
59 | |
60 IncrementalCompilationUnitElementBuilder._(this.unitSource, | |
61 this.librarySource, this.oldUnit, this.newUnit, this.unitElement); | |
62 | |
63 /** | |
64 * Updates [oldUnit] to have the same directives and declarations, in the | |
65 * same order as in [newUnit]. Existing resolution is kept where possible. | |
66 * | |
67 * Updates [unitElement] by adding/removing elements as needed. | |
68 * | |
69 * Fills [unitDelta] with added/remove elements. | |
70 */ | |
71 void build() { | |
72 new CompilationUnitBuilder().buildCompilationUnit( | |
73 unitSource, newUnit, librarySource); | |
74 _processDirectives(); | |
75 _processUnitMembers(); | |
76 newUnit.element = unitElement; | |
77 _replaceUnitContents(oldUnit, newUnit); | |
78 } | |
79 | |
80 void _addElementToHolder(Element element) { | |
81 if (element is PropertyAccessorElement) { | |
82 holder.addAccessor(element); | |
83 } else if (element is ClassElement) { | |
84 if (element.isEnum) { | |
85 holder.addEnum(element); | |
86 } else { | |
87 holder.addType(element); | |
88 } | |
89 } else if (element is FunctionElement) { | |
90 holder.addFunction(element); | |
91 } else if (element is FunctionTypeAliasElement) { | |
92 holder.addTypeAlias(element); | |
93 } else if (element is TopLevelVariableElement) { | |
94 holder.addTopLevelVariable(element); | |
95 } | |
96 } | |
97 | |
98 void _processDirectives() { | |
99 Map<String, Directive> oldDirectiveMap = new HashMap<String, Directive>(); | |
100 for (Directive oldDirective in oldUnit.directives) { | |
101 String code = TokenUtils.getFullCode(oldDirective); | |
102 oldDirectiveMap[code] = oldDirective; | |
103 } | |
104 // Replace new nodes with the identical old nodes. | |
105 Set<Directive> removedDirectives = oldUnit.directives.toSet(); | |
106 for (Directive newDirective in newUnit.directives) { | |
107 String code = TokenUtils.getFullCode(newDirective); | |
108 // Prepare an old directive. | |
109 Directive oldDirective = oldDirectiveMap[code]; | |
110 if (oldDirective == null) { | |
111 unitDelta.hasDirectiveChange = true; | |
112 continue; | |
113 } | |
114 // URI's must be resolved to the same sources. | |
115 if (newDirective is UriBasedDirective && | |
116 oldDirective is UriBasedDirective) { | |
117 if (oldDirective.source != newDirective.source) { | |
118 continue; | |
119 } | |
120 } | |
121 // Do replacement. | |
122 _replaceNode(newDirective, oldDirective); | |
123 removedDirectives.remove(oldDirective); | |
124 } | |
125 // If there are any directives left, then these directives were removed. | |
126 if (removedDirectives.isNotEmpty) { | |
127 unitDelta.hasDirectiveChange = true; | |
128 } | |
129 } | |
130 | |
131 void _processUnitMembers() { | |
132 Map<String, CompilationUnitMember> oldNodeMap = | |
133 new HashMap<String, CompilationUnitMember>(); | |
134 for (CompilationUnitMember oldNode in oldUnit.declarations) { | |
135 String code = TokenUtils.getFullCode(oldNode); | |
136 oldNodeMap[code] = oldNode; | |
137 } | |
138 // Prepare all old top-level elements. | |
139 Set<Element> removedElements = new Set<Element>(); | |
140 removedElements.addAll(unitElement.accessors); | |
141 removedElements.addAll(unitElement.enums); | |
142 removedElements.addAll(unitElement.functions); | |
143 removedElements.addAll(unitElement.functionTypeAliases); | |
144 removedElements.addAll(unitElement.types); | |
145 removedElements.addAll(unitElement.topLevelVariables); | |
146 // Replace new nodes with the identical old nodes. | |
147 for (CompilationUnitMember newNode in newUnit.declarations) { | |
148 String code = TokenUtils.getFullCode(newNode); | |
149 // Prepare an old node. | |
150 CompilationUnitMember oldNode = oldNodeMap[code]; | |
151 if (oldNode == null) { | |
152 List<Element> elements = _getElements(newNode); | |
153 elements.forEach(_addElementToHolder); | |
154 elements.forEach(unitDelta.addedDeclarations.add); | |
155 continue; | |
156 } | |
157 // Do replacement. | |
158 _replaceNode(newNode, oldNode); | |
159 List<Element> elements = _getElements(oldNode); | |
160 elements.forEach(_addElementToHolder); | |
161 elements.forEach(removedElements.remove); | |
162 } | |
163 unitDelta.removedDeclarations.addAll(removedElements); | |
164 // Update CompilationUnitElement. | |
165 unitElement.accessors = holder.accessors; | |
166 unitElement.enums = holder.enums; | |
167 unitElement.functions = holder.functions; | |
168 unitElement.typeAliases = holder.typeAliases; | |
169 unitElement.types = holder.types; | |
170 unitElement.topLevelVariables = holder.topLevelVariables; | |
171 holder.validate(); | |
172 } | |
173 | |
174 /** | |
175 * Replaces [newNode] with [oldNode], updates tokens and elements. | |
176 * The nodes must have the same tokens, but offsets may be different. | |
177 */ | |
178 void _replaceNode(AstNode newNode, AstNode oldNode) { | |
179 // Replace node. | |
180 NodeReplacer.replace(newNode, oldNode); | |
181 // Replace tokens. | |
182 { | |
183 Token oldBeginToken = TokenUtils.getBeginTokenNotComment(newNode); | |
184 Token newBeginToken = TokenUtils.getBeginTokenNotComment(oldNode); | |
185 oldBeginToken.previous.setNext(newBeginToken); | |
186 oldNode.endToken.setNext(newNode.endToken.next); | |
187 } | |
188 // Change tokens offsets. | |
189 Map<int, int> offsetMap = new HashMap<int, int>(); | |
190 TokenUtils.copyTokenOffsets(offsetMap, oldNode.beginToken, | |
191 newNode.beginToken, oldNode.endToken, newNode.endToken, true); | |
192 // Change elements offsets. | |
193 { | |
194 var visitor = new _UpdateElementOffsetsVisitor(offsetMap); | |
195 List<Element> elements = _getElements(oldNode); | |
196 for (Element element in elements) { | |
197 element.accept(visitor); | |
198 } | |
199 } | |
200 } | |
201 | |
202 /** | |
203 * Returns [Element]s that are declared directly by the given [node]. | |
204 * This does not include any child elements - parameters, local variables. | |
205 * | |
206 * Usually just one [Element] is returned, but [VariableDeclarationList] | |
207 * nodes may declare more than one. | |
208 */ | |
209 static List<Element> _getElements(AstNode node) { | |
210 List<Element> elements = <Element>[]; | |
211 if (node is TopLevelVariableDeclaration) { | |
212 VariableDeclarationList variableList = node.variables; | |
213 if (variableList != null) { | |
214 for (VariableDeclaration variable in variableList.variables) { | |
215 TopLevelVariableElement element = variable.element; | |
216 elements.add(element); | |
217 if (element.getter != null) { | |
218 elements.add(element.getter); | |
219 } | |
220 if (element.setter != null) { | |
221 elements.add(element.setter); | |
222 } | |
223 } | |
224 } | |
225 } else if (node is PartDirective || node is PartOfDirective) { | |
226 } else if (node is Directive && node.element != null) { | |
227 elements.add(node.element); | |
228 } else if (node is Declaration && node.element != null) { | |
229 Element element = node.element; | |
230 elements.add(element); | |
231 if (element is PropertyAccessorElement) { | |
232 elements.add(element.variable); | |
233 } | |
234 } | |
235 return elements; | |
236 } | |
237 | |
238 /** | |
239 * Replaces contents of the [to] unit with the contenxts of the [from] unit. | |
240 */ | |
241 static void _replaceUnitContents(CompilationUnit to, CompilationUnit from) { | |
242 to.directives.clear(); | |
243 to.declarations.clear(); | |
244 to.beginToken = from.beginToken; | |
245 to.scriptTag = from.scriptTag; | |
246 to.directives.addAll(from.directives); | |
247 to.declarations.addAll(from.declarations); | |
248 to.element = to.element; | |
249 to.lineInfo = from.lineInfo; | |
250 } | |
251 } | |
252 | |
253 /** | |
254 * Utilities for [Token] manipulations. | |
255 */ | |
256 class TokenUtils { | |
257 static const String _SEPARATOR = "\uFFFF"; | |
258 | |
259 /** | |
260 * Copy offsets from [newToken]s to [oldToken]s. | |
261 */ | |
262 static void copyTokenOffsets(Map<int, int> offsetMap, Token oldToken, | |
263 Token newToken, Token oldEndToken, Token newEndToken, | |
264 [bool goUpComment = false]) { | |
265 if (oldToken is CommentToken && newToken is CommentToken) { | |
266 if (goUpComment) { | |
267 copyTokenOffsets(offsetMap, (oldToken as CommentToken).parent, | |
268 (newToken as CommentToken).parent, oldEndToken, newEndToken); | |
269 } | |
270 while (oldToken != null) { | |
271 offsetMap[oldToken.offset] = newToken.offset; | |
272 oldToken.offset = newToken.offset; | |
273 oldToken = oldToken.next; | |
274 newToken = newToken.next; | |
275 } | |
276 assert(oldToken == null); | |
277 assert(newToken == null); | |
278 return; | |
279 } | |
280 while (true) { | |
281 if (oldToken.precedingComments != null) { | |
282 assert(newToken.precedingComments != null); | |
283 copyTokenOffsets(offsetMap, oldToken.precedingComments, | |
284 newToken.precedingComments, oldEndToken, newEndToken); | |
285 } | |
286 offsetMap[oldToken.offset] = newToken.offset; | |
287 oldToken.offset = newToken.offset; | |
288 if (oldToken == oldEndToken) { | |
289 assert(newToken == newEndToken); | |
290 break; | |
291 } | |
292 oldToken = oldToken.next; | |
293 newToken = newToken.next; | |
294 } | |
295 } | |
296 | |
297 static Token getBeginTokenNotComment(AstNode node) { | |
298 Token oldBeginToken = node.beginToken; | |
299 if (oldBeginToken is CommentToken) { | |
300 oldBeginToken = (oldBeginToken as CommentToken).parent; | |
301 } | |
302 return oldBeginToken; | |
303 } | |
304 | |
305 /** | |
306 * Return the token string of all the [node] tokens. | |
307 */ | |
308 static String getFullCode(AstNode node) { | |
309 List<Token> tokens = getTokens(node); | |
310 return joinTokens(tokens); | |
311 } | |
312 | |
313 /** | |
314 * Returns all tokends (including comments) of the given [node]. | |
315 */ | |
316 static List<Token> getTokens(AstNode node) { | |
317 List<Token> tokens = <Token>[]; | |
318 Token token = getBeginTokenNotComment(node); | |
319 Token endToken = node.endToken; | |
320 while (true) { | |
321 // append comment tokens | |
322 for (Token commentToken = token.precedingComments; | |
323 commentToken != null; | |
324 commentToken = commentToken.next) { | |
325 tokens.add(commentToken); | |
326 } | |
327 // append token | |
328 tokens.add(token); | |
329 // next token | |
330 if (token == endToken) { | |
331 break; | |
332 } | |
333 token = token.next; | |
334 } | |
335 return tokens; | |
336 } | |
337 | |
338 static String joinTokens(List<Token> tokens) { | |
339 return tokens.map((token) => token.lexeme).join(_SEPARATOR); | |
340 } | |
341 } | |
342 | |
343 /** | |
344 * Updates name offsets of [Element]s according to the [map]. | |
345 */ | |
346 class _UpdateElementOffsetsVisitor extends GeneralizingElementVisitor { | |
347 final Map<int, int> map; | |
348 | |
349 _UpdateElementOffsetsVisitor(this.map); | |
350 | |
351 void visitElement(Element element) { | |
352 if (element is CompilationUnitElement) { | |
353 return; | |
354 } | |
355 if (element.isSynthetic) { | |
356 return; | |
357 } | |
358 int oldOffset = element.nameOffset; | |
359 int newOffset = map[oldOffset]; | |
360 assert(newOffset != null); | |
361 (element as ElementImpl).nameOffset = newOffset; | |
362 if (element is! LibraryElement) { | |
363 super.visitElement(element); | |
364 } | |
365 } | |
366 } | |
OLD | NEW |