| 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/dart/ast/ast.dart'; | |
| 10 import 'package:analyzer/dart/ast/token.dart'; | |
| 11 import 'package:analyzer/dart/element/element.dart'; | |
| 12 import 'package:analyzer/dart/element/visitor.dart'; | |
| 13 import 'package:analyzer/src/dart/ast/token.dart'; | |
| 14 import 'package:analyzer/src/dart/ast/utilities.dart'; | |
| 15 import 'package:analyzer/src/dart/constant/utilities.dart'; | |
| 16 import 'package:analyzer/src/dart/element/builder.dart'; | |
| 17 import 'package:analyzer/src/dart/element/element.dart'; | |
| 18 import 'package:analyzer/src/generated/resolver.dart'; | |
| 19 import 'package:analyzer/src/generated/source.dart'; | |
| 20 import 'package:analyzer/src/task/dart.dart'; | |
| 21 | |
| 22 /** | |
| 23 * The change of a single [ClassElement]. | |
| 24 */ | |
| 25 class ClassElementDelta { | |
| 26 final ClassElement _element; | |
| 27 final Source librarySource; | |
| 28 final String name; | |
| 29 | |
| 30 final Set<ClassElementDelta> superDeltas = new Set<ClassElementDelta>(); | |
| 31 | |
| 32 bool hasAnnotationChanges = false; | |
| 33 | |
| 34 final List<PropertyAccessorElement> addedAccessors = | |
| 35 <PropertyAccessorElement>[]; | |
| 36 final List<PropertyAccessorElement> removedAccessors = | |
| 37 <PropertyAccessorElement>[]; | |
| 38 | |
| 39 final List<ConstructorElement> addedConstructors = <ConstructorElement>[]; | |
| 40 final List<ConstructorElement> removedConstructors = <ConstructorElement>[]; | |
| 41 bool hasUnnamedConstructorChange = false; | |
| 42 | |
| 43 final List<MethodElement> addedMethods = <MethodElement>[]; | |
| 44 final List<MethodElement> removedMethods = <MethodElement>[]; | |
| 45 | |
| 46 ClassElementDelta(this._element, this.librarySource, this.name); | |
| 47 | |
| 48 /** | |
| 49 * Return `true` if this delta has changes to the [name] visible in the | |
| 50 * given [librarySource]. | |
| 51 */ | |
| 52 bool hasChanges(Source librarySource, String name) { | |
| 53 if (Identifier.isPrivateName(name) && librarySource != this.librarySource) { | |
| 54 return false; | |
| 55 } | |
| 56 return _hasElementWithName(addedAccessors, name) || | |
| 57 _hasElementWithName(removedAccessors, name) || | |
| 58 _hasElementWithName(addedConstructors, name) || | |
| 59 _hasElementWithName(removedConstructors, name) || | |
| 60 _hasElementWithName(addedMethods, name) || | |
| 61 _hasElementWithName(removedMethods, name); | |
| 62 } | |
| 63 | |
| 64 static bool _hasElementWithName(List<Element> elements, String name) { | |
| 65 return elements.any((e) => e.displayName == name); | |
| 66 } | |
| 67 } | |
| 68 | |
| 69 /** | |
| 70 * The change of a single [CompilationUnitElement]. | |
| 71 */ | |
| 72 class CompilationUnitElementDelta { | |
| 73 /** | |
| 74 * One or more directives were added/removed. | |
| 75 */ | |
| 76 bool hasDirectiveChange = false; | |
| 77 | |
| 78 /** | |
| 79 * The list of added top-level element. | |
| 80 */ | |
| 81 final List<Element> addedDeclarations = <Element>[]; | |
| 82 | |
| 83 /** | |
| 84 * The list of removed top-level elements. | |
| 85 */ | |
| 86 final List<Element> removedDeclarations = <Element>[]; | |
| 87 | |
| 88 /** | |
| 89 * The map from names of changed classes to the change deltas. | |
| 90 */ | |
| 91 final Map<String, ClassElementDelta> classDeltas = | |
| 92 <String, ClassElementDelta>{}; | |
| 93 } | |
| 94 | |
| 95 /** | |
| 96 * Incrementally updates the existing [unitElement] and builds elements for | |
| 97 * the [newUnit]. | |
| 98 */ | |
| 99 class IncrementalCompilationUnitElementBuilder { | |
| 100 final Source unitSource; | |
| 101 final Source librarySource; | |
| 102 final CompilationUnit oldUnit; | |
| 103 final CompilationUnitElementImpl unitElement; | |
| 104 final CompilationUnit newUnit; | |
| 105 final ElementHolder unitElementHolder = new ElementHolder(); | |
| 106 | |
| 107 final List<ConstantEvaluationTarget> unitConstants = | |
| 108 <ConstantEvaluationTarget>[]; | |
| 109 | |
| 110 /** | |
| 111 * The change between element models of [oldUnit] and [newUnit]. | |
| 112 */ | |
| 113 final CompilationUnitElementDelta unitDelta = | |
| 114 new CompilationUnitElementDelta(); | |
| 115 | |
| 116 factory IncrementalCompilationUnitElementBuilder( | |
| 117 CompilationUnit oldUnit, CompilationUnit newUnit) { | |
| 118 CompilationUnitElementImpl unitElement = oldUnit.element; | |
| 119 return new IncrementalCompilationUnitElementBuilder._(unitElement.source, | |
| 120 unitElement.librarySource, oldUnit, newUnit, unitElement); | |
| 121 } | |
| 122 | |
| 123 IncrementalCompilationUnitElementBuilder._(this.unitSource, | |
| 124 this.librarySource, this.oldUnit, this.newUnit, this.unitElement); | |
| 125 | |
| 126 /** | |
| 127 * Updates [oldUnit] to have the same directives and declarations, in the | |
| 128 * same order as in [newUnit]. Existing resolution is kept where possible. | |
| 129 * | |
| 130 * Updates [unitElement] by adding/removing elements as needed. | |
| 131 * | |
| 132 * Fills [unitDelta] with added/remove elements. | |
| 133 */ | |
| 134 void build() { | |
| 135 _materializeLazyElements(); | |
| 136 new CompilationUnitBuilder() | |
| 137 .buildCompilationUnit(unitSource, newUnit, librarySource); | |
| 138 newUnit.accept(new EnumMemberBuilder(unitElement.context.typeProvider)); | |
| 139 _processDirectives(); | |
| 140 _processUnitMembers(); | |
| 141 _replaceUnitContents(oldUnit, newUnit); | |
| 142 _findConstants(); | |
| 143 newUnit.element = unitElement; | |
| 144 unitElement.setCodeRange(0, newUnit.endToken.end); | |
| 145 } | |
| 146 | |
| 147 void _addElementToUnitHolder(Element element) { | |
| 148 if (element is ClassElement) { | |
| 149 if (element.isEnum) { | |
| 150 unitElementHolder.addEnum(element); | |
| 151 } else { | |
| 152 unitElementHolder.addType(element); | |
| 153 } | |
| 154 } else if (element is FunctionElement) { | |
| 155 unitElementHolder.addFunction(element); | |
| 156 } else if (element is FunctionTypeAliasElement) { | |
| 157 unitElementHolder.addTypeAlias(element); | |
| 158 } else if (element is PropertyAccessorElement) { | |
| 159 unitElementHolder.addAccessor(element); | |
| 160 } else if (element is TopLevelVariableElement) { | |
| 161 unitElementHolder.addTopLevelVariable(element); | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 void _findConstants() { | |
| 166 ConstantFinder finder = new ConstantFinder(); | |
| 167 oldUnit.accept(finder); | |
| 168 unitConstants.addAll(finder.constantsToCompute); | |
| 169 // Update annotation constants to using the old unit element. | |
| 170 for (ConstantEvaluationTarget constant in unitConstants) { | |
| 171 if (constant is ElementAnnotationImpl) { | |
| 172 constant.compilationUnit = unitElement; | |
| 173 } | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 void _materializeLazyElements() { | |
| 178 unitElement.accept(new RecursiveElementVisitor()); | |
| 179 } | |
| 180 | |
| 181 ClassElementDelta _processClassMembers( | |
| 182 ClassDeclaration oldClass, ClassDeclaration newClass) { | |
| 183 // If the class hierarchy or type parameters are changed, | |
| 184 // then the class changed too much - don't compute the delta. | |
| 185 if (newClass.abstractKeyword != null && oldClass.abstractKeyword == null || | |
| 186 newClass.abstractKeyword == null && oldClass.abstractKeyword != null || | |
| 187 TokenUtils.getFullCode(newClass.typeParameters) != | |
| 188 TokenUtils.getFullCode(oldClass.typeParameters) || | |
| 189 TokenUtils.getFullCode(newClass.extendsClause) != | |
| 190 TokenUtils.getFullCode(oldClass.extendsClause) || | |
| 191 TokenUtils.getFullCode(newClass.withClause) != | |
| 192 TokenUtils.getFullCode(oldClass.withClause) || | |
| 193 TokenUtils.getFullCode(newClass.implementsClause) != | |
| 194 TokenUtils.getFullCode(oldClass.implementsClause)) { | |
| 195 return null; | |
| 196 } | |
| 197 // Build the old class members map. | |
| 198 Map<String, ClassMember> oldNodeMap = new HashMap<String, ClassMember>(); | |
| 199 for (ClassMember oldNode in oldClass.members) { | |
| 200 String code = TokenUtils.getFullCode(oldNode); | |
| 201 oldNodeMap[code] = oldNode; | |
| 202 } | |
| 203 // Prepare elements. | |
| 204 ClassElement newElement = newClass.element; | |
| 205 ClassElement oldElement = oldClass.element; | |
| 206 // Use the old element for the new node. | |
| 207 newClass.name.staticElement = oldElement; | |
| 208 if (newElement is ClassElementImpl && oldElement is ClassElementImpl) { | |
| 209 oldElement.nameOffset = newElement.nameOffset; | |
| 210 oldElement.setCodeRange(newElement.codeOffset, newElement.codeLength); | |
| 211 oldElement.typeParameters = newElement.typeParameters; | |
| 212 } | |
| 213 // Prepare delta. | |
| 214 ClassElementImpl classElement = oldClass.element; | |
| 215 ElementHolder classElementHolder = new ElementHolder(); | |
| 216 ClassElementDelta classDelta = | |
| 217 new ClassElementDelta(classElement, librarySource, classElement.name); | |
| 218 // Check for annotation changes. | |
| 219 { | |
| 220 String oldAnnotationsCode = | |
| 221 TokenUtils.getFullCodeOfList(oldClass.metadata); | |
| 222 String newAnnotationsCode = | |
| 223 TokenUtils.getFullCodeOfList(newClass.metadata); | |
| 224 classDelta.hasAnnotationChanges = | |
| 225 oldAnnotationsCode != newAnnotationsCode; | |
| 226 } | |
| 227 // Prepare all old member elements. | |
| 228 var removedAccessors = new Set<PropertyAccessorElement>.identity(); | |
| 229 var removedConstructors = new Set<ConstructorElement>.identity(); | |
| 230 var removedMethods = new Set<MethodElement>.identity(); | |
| 231 removedAccessors.addAll(classElement.accessors); | |
| 232 removedConstructors.addAll(classElement.constructors); | |
| 233 removedMethods.addAll(classElement.methods); | |
| 234 // Utilities. | |
| 235 void processConstructorDeclaration( | |
| 236 ConstructorDeclaration node, bool isNew) { | |
| 237 ConstructorElement element = node.element; | |
| 238 if (element != null) { | |
| 239 classElementHolder.addConstructor(element); | |
| 240 if (isNew) { | |
| 241 classDelta.addedConstructors.add(element); | |
| 242 } else { | |
| 243 removedConstructors.remove(element); | |
| 244 } | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 void processFieldDeclaration(FieldDeclaration node, bool isNew) { | |
| 249 for (VariableDeclaration field in node.fields.variables) { | |
| 250 PropertyInducingElement element = field.element; | |
| 251 if (element != null) { | |
| 252 PropertyAccessorElement getter = element.getter; | |
| 253 PropertyAccessorElement setter = element.setter; | |
| 254 if (getter != null) { | |
| 255 classElementHolder.addAccessor(getter); | |
| 256 if (isNew) { | |
| 257 classDelta.addedAccessors.add(getter); | |
| 258 } else { | |
| 259 removedAccessors.remove(getter); | |
| 260 } | |
| 261 } | |
| 262 if (setter != null) { | |
| 263 classElementHolder.addAccessor(setter); | |
| 264 if (isNew) { | |
| 265 classDelta.addedAccessors.add(setter); | |
| 266 } else { | |
| 267 removedAccessors.remove(setter); | |
| 268 } | |
| 269 } | |
| 270 } | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 void processMethodDeclaration(MethodDeclaration node, bool isNew) { | |
| 275 Element element = node.element; | |
| 276 if (element is MethodElement) { | |
| 277 classElementHolder.addMethod(element); | |
| 278 if (isNew) { | |
| 279 classDelta.addedMethods.add(element); | |
| 280 } else { | |
| 281 removedMethods.remove(element); | |
| 282 } | |
| 283 } else if (element is PropertyAccessorElement) { | |
| 284 classElementHolder.addAccessor(element); | |
| 285 if (isNew) { | |
| 286 classDelta.addedAccessors.add(element); | |
| 287 } else { | |
| 288 removedAccessors.remove(element); | |
| 289 } | |
| 290 } | |
| 291 } | |
| 292 | |
| 293 // Replace new nodes with the identical old nodes. | |
| 294 bool newHasConstructor = false; | |
| 295 for (ClassMember newNode in newClass.members) { | |
| 296 String code = TokenUtils.getFullCode(newNode); | |
| 297 ClassMember oldNode = oldNodeMap.remove(code); | |
| 298 // When we type a name before a constructor with a documentation | |
| 299 // comment, this makes the comment disappear from AST. So, even though | |
| 300 // tokens are the same, the nodes are not the same. | |
| 301 if (oldNode != null) { | |
| 302 if (oldNode.documentationComment == null && | |
| 303 newNode.documentationComment != null || | |
| 304 oldNode.documentationComment != null && | |
| 305 newNode.documentationComment == null) { | |
| 306 oldNode = null; | |
| 307 } | |
| 308 } | |
| 309 // Add the new element. | |
| 310 if (oldNode == null) { | |
| 311 if (newNode is ConstructorDeclaration) { | |
| 312 newHasConstructor = true; | |
| 313 processConstructorDeclaration(newNode, true); | |
| 314 } | |
| 315 if (newNode is FieldDeclaration) { | |
| 316 processFieldDeclaration(newNode, true); | |
| 317 } | |
| 318 if (newNode is MethodDeclaration) { | |
| 319 processMethodDeclaration(newNode, true); | |
| 320 } | |
| 321 continue; | |
| 322 } | |
| 323 // Do replacement. | |
| 324 _replaceNode(newNode, oldNode); | |
| 325 if (oldNode is ConstructorDeclaration) { | |
| 326 processConstructorDeclaration(oldNode, false); | |
| 327 } | |
| 328 if (oldNode is FieldDeclaration) { | |
| 329 processFieldDeclaration(oldNode, false); | |
| 330 } | |
| 331 if (oldNode is MethodDeclaration) { | |
| 332 processMethodDeclaration(oldNode, false); | |
| 333 } | |
| 334 } | |
| 335 // If the class had only a default synthetic constructor, and there are | |
| 336 // no explicit constructors in the new AST, keep the constructor. | |
| 337 if (!newHasConstructor) { | |
| 338 List<ConstructorElement> constructors = classElement.constructors; | |
| 339 if (constructors.length == 1) { | |
| 340 ConstructorElement constructor = constructors[0]; | |
| 341 if (constructor.isSynthetic && constructor.isDefaultConstructor) { | |
| 342 classElementHolder.addConstructor(constructor); | |
| 343 removedConstructors.remove(constructor); | |
| 344 } | |
| 345 } | |
| 346 } | |
| 347 // Update the delta. | |
| 348 classDelta.removedAccessors.addAll(removedAccessors); | |
| 349 classDelta.removedConstructors.addAll(removedConstructors); | |
| 350 classDelta.removedMethods.addAll(removedMethods); | |
| 351 // Prepare fields. | |
| 352 List<PropertyAccessorElement> newAccessors = classElementHolder.accessors; | |
| 353 Map<String, FieldElement> newFields = <String, FieldElement>{}; | |
| 354 for (PropertyAccessorElement accessor in newAccessors) { | |
| 355 newFields[accessor.displayName] = accessor.variable; | |
| 356 } | |
| 357 // Update references to fields from constructors. | |
| 358 for (ClassMember member in newClass.members) { | |
| 359 if (member is ConstructorDeclaration) { | |
| 360 for (FormalParameter parameter in member.parameters.parameters) { | |
| 361 FormalParameter normalParameter = parameter; | |
| 362 if (parameter is DefaultFormalParameter) { | |
| 363 normalParameter = parameter.parameter; | |
| 364 } | |
| 365 if (normalParameter is FieldFormalParameter) { | |
| 366 FieldFormalParameterElementImpl parameterElement = | |
| 367 normalParameter.element as FieldFormalParameterElementImpl; | |
| 368 parameterElement.field = newFields[parameterElement.name]; | |
| 369 } | |
| 370 } | |
| 371 } | |
| 372 } | |
| 373 // Update ClassElement. | |
| 374 classElement.metadata = newElement.metadata; | |
| 375 classElement.accessors = newAccessors; | |
| 376 classElement.constructors = classElementHolder.constructors; | |
| 377 classElement.fields = newFields.values.toList(); | |
| 378 classElement.methods = classElementHolder.methods; | |
| 379 classElement.version++; | |
| 380 classElementHolder.validate(); | |
| 381 // Ensure at least a default synthetic constructor. | |
| 382 if (classElement.constructors.isEmpty) { | |
| 383 ConstructorElementImpl constructor = | |
| 384 new ConstructorElementImpl.forNode(null); | |
| 385 constructor.isSynthetic = true; | |
| 386 classElement.constructors = <ConstructorElement>[constructor]; | |
| 387 classDelta.addedConstructors.add(constructor); | |
| 388 } | |
| 389 classDelta.hasUnnamedConstructorChange = | |
| 390 classDelta.addedConstructors.any((c) => c.name == '') || | |
| 391 classDelta.removedConstructors.any((c) => c.name == ''); | |
| 392 // OK | |
| 393 return classDelta; | |
| 394 } | |
| 395 | |
| 396 void _processDirectives() { | |
| 397 Map<String, Directive> oldDirectiveMap = new HashMap<String, Directive>(); | |
| 398 for (Directive oldDirective in oldUnit.directives) { | |
| 399 String code = TokenUtils.getFullCode(oldDirective); | |
| 400 oldDirectiveMap[code] = oldDirective; | |
| 401 } | |
| 402 // Replace new nodes with the identical old nodes. | |
| 403 Set<Directive> removedDirectives = oldUnit.directives.toSet(); | |
| 404 for (Directive newDirective in newUnit.directives) { | |
| 405 String code = TokenUtils.getFullCode(newDirective); | |
| 406 // Prepare an old directive. | |
| 407 Directive oldDirective = oldDirectiveMap[code]; | |
| 408 if (oldDirective == null) { | |
| 409 unitDelta.hasDirectiveChange = true; | |
| 410 continue; | |
| 411 } | |
| 412 // URI's must be resolved to the same sources. | |
| 413 if (newDirective is UriBasedDirective && | |
| 414 oldDirective is UriBasedDirective) { | |
| 415 Source source(UriBasedDirective directive) => | |
| 416 directive is NamespaceDirective | |
| 417 ? directive.selectedSource | |
| 418 : directive.uriSource; | |
| 419 if (source(oldDirective) != source(newDirective)) { | |
| 420 continue; | |
| 421 } | |
| 422 } | |
| 423 // Do replacement. | |
| 424 _replaceNode(newDirective, oldDirective); | |
| 425 removedDirectives.remove(oldDirective); | |
| 426 } | |
| 427 // If there are any directives left, then these directives were removed. | |
| 428 if (removedDirectives.isNotEmpty) { | |
| 429 unitDelta.hasDirectiveChange = true; | |
| 430 } | |
| 431 } | |
| 432 | |
| 433 void _processUnitMembers() { | |
| 434 Map<String, CompilationUnitMember> oldNodeMap = | |
| 435 new HashMap<String, CompilationUnitMember>(); | |
| 436 Map<String, ClassDeclaration> nameToOldClassMap = | |
| 437 new HashMap<String, ClassDeclaration>(); | |
| 438 for (CompilationUnitMember oldNode in oldUnit.declarations) { | |
| 439 String code = TokenUtils.getFullCode(oldNode); | |
| 440 oldNodeMap[code] = oldNode; | |
| 441 if (oldNode is ClassDeclaration) { | |
| 442 nameToOldClassMap[oldNode.name.name] = oldNode; | |
| 443 } | |
| 444 } | |
| 445 // Prepare all old top-level elements. | |
| 446 Set<Element> removedElements = new Set<Element>(); | |
| 447 removedElements.addAll(unitElement.accessors); | |
| 448 removedElements.addAll(unitElement.enums); | |
| 449 removedElements.addAll(unitElement.functions); | |
| 450 removedElements.addAll(unitElement.functionTypeAliases); | |
| 451 removedElements.addAll(unitElement.types); | |
| 452 removedElements.addAll(unitElement.topLevelVariables); | |
| 453 // Replace new nodes with the identical old nodes. | |
| 454 for (CompilationUnitMember newNode in newUnit.declarations) { | |
| 455 String code = TokenUtils.getFullCode(newNode); | |
| 456 CompilationUnitMember oldNode = oldNodeMap[code]; | |
| 457 // Add the new element. | |
| 458 if (oldNode == null) { | |
| 459 // Compute a delta for the class. | |
| 460 if (newNode is ClassDeclaration) { | |
| 461 ClassDeclaration oldClass = nameToOldClassMap[newNode.name.name]; | |
| 462 if (oldClass != null) { | |
| 463 ClassElementDelta delta = _processClassMembers(oldClass, newNode); | |
| 464 if (delta != null) { | |
| 465 unitDelta.classDeltas[delta._element.name] = delta; | |
| 466 _addElementToUnitHolder(delta._element); | |
| 467 removedElements.remove(delta._element); | |
| 468 continue; | |
| 469 } | |
| 470 } | |
| 471 } | |
| 472 // Add the new node elements. | |
| 473 List<Element> elements = _getElements(newNode); | |
| 474 elements.forEach(_addElementToUnitHolder); | |
| 475 elements.forEach(unitDelta.addedDeclarations.add); | |
| 476 continue; | |
| 477 } | |
| 478 // Do replacement. | |
| 479 _replaceNode(newNode, oldNode); | |
| 480 List<Element> elements = _getElements(oldNode); | |
| 481 elements.forEach(_addElementToUnitHolder); | |
| 482 elements.forEach(removedElements.remove); | |
| 483 } | |
| 484 unitDelta.removedDeclarations.addAll(removedElements); | |
| 485 // Update CompilationUnitElement. | |
| 486 unitElement.accessors = unitElementHolder.accessors; | |
| 487 unitElement.enums = unitElementHolder.enums; | |
| 488 unitElement.functions = unitElementHolder.functions; | |
| 489 unitElement.typeAliases = unitElementHolder.typeAliases; | |
| 490 unitElement.types = unitElementHolder.types; | |
| 491 unitElement.topLevelVariables = unitElementHolder.topLevelVariables; | |
| 492 unitElementHolder.validate(); | |
| 493 } | |
| 494 | |
| 495 /** | |
| 496 * Replaces [newNode] with [oldNode], updates tokens and elements. | |
| 497 * The nodes must have the same tokens, but offsets may be different. | |
| 498 */ | |
| 499 void _replaceNode(AstNode newNode, AstNode oldNode) { | |
| 500 // Replace node. | |
| 501 NodeReplacer.replace(newNode, oldNode); | |
| 502 // Replace tokens. | |
| 503 Token oldBeginToken = TokenUtils.getBeginTokenNotComment(oldNode); | |
| 504 Token newBeginToken = TokenUtils.getBeginTokenNotComment(newNode); | |
| 505 newBeginToken.previous.setNext(oldBeginToken); | |
| 506 oldNode.endToken.setNext(newNode.endToken.next); | |
| 507 // Change tokens offsets. | |
| 508 Map<int, int> offsetMap = new HashMap<int, int>(); | |
| 509 TokenUtils.copyTokenOffsets(offsetMap, oldBeginToken, newBeginToken, | |
| 510 oldNode.endToken, newNode.endToken); | |
| 511 // Change elements offsets. | |
| 512 { | |
| 513 var visitor = new _UpdateElementOffsetsVisitor(offsetMap); | |
| 514 List<Element> elements = _getElements(oldNode); | |
| 515 for (Element element in elements) { | |
| 516 element.accept(visitor); | |
| 517 } | |
| 518 } | |
| 519 } | |
| 520 | |
| 521 /** | |
| 522 * Returns [Element]s that are declared directly by the given [node]. | |
| 523 * This does not include any child elements - parameters, local variables. | |
| 524 * | |
| 525 * Usually just one [Element] is returned, but [VariableDeclarationList] | |
| 526 * nodes may declare more than one. | |
| 527 */ | |
| 528 static List<Element> _getElements(AstNode node) { | |
| 529 List<Element> elements = <Element>[]; | |
| 530 void addPropertyAccessors(VariableDeclarationList variableList) { | |
| 531 if (variableList != null) { | |
| 532 for (VariableDeclaration variable in variableList.variables) { | |
| 533 PropertyInducingElement element = variable.element; | |
| 534 if (element != null) { | |
| 535 elements.add(element); | |
| 536 if (element.getter != null) { | |
| 537 elements.add(element.getter); | |
| 538 } | |
| 539 if (element.setter != null) { | |
| 540 elements.add(element.setter); | |
| 541 } | |
| 542 } | |
| 543 } | |
| 544 } | |
| 545 } | |
| 546 | |
| 547 if (node is FieldDeclaration) { | |
| 548 addPropertyAccessors(node.fields); | |
| 549 } else if (node is TopLevelVariableDeclaration) { | |
| 550 addPropertyAccessors(node.variables); | |
| 551 } else if (node is PartDirective || node is PartOfDirective) { | |
| 552 // Ignore. | |
| 553 } else if (node is Directive && node.element != null) { | |
| 554 elements.add(node.element); | |
| 555 } else if (node is Declaration && node.element != null) { | |
| 556 Element element = node.element; | |
| 557 elements.add(element); | |
| 558 if (element is PropertyAccessorElement) { | |
| 559 elements.add(element.variable); | |
| 560 } | |
| 561 } | |
| 562 return elements; | |
| 563 } | |
| 564 | |
| 565 /** | |
| 566 * Replaces contents of the [to] unit with the contexts of the [from] unit. | |
| 567 */ | |
| 568 static void _replaceUnitContents(CompilationUnit to, CompilationUnit from) { | |
| 569 to.directives.clear(); | |
| 570 to.declarations.clear(); | |
| 571 to.beginToken = from.beginToken; | |
| 572 to.scriptTag = from.scriptTag; | |
| 573 to.directives.addAll(from.directives); | |
| 574 to.declarations.addAll(from.declarations); | |
| 575 to.element = to.element; | |
| 576 to.lineInfo = from.lineInfo; | |
| 577 to.endToken = from.endToken; | |
| 578 } | |
| 579 } | |
| 580 | |
| 581 /** | |
| 582 * Utilities for [Token] manipulations. | |
| 583 */ | |
| 584 class TokenUtils { | |
| 585 static const String _SEPARATOR = "\uFFFF"; | |
| 586 | |
| 587 /** | |
| 588 * Copy offsets from [newToken]s to [oldToken]s. | |
| 589 */ | |
| 590 static void copyTokenOffsets(Map<int, int> offsetMap, Token oldToken, | |
| 591 Token newToken, Token oldEndToken, Token newEndToken) { | |
| 592 if (oldToken is CommentToken && newToken is CommentToken) { | |
| 593 // Update documentation tokens. | |
| 594 while (oldToken != null) { | |
| 595 offsetMap[oldToken.offset] = newToken.offset; | |
| 596 offsetMap[oldToken.end] = newToken.end; | |
| 597 oldToken.offset = newToken.offset; | |
| 598 // Update (otherwise unlinked) reference tokens in documentation. | |
| 599 if (oldToken is DocumentationCommentToken && | |
| 600 newToken is DocumentationCommentToken) { | |
| 601 List<Token> oldReferences = oldToken.references; | |
| 602 List<Token> newReferences = newToken.references; | |
| 603 assert(oldReferences.length == newReferences.length); | |
| 604 for (int i = 0; i < oldReferences.length; i++) { | |
| 605 Token oldToken = oldReferences[i]; | |
| 606 Token newToken = newReferences[i]; | |
| 607 // For [new Name] the 'Name' token is the reference. | |
| 608 // But we need to process all tokens, including 'new'. | |
| 609 while (oldToken.previous != null && | |
| 610 oldToken.previous.type != TokenType.EOF) { | |
| 611 oldToken = oldToken.previous; | |
| 612 } | |
| 613 while (newToken.previous != null && | |
| 614 newToken.previous.type != TokenType.EOF) { | |
| 615 newToken = newToken.previous; | |
| 616 } | |
| 617 copyTokenOffsets( | |
| 618 offsetMap, oldToken, newToken, oldEndToken, newEndToken); | |
| 619 } | |
| 620 } | |
| 621 // Next tokens. | |
| 622 oldToken = oldToken.next; | |
| 623 newToken = newToken.next; | |
| 624 } | |
| 625 assert(oldToken == null); | |
| 626 assert(newToken == null); | |
| 627 return; | |
| 628 } | |
| 629 while (true) { | |
| 630 if (oldToken.precedingComments != null) { | |
| 631 assert(newToken.precedingComments != null); | |
| 632 copyTokenOffsets(offsetMap, oldToken.precedingComments, | |
| 633 newToken.precedingComments, oldEndToken, newEndToken); | |
| 634 } | |
| 635 offsetMap[oldToken.offset] = newToken.offset; | |
| 636 offsetMap[oldToken.end] = newToken.end; | |
| 637 oldToken.offset = newToken.offset; | |
| 638 if (oldToken.type == TokenType.EOF) { | |
| 639 assert(newToken.type == TokenType.EOF); | |
| 640 break; | |
| 641 } | |
| 642 if (oldToken == oldEndToken) { | |
| 643 assert(newToken == newEndToken); | |
| 644 break; | |
| 645 } | |
| 646 oldToken = oldToken.next; | |
| 647 newToken = newToken.next; | |
| 648 } | |
| 649 } | |
| 650 | |
| 651 static Token getBeginTokenNotComment(AstNode node) { | |
| 652 Token oldBeginToken = node.beginToken; | |
| 653 if (oldBeginToken is CommentToken) { | |
| 654 return oldBeginToken.parent; | |
| 655 } | |
| 656 return oldBeginToken; | |
| 657 } | |
| 658 | |
| 659 /** | |
| 660 * Return the token string of all the [node]. | |
| 661 */ | |
| 662 static String getFullCode(AstNode node) { | |
| 663 if (node == null) { | |
| 664 return ''; | |
| 665 } | |
| 666 List<Token> tokens = getTokens(node); | |
| 667 return joinTokens(tokens); | |
| 668 } | |
| 669 | |
| 670 /** | |
| 671 * Return the token string of all the [nodes]. | |
| 672 */ | |
| 673 static String getFullCodeOfList(List<AstNode> nodes) { | |
| 674 if (nodes == null) { | |
| 675 return ''; | |
| 676 } | |
| 677 return nodes.map(getFullCode).join(_SEPARATOR); | |
| 678 } | |
| 679 | |
| 680 /** | |
| 681 * Returns all tokens (including comments) of the given [node]. | |
| 682 */ | |
| 683 static List<Token> getTokens(AstNode node) { | |
| 684 List<Token> tokens = <Token>[]; | |
| 685 Token token = getBeginTokenNotComment(node); | |
| 686 Token endToken = node.endToken; | |
| 687 while (true) { | |
| 688 // stop if past the end token | |
| 689 if (token.offset > endToken.end) { | |
| 690 break; | |
| 691 } | |
| 692 // append comment tokens | |
| 693 for (Token commentToken = token.precedingComments; | |
| 694 commentToken != null; | |
| 695 commentToken = commentToken.next) { | |
| 696 tokens.add(commentToken); | |
| 697 } | |
| 698 // append token | |
| 699 tokens.add(token); | |
| 700 // next token | |
| 701 if (token == endToken) { | |
| 702 break; | |
| 703 } | |
| 704 token = token.next; | |
| 705 } | |
| 706 return tokens; | |
| 707 } | |
| 708 | |
| 709 static String joinTokens(List<Token> tokens) { | |
| 710 return tokens.map((token) => token.lexeme).join(_SEPARATOR); | |
| 711 } | |
| 712 } | |
| 713 | |
| 714 /** | |
| 715 * Updates name offsets of [Element]s according to the [map]. | |
| 716 */ | |
| 717 class _UpdateElementOffsetsVisitor extends GeneralizingElementVisitor { | |
| 718 final Map<int, int> map; | |
| 719 | |
| 720 _UpdateElementOffsetsVisitor(this.map); | |
| 721 | |
| 722 void visitElement(Element element) { | |
| 723 if (element is ElementImpl) { | |
| 724 // name offset | |
| 725 { | |
| 726 int oldOffset = element.nameOffset; | |
| 727 int newOffset = map[oldOffset]; | |
| 728 // Some synthetic elements have new offsets, e.g. synthetic accessors | |
| 729 // of property inducing elements. But some are purely synthetic, e.g. | |
| 730 // synthetic enum fields and their accessors. | |
| 731 // PrefixElement(s) can be shared between import directives, so | |
| 732 // their name offsets are outside of the second and subsequent import | |
| 733 // directives. But we update the name offsets while visiting the first | |
| 734 // import directive. | |
| 735 if (newOffset == null) { | |
| 736 assert(element.isSynthetic || element is PrefixElement); | |
| 737 return; | |
| 738 } | |
| 739 element.nameOffset = newOffset; | |
| 740 } | |
| 741 // stop here for LibraryElement | |
| 742 if (element is LibraryElementImpl) { | |
| 743 return; | |
| 744 } | |
| 745 // code range | |
| 746 { | |
| 747 int oldOffset = element.codeOffset; | |
| 748 if (oldOffset != null) { | |
| 749 int oldEnd = oldOffset + element.codeLength; | |
| 750 int newOffset = map[oldOffset]; | |
| 751 int newEnd = map[oldEnd]; | |
| 752 assert(newOffset != null); | |
| 753 assert(newEnd != null); | |
| 754 int newLength = newEnd - newOffset; | |
| 755 element.setCodeRange(newOffset, newLength); | |
| 756 } | |
| 757 } | |
| 758 // visible range | |
| 759 if (element is LocalElement) { | |
| 760 SourceRange oldVisibleRange = (element as LocalElement).visibleRange; | |
| 761 if (oldVisibleRange != null) { | |
| 762 int oldOffset = oldVisibleRange.offset; | |
| 763 int oldLength = oldVisibleRange.length; | |
| 764 int oldEnd = oldOffset + oldLength; | |
| 765 int newOffset = map[oldOffset]; | |
| 766 int newEnd = map[oldEnd]; | |
| 767 assert(newOffset != null); | |
| 768 assert(newEnd != null); | |
| 769 int newLength = newEnd - newOffset; | |
| 770 if (newOffset != oldOffset || newLength != oldLength) { | |
| 771 if (element is FunctionElementImpl) { | |
| 772 element.setVisibleRange(newOffset, newLength); | |
| 773 } else if (element is LocalVariableElementImpl) { | |
| 774 element.setVisibleRange(newOffset, newLength); | |
| 775 } else if (element is ParameterElementImpl) { | |
| 776 element.setVisibleRange(newOffset, newLength); | |
| 777 } | |
| 778 } | |
| 779 } | |
| 780 } | |
| 781 } | |
| 782 super.visitElement(element); | |
| 783 } | |
| 784 } | |
| OLD | NEW |