| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library analyzer.src.task.incremental_element_builder; | 5 library analyzer.src.task.incremental_element_builder; |
| 6 | 6 |
| 7 import 'dart:collection'; | 7 import 'dart:collection'; |
| 8 | 8 |
| 9 import 'package:analyzer/src/generated/ast.dart'; | 9 import 'package:analyzer/dart/ast/ast.dart'; |
| 10 import 'package:analyzer/src/generated/element.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'; |
| 11 import 'package:analyzer/src/generated/resolver.dart'; | 18 import 'package:analyzer/src/generated/resolver.dart'; |
| 12 import 'package:analyzer/src/generated/scanner.dart'; | |
| 13 import 'package:analyzer/src/generated/source.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 } |
| 14 | 68 |
| 15 /** | 69 /** |
| 16 * The change of a single [CompilationUnitElement]. | 70 * The change of a single [CompilationUnitElement]. |
| 17 */ | 71 */ |
| 18 class CompilationUnitElementDelta { | 72 class CompilationUnitElementDelta { |
| 19 /** | 73 /** |
| 20 * One or more directives were added/removed. | 74 * One or more directives were added/removed. |
| 21 */ | 75 */ |
| 22 bool hasDirectiveChange = false; | 76 bool hasDirectiveChange = false; |
| 23 | 77 |
| 24 /** | 78 /** |
| 25 * The list of added top-level element. | 79 * The list of added top-level element. |
| 26 */ | 80 */ |
| 27 final List<Element> addedDeclarations = <Element>[]; | 81 final List<Element> addedDeclarations = <Element>[]; |
| 28 | 82 |
| 29 /** | 83 /** |
| 30 * The list of removed top-level elements. | 84 * The list of removed top-level elements. |
| 31 */ | 85 */ |
| 32 final List<Element> removedDeclarations = <Element>[]; | 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>{}; |
| 33 } | 93 } |
| 34 | 94 |
| 35 /** | 95 /** |
| 36 * Incrementally updates the existing [unitElement] and builds elements for | 96 * Incrementally updates the existing [unitElement] and builds elements for |
| 37 * the [newUnit]. | 97 * the [newUnit]. |
| 38 */ | 98 */ |
| 39 class IncrementalCompilationUnitElementBuilder { | 99 class IncrementalCompilationUnitElementBuilder { |
| 40 final Source unitSource; | 100 final Source unitSource; |
| 41 final Source librarySource; | 101 final Source librarySource; |
| 42 final CompilationUnit oldUnit; | 102 final CompilationUnit oldUnit; |
| 43 final CompilationUnitElementImpl unitElement; | 103 final CompilationUnitElementImpl unitElement; |
| 44 final CompilationUnit newUnit; | 104 final CompilationUnit newUnit; |
| 45 final ElementHolder holder = new ElementHolder(); | 105 final ElementHolder unitElementHolder = new ElementHolder(); |
| 106 |
| 107 final List<ConstantEvaluationTarget> unitConstants = |
| 108 <ConstantEvaluationTarget>[]; |
| 46 | 109 |
| 47 /** | 110 /** |
| 48 * The change between element models of [oldUnit] and [newUnit]. | 111 * The change between element models of [oldUnit] and [newUnit]. |
| 49 */ | 112 */ |
| 50 final CompilationUnitElementDelta unitDelta = | 113 final CompilationUnitElementDelta unitDelta = |
| 51 new CompilationUnitElementDelta(); | 114 new CompilationUnitElementDelta(); |
| 52 | 115 |
| 53 factory IncrementalCompilationUnitElementBuilder( | 116 factory IncrementalCompilationUnitElementBuilder( |
| 54 CompilationUnit oldUnit, CompilationUnit newUnit) { | 117 CompilationUnit oldUnit, CompilationUnit newUnit) { |
| 55 CompilationUnitElementImpl unitElement = oldUnit.element; | 118 CompilationUnitElementImpl unitElement = oldUnit.element; |
| 56 return new IncrementalCompilationUnitElementBuilder._(unitElement.source, | 119 return new IncrementalCompilationUnitElementBuilder._(unitElement.source, |
| 57 unitElement.librarySource, oldUnit, newUnit, unitElement); | 120 unitElement.librarySource, oldUnit, newUnit, unitElement); |
| 58 } | 121 } |
| 59 | 122 |
| 60 IncrementalCompilationUnitElementBuilder._(this.unitSource, | 123 IncrementalCompilationUnitElementBuilder._(this.unitSource, |
| 61 this.librarySource, this.oldUnit, this.newUnit, this.unitElement); | 124 this.librarySource, this.oldUnit, this.newUnit, this.unitElement); |
| 62 | 125 |
| 63 /** | 126 /** |
| 64 * Updates [oldUnit] to have the same directives and declarations, in the | 127 * Updates [oldUnit] to have the same directives and declarations, in the |
| 65 * same order as in [newUnit]. Existing resolution is kept where possible. | 128 * same order as in [newUnit]. Existing resolution is kept where possible. |
| 66 * | 129 * |
| 67 * Updates [unitElement] by adding/removing elements as needed. | 130 * Updates [unitElement] by adding/removing elements as needed. |
| 68 * | 131 * |
| 69 * Fills [unitDelta] with added/remove elements. | 132 * Fills [unitDelta] with added/remove elements. |
| 70 */ | 133 */ |
| 71 void build() { | 134 void build() { |
| 135 _materializeLazyElements(); |
| 72 new CompilationUnitBuilder() | 136 new CompilationUnitBuilder() |
| 73 .buildCompilationUnit(unitSource, newUnit, librarySource); | 137 .buildCompilationUnit(unitSource, newUnit, librarySource); |
| 138 newUnit.accept(new EnumMemberBuilder(unitElement.context.typeProvider)); |
| 74 _processDirectives(); | 139 _processDirectives(); |
| 75 _processUnitMembers(); | 140 _processUnitMembers(); |
| 141 _replaceUnitContents(oldUnit, newUnit); |
| 142 _findConstants(); |
| 76 newUnit.element = unitElement; | 143 newUnit.element = unitElement; |
| 77 _replaceUnitContents(oldUnit, newUnit); | 144 unitElement.setCodeRange(0, newUnit.endToken.end); |
| 78 } | 145 } |
| 79 | 146 |
| 80 void _addElementToHolder(Element element) { | 147 void _addElementToUnitHolder(Element element) { |
| 81 if (element is PropertyAccessorElement) { | 148 if (element is ClassElement) { |
| 82 holder.addAccessor(element); | |
| 83 } else if (element is ClassElement) { | |
| 84 if (element.isEnum) { | 149 if (element.isEnum) { |
| 85 holder.addEnum(element); | 150 unitElementHolder.addEnum(element); |
| 86 } else { | 151 } else { |
| 87 holder.addType(element); | 152 unitElementHolder.addType(element); |
| 88 } | 153 } |
| 89 } else if (element is FunctionElement) { | 154 } else if (element is FunctionElement) { |
| 90 holder.addFunction(element); | 155 unitElementHolder.addFunction(element); |
| 91 } else if (element is FunctionTypeAliasElement) { | 156 } else if (element is FunctionTypeAliasElement) { |
| 92 holder.addTypeAlias(element); | 157 unitElementHolder.addTypeAlias(element); |
| 158 } else if (element is PropertyAccessorElement) { |
| 159 unitElementHolder.addAccessor(element); |
| 93 } else if (element is TopLevelVariableElement) { | 160 } else if (element is TopLevelVariableElement) { |
| 94 holder.addTopLevelVariable(element); | 161 unitElementHolder.addTopLevelVariable(element); |
| 95 } | 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.synthetic = 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; |
| 96 } | 394 } |
| 97 | 395 |
| 98 void _processDirectives() { | 396 void _processDirectives() { |
| 99 Map<String, Directive> oldDirectiveMap = new HashMap<String, Directive>(); | 397 Map<String, Directive> oldDirectiveMap = new HashMap<String, Directive>(); |
| 100 for (Directive oldDirective in oldUnit.directives) { | 398 for (Directive oldDirective in oldUnit.directives) { |
| 101 String code = TokenUtils.getFullCode(oldDirective); | 399 String code = TokenUtils.getFullCode(oldDirective); |
| 102 oldDirectiveMap[code] = oldDirective; | 400 oldDirectiveMap[code] = oldDirective; |
| 103 } | 401 } |
| 104 // Replace new nodes with the identical old nodes. | 402 // Replace new nodes with the identical old nodes. |
| 105 Set<Directive> removedDirectives = oldUnit.directives.toSet(); | 403 Set<Directive> removedDirectives = oldUnit.directives.toSet(); |
| 106 for (Directive newDirective in newUnit.directives) { | 404 for (Directive newDirective in newUnit.directives) { |
| 107 String code = TokenUtils.getFullCode(newDirective); | 405 String code = TokenUtils.getFullCode(newDirective); |
| 108 // Prepare an old directive. | 406 // Prepare an old directive. |
| 109 Directive oldDirective = oldDirectiveMap[code]; | 407 Directive oldDirective = oldDirectiveMap[code]; |
| 110 if (oldDirective == null) { | 408 if (oldDirective == null) { |
| 111 unitDelta.hasDirectiveChange = true; | 409 unitDelta.hasDirectiveChange = true; |
| 112 continue; | 410 continue; |
| 113 } | 411 } |
| 114 // URI's must be resolved to the same sources. | 412 // URI's must be resolved to the same sources. |
| 115 if (newDirective is UriBasedDirective && | 413 if (newDirective is UriBasedDirective && |
| 116 oldDirective is UriBasedDirective) { | 414 oldDirective is UriBasedDirective) { |
| 117 if (oldDirective.source != newDirective.source) { | 415 Source source(UriBasedDirective directive) => |
| 416 directive is NamespaceDirective |
| 417 ? directive.selectedSource |
| 418 : directive.uriSource; |
| 419 if (source(oldDirective) != source(newDirective)) { |
| 118 continue; | 420 continue; |
| 119 } | 421 } |
| 120 } | 422 } |
| 121 // Do replacement. | 423 // Do replacement. |
| 122 _replaceNode(newDirective, oldDirective); | 424 _replaceNode(newDirective, oldDirective); |
| 123 removedDirectives.remove(oldDirective); | 425 removedDirectives.remove(oldDirective); |
| 124 } | 426 } |
| 125 // If there are any directives left, then these directives were removed. | 427 // If there are any directives left, then these directives were removed. |
| 126 if (removedDirectives.isNotEmpty) { | 428 if (removedDirectives.isNotEmpty) { |
| 127 unitDelta.hasDirectiveChange = true; | 429 unitDelta.hasDirectiveChange = true; |
| 128 } | 430 } |
| 129 } | 431 } |
| 130 | 432 |
| 131 void _processUnitMembers() { | 433 void _processUnitMembers() { |
| 132 Map<String, CompilationUnitMember> oldNodeMap = | 434 Map<String, CompilationUnitMember> oldNodeMap = |
| 133 new HashMap<String, CompilationUnitMember>(); | 435 new HashMap<String, CompilationUnitMember>(); |
| 436 Map<String, ClassDeclaration> nameToOldClassMap = |
| 437 new HashMap<String, ClassDeclaration>(); |
| 134 for (CompilationUnitMember oldNode in oldUnit.declarations) { | 438 for (CompilationUnitMember oldNode in oldUnit.declarations) { |
| 135 String code = TokenUtils.getFullCode(oldNode); | 439 String code = TokenUtils.getFullCode(oldNode); |
| 136 oldNodeMap[code] = oldNode; | 440 oldNodeMap[code] = oldNode; |
| 441 if (oldNode is ClassDeclaration) { |
| 442 nameToOldClassMap[oldNode.name.name] = oldNode; |
| 443 } |
| 137 } | 444 } |
| 138 // Prepare all old top-level elements. | 445 // Prepare all old top-level elements. |
| 139 Set<Element> removedElements = new Set<Element>(); | 446 Set<Element> removedElements = new Set<Element>(); |
| 140 removedElements.addAll(unitElement.accessors); | 447 removedElements.addAll(unitElement.accessors); |
| 141 removedElements.addAll(unitElement.enums); | 448 removedElements.addAll(unitElement.enums); |
| 142 removedElements.addAll(unitElement.functions); | 449 removedElements.addAll(unitElement.functions); |
| 143 removedElements.addAll(unitElement.functionTypeAliases); | 450 removedElements.addAll(unitElement.functionTypeAliases); |
| 144 removedElements.addAll(unitElement.types); | 451 removedElements.addAll(unitElement.types); |
| 145 removedElements.addAll(unitElement.topLevelVariables); | 452 removedElements.addAll(unitElement.topLevelVariables); |
| 146 // Replace new nodes with the identical old nodes. | 453 // Replace new nodes with the identical old nodes. |
| 147 for (CompilationUnitMember newNode in newUnit.declarations) { | 454 for (CompilationUnitMember newNode in newUnit.declarations) { |
| 148 String code = TokenUtils.getFullCode(newNode); | 455 String code = TokenUtils.getFullCode(newNode); |
| 149 // Prepare an old node. | |
| 150 CompilationUnitMember oldNode = oldNodeMap[code]; | 456 CompilationUnitMember oldNode = oldNodeMap[code]; |
| 457 // Add the new element. |
| 151 if (oldNode == null) { | 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. |
| 152 List<Element> elements = _getElements(newNode); | 473 List<Element> elements = _getElements(newNode); |
| 153 elements.forEach(_addElementToHolder); | 474 elements.forEach(_addElementToUnitHolder); |
| 154 elements.forEach(unitDelta.addedDeclarations.add); | 475 elements.forEach(unitDelta.addedDeclarations.add); |
| 155 continue; | 476 continue; |
| 156 } | 477 } |
| 157 // Do replacement. | 478 // Do replacement. |
| 158 _replaceNode(newNode, oldNode); | 479 _replaceNode(newNode, oldNode); |
| 159 List<Element> elements = _getElements(oldNode); | 480 List<Element> elements = _getElements(oldNode); |
| 160 elements.forEach(_addElementToHolder); | 481 elements.forEach(_addElementToUnitHolder); |
| 161 elements.forEach(removedElements.remove); | 482 elements.forEach(removedElements.remove); |
| 162 } | 483 } |
| 163 unitDelta.removedDeclarations.addAll(removedElements); | 484 unitDelta.removedDeclarations.addAll(removedElements); |
| 164 // Update CompilationUnitElement. | 485 // Update CompilationUnitElement. |
| 165 unitElement.accessors = holder.accessors; | 486 unitElement.accessors = unitElementHolder.accessors; |
| 166 unitElement.enums = holder.enums; | 487 unitElement.enums = unitElementHolder.enums; |
| 167 unitElement.functions = holder.functions; | 488 unitElement.functions = unitElementHolder.functions; |
| 168 unitElement.typeAliases = holder.typeAliases; | 489 unitElement.typeAliases = unitElementHolder.typeAliases; |
| 169 unitElement.types = holder.types; | 490 unitElement.types = unitElementHolder.types; |
| 170 unitElement.topLevelVariables = holder.topLevelVariables; | 491 unitElement.topLevelVariables = unitElementHolder.topLevelVariables; |
| 171 holder.validate(); | 492 unitElementHolder.validate(); |
| 172 } | 493 } |
| 173 | 494 |
| 174 /** | 495 /** |
| 175 * Replaces [newNode] with [oldNode], updates tokens and elements. | 496 * Replaces [newNode] with [oldNode], updates tokens and elements. |
| 176 * The nodes must have the same tokens, but offsets may be different. | 497 * The nodes must have the same tokens, but offsets may be different. |
| 177 */ | 498 */ |
| 178 void _replaceNode(AstNode newNode, AstNode oldNode) { | 499 void _replaceNode(AstNode newNode, AstNode oldNode) { |
| 179 // Replace node. | 500 // Replace node. |
| 180 NodeReplacer.replace(newNode, oldNode); | 501 NodeReplacer.replace(newNode, oldNode); |
| 181 // Replace tokens. | 502 // Replace tokens. |
| 182 { | 503 Token oldBeginToken = TokenUtils.getBeginTokenNotComment(oldNode); |
| 183 Token oldBeginToken = TokenUtils.getBeginTokenNotComment(newNode); | 504 Token newBeginToken = TokenUtils.getBeginTokenNotComment(newNode); |
| 184 Token newBeginToken = TokenUtils.getBeginTokenNotComment(oldNode); | 505 newBeginToken.previous.setNext(oldBeginToken); |
| 185 oldBeginToken.previous.setNext(newBeginToken); | 506 oldNode.endToken.setNext(newNode.endToken.next); |
| 186 oldNode.endToken.setNext(newNode.endToken.next); | |
| 187 } | |
| 188 // Change tokens offsets. | 507 // Change tokens offsets. |
| 189 Map<int, int> offsetMap = new HashMap<int, int>(); | 508 Map<int, int> offsetMap = new HashMap<int, int>(); |
| 190 TokenUtils.copyTokenOffsets(offsetMap, oldNode.beginToken, | 509 TokenUtils.copyTokenOffsets(offsetMap, oldBeginToken, newBeginToken, |
| 191 newNode.beginToken, oldNode.endToken, newNode.endToken, true); | 510 oldNode.endToken, newNode.endToken); |
| 192 // Change elements offsets. | 511 // Change elements offsets. |
| 193 { | 512 { |
| 194 var visitor = new _UpdateElementOffsetsVisitor(offsetMap); | 513 var visitor = new _UpdateElementOffsetsVisitor(offsetMap); |
| 195 List<Element> elements = _getElements(oldNode); | 514 List<Element> elements = _getElements(oldNode); |
| 196 for (Element element in elements) { | 515 for (Element element in elements) { |
| 197 element.accept(visitor); | 516 element.accept(visitor); |
| 198 } | 517 } |
| 199 } | 518 } |
| 200 } | 519 } |
| 201 | 520 |
| 202 /** | 521 /** |
| 203 * Returns [Element]s that are declared directly by the given [node]. | 522 * Returns [Element]s that are declared directly by the given [node]. |
| 204 * This does not include any child elements - parameters, local variables. | 523 * This does not include any child elements - parameters, local variables. |
| 205 * | 524 * |
| 206 * Usually just one [Element] is returned, but [VariableDeclarationList] | 525 * Usually just one [Element] is returned, but [VariableDeclarationList] |
| 207 * nodes may declare more than one. | 526 * nodes may declare more than one. |
| 208 */ | 527 */ |
| 209 static List<Element> _getElements(AstNode node) { | 528 static List<Element> _getElements(AstNode node) { |
| 210 List<Element> elements = <Element>[]; | 529 List<Element> elements = <Element>[]; |
| 211 if (node is TopLevelVariableDeclaration) { | 530 void addPropertyAccessors(VariableDeclarationList variableList) { |
| 212 VariableDeclarationList variableList = node.variables; | |
| 213 if (variableList != null) { | 531 if (variableList != null) { |
| 214 for (VariableDeclaration variable in variableList.variables) { | 532 for (VariableDeclaration variable in variableList.variables) { |
| 215 TopLevelVariableElement element = variable.element; | 533 PropertyInducingElement element = variable.element; |
| 216 elements.add(element); | 534 if (element != null) { |
| 217 if (element.getter != null) { | 535 elements.add(element); |
| 218 elements.add(element.getter); | 536 if (element.getter != null) { |
| 219 } | 537 elements.add(element.getter); |
| 220 if (element.setter != null) { | 538 } |
| 221 elements.add(element.setter); | 539 if (element.setter != null) { |
| 540 elements.add(element.setter); |
| 541 } |
| 222 } | 542 } |
| 223 } | 543 } |
| 224 } | 544 } |
| 545 } |
| 546 |
| 547 if (node is FieldDeclaration) { |
| 548 addPropertyAccessors(node.fields); |
| 549 } else if (node is TopLevelVariableDeclaration) { |
| 550 addPropertyAccessors(node.variables); |
| 225 } else if (node is PartDirective || node is PartOfDirective) { | 551 } else if (node is PartDirective || node is PartOfDirective) { |
| 552 // Ignore. |
| 226 } else if (node is Directive && node.element != null) { | 553 } else if (node is Directive && node.element != null) { |
| 227 elements.add(node.element); | 554 elements.add(node.element); |
| 228 } else if (node is Declaration && node.element != null) { | 555 } else if (node is Declaration && node.element != null) { |
| 229 Element element = node.element; | 556 Element element = node.element; |
| 230 elements.add(element); | 557 elements.add(element); |
| 231 if (element is PropertyAccessorElement) { | 558 if (element is PropertyAccessorElement) { |
| 232 elements.add(element.variable); | 559 elements.add(element.variable); |
| 233 } | 560 } |
| 234 } | 561 } |
| 235 return elements; | 562 return elements; |
| 236 } | 563 } |
| 237 | 564 |
| 238 /** | 565 /** |
| 239 * Replaces contents of the [to] unit with the contenxts of the [from] unit. | 566 * Replaces contents of the [to] unit with the contexts of the [from] unit. |
| 240 */ | 567 */ |
| 241 static void _replaceUnitContents(CompilationUnit to, CompilationUnit from) { | 568 static void _replaceUnitContents(CompilationUnit to, CompilationUnit from) { |
| 242 to.directives.clear(); | 569 to.directives.clear(); |
| 243 to.declarations.clear(); | 570 to.declarations.clear(); |
| 244 to.beginToken = from.beginToken; | 571 to.beginToken = from.beginToken; |
| 245 to.scriptTag = from.scriptTag; | 572 to.scriptTag = from.scriptTag; |
| 246 to.directives.addAll(from.directives); | 573 to.directives.addAll(from.directives); |
| 247 to.declarations.addAll(from.declarations); | 574 to.declarations.addAll(from.declarations); |
| 248 to.element = to.element; | 575 to.element = to.element; |
| 249 to.lineInfo = from.lineInfo; | 576 to.lineInfo = from.lineInfo; |
| 577 to.endToken = from.endToken; |
| 250 } | 578 } |
| 251 } | 579 } |
| 252 | 580 |
| 253 /** | 581 /** |
| 254 * Utilities for [Token] manipulations. | 582 * Utilities for [Token] manipulations. |
| 255 */ | 583 */ |
| 256 class TokenUtils { | 584 class TokenUtils { |
| 257 static const String _SEPARATOR = "\uFFFF"; | 585 static const String _SEPARATOR = "\uFFFF"; |
| 258 | 586 |
| 259 /** | 587 /** |
| 260 * Copy offsets from [newToken]s to [oldToken]s. | 588 * Copy offsets from [newToken]s to [oldToken]s. |
| 261 */ | 589 */ |
| 262 static void copyTokenOffsets(Map<int, int> offsetMap, Token oldToken, | 590 static void copyTokenOffsets(Map<int, int> offsetMap, Token oldToken, |
| 263 Token newToken, Token oldEndToken, Token newEndToken, | 591 Token newToken, Token oldEndToken, Token newEndToken) { |
| 264 [bool goUpComment = false]) { | |
| 265 if (oldToken is CommentToken && newToken is CommentToken) { | 592 if (oldToken is CommentToken && newToken is CommentToken) { |
| 266 if (goUpComment) { | 593 // Update documentation tokens. |
| 267 copyTokenOffsets(offsetMap, (oldToken as CommentToken).parent, | |
| 268 (newToken as CommentToken).parent, oldEndToken, newEndToken); | |
| 269 } | |
| 270 while (oldToken != null) { | 594 while (oldToken != null) { |
| 271 offsetMap[oldToken.offset] = newToken.offset; | 595 offsetMap[oldToken.offset] = newToken.offset; |
| 596 offsetMap[oldToken.end] = newToken.end; |
| 272 oldToken.offset = newToken.offset; | 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. |
| 273 oldToken = oldToken.next; | 622 oldToken = oldToken.next; |
| 274 newToken = newToken.next; | 623 newToken = newToken.next; |
| 275 } | 624 } |
| 276 assert(oldToken == null); | 625 assert(oldToken == null); |
| 277 assert(newToken == null); | 626 assert(newToken == null); |
| 278 return; | 627 return; |
| 279 } | 628 } |
| 280 while (true) { | 629 while (true) { |
| 281 if (oldToken.precedingComments != null) { | 630 if (oldToken.precedingComments != null) { |
| 282 assert(newToken.precedingComments != null); | 631 assert(newToken.precedingComments != null); |
| 283 copyTokenOffsets(offsetMap, oldToken.precedingComments, | 632 copyTokenOffsets(offsetMap, oldToken.precedingComments, |
| 284 newToken.precedingComments, oldEndToken, newEndToken); | 633 newToken.precedingComments, oldEndToken, newEndToken); |
| 285 } | 634 } |
| 286 offsetMap[oldToken.offset] = newToken.offset; | 635 offsetMap[oldToken.offset] = newToken.offset; |
| 636 offsetMap[oldToken.end] = newToken.end; |
| 287 oldToken.offset = newToken.offset; | 637 oldToken.offset = newToken.offset; |
| 638 if (oldToken.type == TokenType.EOF) { |
| 639 assert(newToken.type == TokenType.EOF); |
| 640 break; |
| 641 } |
| 288 if (oldToken == oldEndToken) { | 642 if (oldToken == oldEndToken) { |
| 289 assert(newToken == newEndToken); | 643 assert(newToken == newEndToken); |
| 290 break; | 644 break; |
| 291 } | 645 } |
| 292 oldToken = oldToken.next; | 646 oldToken = oldToken.next; |
| 293 newToken = newToken.next; | 647 newToken = newToken.next; |
| 294 } | 648 } |
| 295 } | 649 } |
| 296 | 650 |
| 297 static Token getBeginTokenNotComment(AstNode node) { | 651 static Token getBeginTokenNotComment(AstNode node) { |
| 298 Token oldBeginToken = node.beginToken; | 652 Token oldBeginToken = node.beginToken; |
| 299 if (oldBeginToken is CommentToken) { | 653 if (oldBeginToken is CommentToken) { |
| 300 oldBeginToken = (oldBeginToken as CommentToken).parent; | 654 return oldBeginToken.parent; |
| 301 } | 655 } |
| 302 return oldBeginToken; | 656 return oldBeginToken; |
| 303 } | 657 } |
| 304 | 658 |
| 305 /** | 659 /** |
| 306 * Return the token string of all the [node] tokens. | 660 * Return the token string of all the [node]. |
| 307 */ | 661 */ |
| 308 static String getFullCode(AstNode node) { | 662 static String getFullCode(AstNode node) { |
| 663 if (node == null) { |
| 664 return ''; |
| 665 } |
| 309 List<Token> tokens = getTokens(node); | 666 List<Token> tokens = getTokens(node); |
| 310 return joinTokens(tokens); | 667 return joinTokens(tokens); |
| 311 } | 668 } |
| 312 | 669 |
| 313 /** | 670 /** |
| 314 * Returns all tokends (including comments) of the given [node]. | 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]. |
| 315 */ | 682 */ |
| 316 static List<Token> getTokens(AstNode node) { | 683 static List<Token> getTokens(AstNode node) { |
| 317 List<Token> tokens = <Token>[]; | 684 List<Token> tokens = <Token>[]; |
| 318 Token token = getBeginTokenNotComment(node); | 685 Token token = getBeginTokenNotComment(node); |
| 319 Token endToken = node.endToken; | 686 Token endToken = node.endToken; |
| 320 while (true) { | 687 while (true) { |
| 688 // stop if past the end token |
| 689 if (token.offset > endToken.end) { |
| 690 break; |
| 691 } |
| 321 // append comment tokens | 692 // append comment tokens |
| 322 for (Token commentToken = token.precedingComments; | 693 for (Token commentToken = token.precedingComments; |
| 323 commentToken != null; | 694 commentToken != null; |
| 324 commentToken = commentToken.next) { | 695 commentToken = commentToken.next) { |
| 325 tokens.add(commentToken); | 696 tokens.add(commentToken); |
| 326 } | 697 } |
| 327 // append token | 698 // append token |
| 328 tokens.add(token); | 699 tokens.add(token); |
| 329 // next token | 700 // next token |
| 330 if (token == endToken) { | 701 if (token == endToken) { |
| (...skipping 11 matching lines...) Expand all Loading... |
| 342 | 713 |
| 343 /** | 714 /** |
| 344 * Updates name offsets of [Element]s according to the [map]. | 715 * Updates name offsets of [Element]s according to the [map]. |
| 345 */ | 716 */ |
| 346 class _UpdateElementOffsetsVisitor extends GeneralizingElementVisitor { | 717 class _UpdateElementOffsetsVisitor extends GeneralizingElementVisitor { |
| 347 final Map<int, int> map; | 718 final Map<int, int> map; |
| 348 | 719 |
| 349 _UpdateElementOffsetsVisitor(this.map); | 720 _UpdateElementOffsetsVisitor(this.map); |
| 350 | 721 |
| 351 void visitElement(Element element) { | 722 void visitElement(Element element) { |
| 352 if (element is CompilationUnitElement) { | 723 if (element is ElementImpl) { |
| 353 return; | 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 } |
| 354 } | 781 } |
| 355 if (element.isSynthetic) { | 782 super.visitElement(element); |
| 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 } | 783 } |
| 366 } | 784 } |
| OLD | NEW |