| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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.generated.incremental_resolver; | 5 library analyzer.src.generated.incremental_resolver; |
| 6 | 6 |
| 7 import 'dart:collection'; |
| 8 import 'dart:math' as math; |
| 9 |
| 7 import 'package:analyzer/dart/ast/ast.dart'; | 10 import 'package:analyzer/dart/ast/ast.dart'; |
| 11 import 'package:analyzer/dart/ast/token.dart'; |
| 8 import 'package:analyzer/dart/element/element.dart'; | 12 import 'package:analyzer/dart/element/element.dart'; |
| 13 import 'package:analyzer/dart/element/visitor.dart'; |
| 14 import 'package:analyzer/error/error.dart'; |
| 15 import 'package:analyzer/error/listener.dart'; |
| 9 import 'package:analyzer/exception/exception.dart'; | 16 import 'package:analyzer/exception/exception.dart'; |
| 17 import 'package:analyzer/src/context/cache.dart'; |
| 18 import 'package:analyzer/src/dart/ast/token.dart'; |
| 19 import 'package:analyzer/src/dart/ast/utilities.dart'; |
| 20 import 'package:analyzer/src/dart/element/builder.dart'; |
| 21 import 'package:analyzer/src/dart/element/element.dart'; |
| 22 import 'package:analyzer/src/dart/resolver/inheritance_manager.dart'; |
| 23 import 'package:analyzer/src/dart/scanner/reader.dart'; |
| 24 import 'package:analyzer/src/dart/scanner/scanner.dart'; |
| 25 import 'package:analyzer/src/generated/constant.dart'; |
| 26 import 'package:analyzer/src/generated/engine.dart'; |
| 27 import 'package:analyzer/src/generated/error_verifier.dart'; |
| 28 import 'package:analyzer/src/generated/incremental_logger.dart' |
| 29 show logger, LoggingTimer; |
| 30 import 'package:analyzer/src/generated/parser.dart'; |
| 10 import 'package:analyzer/src/generated/resolver.dart'; | 31 import 'package:analyzer/src/generated/resolver.dart'; |
| 32 import 'package:analyzer/src/generated/source.dart'; |
| 33 import 'package:analyzer/src/generated/utilities_dart.dart'; |
| 34 import 'package:analyzer/src/task/dart.dart'; |
| 35 import 'package:analyzer/task/dart.dart'; |
| 36 import 'package:analyzer/task/general.dart' show CONTENT, LINE_INFO; |
| 37 import 'package:analyzer/task/model.dart'; |
| 38 |
| 39 /** |
| 40 * The [Delta] implementation used by incremental resolver. |
| 41 * It keeps Dart results that are either don't change or are updated. |
| 42 */ |
| 43 class IncrementalBodyDelta extends Delta { |
| 44 /** |
| 45 * The offset of the changed contents. |
| 46 */ |
| 47 final int updateOffset; |
| 48 |
| 49 /** |
| 50 * The end of the changed contents in the old unit. |
| 51 */ |
| 52 final int updateEndOld; |
| 53 |
| 54 /** |
| 55 * The end of the changed contents in the new unit. |
| 56 */ |
| 57 final int updateEndNew; |
| 58 |
| 59 /** |
| 60 * The delta between [updateEndNew] and [updateEndOld]. |
| 61 */ |
| 62 final int updateDelta; |
| 63 |
| 64 IncrementalBodyDelta(Source source, this.updateOffset, this.updateEndOld, |
| 65 this.updateEndNew, this.updateDelta) |
| 66 : super(source); |
| 67 |
| 68 @override |
| 69 DeltaResult validate(InternalAnalysisContext context, AnalysisTarget target, |
| 70 ResultDescriptor descriptor, Object value) { |
| 71 // A body change delta should never leak outside its source. |
| 72 // It can cause invalidation of results (e.g. hints) in other sources, |
| 73 // but only when a result in the updated source is INVALIDATE_NO_DELTA. |
| 74 if (target.source != source) { |
| 75 return DeltaResult.STOP; |
| 76 } |
| 77 // don't invalidate results of standard Dart tasks |
| 78 bool isByTask(TaskDescriptor taskDescriptor) { |
| 79 return taskDescriptor.results.contains(descriptor); |
| 80 } |
| 81 |
| 82 if (descriptor == CONTENT) { |
| 83 return DeltaResult.KEEP_CONTINUE; |
| 84 } |
| 85 if (target is LibrarySpecificUnit && target.unit != source) { |
| 86 if (isByTask(GatherUsedLocalElementsTask.DESCRIPTOR) || |
| 87 isByTask(GatherUsedImportedElementsTask.DESCRIPTOR)) { |
| 88 return DeltaResult.KEEP_CONTINUE; |
| 89 } |
| 90 } |
| 91 if (isByTask(BuildCompilationUnitElementTask.DESCRIPTOR) || |
| 92 isByTask(BuildDirectiveElementsTask.DESCRIPTOR) || |
| 93 isByTask(BuildEnumMemberElementsTask.DESCRIPTOR) || |
| 94 isByTask(BuildExportNamespaceTask.DESCRIPTOR) || |
| 95 isByTask(BuildLibraryElementTask.DESCRIPTOR) || |
| 96 isByTask(BuildPublicNamespaceTask.DESCRIPTOR) || |
| 97 isByTask(BuildSourceExportClosureTask.DESCRIPTOR) || |
| 98 isByTask(ComputeConstantDependenciesTask.DESCRIPTOR) || |
| 99 isByTask(ComputeConstantValueTask.DESCRIPTOR) || |
| 100 isByTask(ComputeInferableStaticVariableDependenciesTask.DESCRIPTOR) || |
| 101 isByTask(ComputeLibraryCycleTask.DESCRIPTOR) || |
| 102 isByTask(DartErrorsTask.DESCRIPTOR) || |
| 103 isByTask(ReadyLibraryElement2Task.DESCRIPTOR) || |
| 104 isByTask(ReadyLibraryElement5Task.DESCRIPTOR) || |
| 105 isByTask(ReadyLibraryElement7Task.DESCRIPTOR) || |
| 106 isByTask(ReadyResolvedUnitTask.DESCRIPTOR) || |
| 107 isByTask(EvaluateUnitConstantsTask.DESCRIPTOR) || |
| 108 isByTask(GenerateHintsTask.DESCRIPTOR) || |
| 109 isByTask(InferInstanceMembersInUnitTask.DESCRIPTOR) || |
| 110 isByTask(InferStaticVariableTypesInUnitTask.DESCRIPTOR) || |
| 111 isByTask(InferStaticVariableTypeTask.DESCRIPTOR) || |
| 112 isByTask(LibraryErrorsReadyTask.DESCRIPTOR) || |
| 113 isByTask(LibraryUnitErrorsTask.DESCRIPTOR) || |
| 114 isByTask(ParseDartTask.DESCRIPTOR) || |
| 115 isByTask(PartiallyResolveUnitReferencesTask.DESCRIPTOR) || |
| 116 isByTask(ScanDartTask.DESCRIPTOR) || |
| 117 isByTask(ResolveConstantExpressionTask.DESCRIPTOR) || |
| 118 isByTask(ResolveDirectiveElementsTask.DESCRIPTOR) || |
| 119 isByTask(ResolvedUnit7InLibraryClosureTask.DESCRIPTOR) || |
| 120 isByTask(ResolvedUnit7InLibraryTask.DESCRIPTOR) || |
| 121 isByTask(ResolveInstanceFieldsInUnitTask.DESCRIPTOR) || |
| 122 isByTask(ResolveLibraryReferencesTask.DESCRIPTOR) || |
| 123 isByTask(ResolveLibraryTask.DESCRIPTOR) || |
| 124 isByTask(ResolveLibraryTypeNamesTask.DESCRIPTOR) || |
| 125 isByTask(ResolveTopLevelLibraryTypeBoundsTask.DESCRIPTOR) || |
| 126 isByTask(ResolveTopLevelUnitTypeBoundsTask.DESCRIPTOR) || |
| 127 isByTask(ResolveUnitTask.DESCRIPTOR) || |
| 128 isByTask(ResolveUnitTypeNamesTask.DESCRIPTOR) || |
| 129 isByTask(ResolveVariableReferencesTask.DESCRIPTOR) || |
| 130 isByTask(StrongModeVerifyUnitTask.DESCRIPTOR) || |
| 131 isByTask(VerifyUnitTask.DESCRIPTOR)) { |
| 132 return DeltaResult.KEEP_CONTINUE; |
| 133 } |
| 134 // invalidate all the other results |
| 135 return DeltaResult.INVALIDATE_NO_DELTA; |
| 136 } |
| 137 } |
| 138 |
| 139 /** |
| 140 * Instances of the class [IncrementalResolver] resolve the smallest portion of |
| 141 * an AST structure that we currently know how to resolve. |
| 142 */ |
| 143 class IncrementalResolver { |
| 144 /** |
| 145 * The element of the compilation unit being resolved. |
| 146 */ |
| 147 final CompilationUnitElementImpl _definingUnit; |
| 148 |
| 149 /** |
| 150 * The context the compilation unit being resolved in. |
| 151 */ |
| 152 final AnalysisContext _context; |
| 153 |
| 154 /** |
| 155 * The object used to access the types from the core library. |
| 156 */ |
| 157 final TypeProvider _typeProvider; |
| 158 |
| 159 /** |
| 160 * The type system primitives. |
| 161 */ |
| 162 final TypeSystem _typeSystem; |
| 163 |
| 164 /** |
| 165 * The element for the library containing the compilation unit being resolved. |
| 166 */ |
| 167 final LibraryElementImpl _definingLibrary; |
| 168 |
| 169 final AnalysisCache _cache; |
| 170 |
| 171 /** |
| 172 * The [CacheEntry] corresponding to the source being resolved. |
| 173 */ |
| 174 final CacheEntry newSourceEntry; |
| 175 |
| 176 /** |
| 177 * The [CacheEntry] corresponding to the [LibrarySpecificUnit] being resolved. |
| 178 */ |
| 179 final CacheEntry newUnitEntry; |
| 180 |
| 181 /** |
| 182 * The source representing the compilation unit being visited. |
| 183 */ |
| 184 final Source _source; |
| 185 |
| 186 /** |
| 187 * The source representing the library of the compilation unit being visited. |
| 188 */ |
| 189 final Source _librarySource; |
| 190 |
| 191 /** |
| 192 * The offset of the changed contents. |
| 193 */ |
| 194 final int _updateOffset; |
| 195 |
| 196 /** |
| 197 * The end of the changed contents in the old unit. |
| 198 */ |
| 199 final int _updateEndOld; |
| 200 |
| 201 /** |
| 202 * The end of the changed contents in the new unit. |
| 203 */ |
| 204 final int _updateEndNew; |
| 205 |
| 206 /** |
| 207 * The delta between [_updateEndNew] and [_updateEndOld]. |
| 208 */ |
| 209 final int _updateDelta; |
| 210 |
| 211 /** |
| 212 * The set of [AnalysisError]s that have been already shifted. |
| 213 */ |
| 214 final Set<AnalysisError> _alreadyShiftedErrors = new HashSet.identity(); |
| 215 |
| 216 final RecordingErrorListener errorListener = new RecordingErrorListener(); |
| 217 ResolutionContext _resolutionContext; |
| 218 |
| 219 List<AnalysisError> _resolveErrors = AnalysisError.NO_ERRORS; |
| 220 List<AnalysisError> _verifyErrors = AnalysisError.NO_ERRORS; |
| 221 |
| 222 /** |
| 223 * Initialize a newly created incremental resolver to resolve a node in the |
| 224 * given source in the given library. |
| 225 */ |
| 226 IncrementalResolver( |
| 227 this._cache, |
| 228 this.newSourceEntry, |
| 229 this.newUnitEntry, |
| 230 CompilationUnitElementImpl definingUnit, |
| 231 this._updateOffset, |
| 232 int updateEndOld, |
| 233 int updateEndNew) |
| 234 : _definingUnit = definingUnit, |
| 235 _context = definingUnit.context, |
| 236 _typeProvider = definingUnit.context.typeProvider, |
| 237 _typeSystem = definingUnit.context.typeSystem, |
| 238 _definingLibrary = definingUnit.library, |
| 239 _source = definingUnit.source, |
| 240 _librarySource = definingUnit.library.source, |
| 241 _updateEndOld = updateEndOld, |
| 242 _updateEndNew = updateEndNew, |
| 243 _updateDelta = updateEndNew - updateEndOld; |
| 244 |
| 245 /** |
| 246 * Resolve [body], reporting any errors or warnings to the given listener. |
| 247 * |
| 248 * [body] - the root of the AST structure to be resolved. |
| 249 */ |
| 250 void resolve(BlockFunctionBody body) { |
| 251 logger.enter('resolve: $_definingUnit'); |
| 252 try { |
| 253 Declaration executable = _findResolutionRoot(body); |
| 254 _prepareResolutionContext(executable); |
| 255 // update elements |
| 256 _updateCache(); |
| 257 _updateElementNameOffsets(); |
| 258 _buildElements(executable, body); |
| 259 // resolve |
| 260 _resolveReferences(executable); |
| 261 _computeConstants(executable); |
| 262 _resolveErrors = errorListener.getErrorsForSource(_source); |
| 263 // verify |
| 264 _verify(executable); |
| 265 _context.invalidateLibraryHints(_librarySource); |
| 266 // update entry errors |
| 267 _updateEntry(); |
| 268 } finally { |
| 269 logger.exit(); |
| 270 } |
| 271 } |
| 272 |
| 273 void _buildElements(Declaration executable, AstNode node) { |
| 274 LoggingTimer timer = logger.startTimer(); |
| 275 try { |
| 276 ElementHolder holder = new ElementHolder(); |
| 277 node.accept(new LocalElementBuilder(holder, _definingUnit)); |
| 278 // Move local elements into the ExecutableElementImpl. |
| 279 ExecutableElementImpl executableElement = |
| 280 executable.element as ExecutableElementImpl; |
| 281 executableElement.localVariables = holder.localVariables; |
| 282 executableElement.functions = holder.functions; |
| 283 executableElement.labels = holder.labels; |
| 284 holder.validate(); |
| 285 } finally { |
| 286 timer.stop('build elements'); |
| 287 } |
| 288 } |
| 289 |
| 290 /** |
| 291 * Compute a value for all of the constants in the given [node]. |
| 292 */ |
| 293 void _computeConstants(AstNode node) { |
| 294 // compute values |
| 295 { |
| 296 CompilationUnit unit = node.getAncestor((n) => n is CompilationUnit); |
| 297 ConstantValueComputer computer = new ConstantValueComputer( |
| 298 _typeProvider, _context.declaredVariables, null, _typeSystem); |
| 299 computer.add(unit); |
| 300 computer.computeValues(); |
| 301 } |
| 302 // validate |
| 303 { |
| 304 ErrorReporter errorReporter = new ErrorReporter(errorListener, _source); |
| 305 ConstantVerifier constantVerifier = new ConstantVerifier(errorReporter, |
| 306 _definingLibrary, _typeProvider, _context.declaredVariables); |
| 307 node.accept(constantVerifier); |
| 308 } |
| 309 } |
| 310 |
| 311 /** |
| 312 * Starting at [node], find the smallest AST node that can be resolved |
| 313 * independently of any other nodes. Return the node that was found. |
| 314 * |
| 315 * [node] - the node at which the search is to begin |
| 316 * |
| 317 * Throws [AnalysisException] if there is no such node. |
| 318 */ |
| 319 Declaration _findResolutionRoot(AstNode node) { |
| 320 while (node != null) { |
| 321 if (node is ConstructorDeclaration || |
| 322 node is FunctionDeclaration || |
| 323 node is MethodDeclaration) { |
| 324 return node; |
| 325 } |
| 326 node = node.parent; |
| 327 } |
| 328 throw new AnalysisException("Cannot resolve node: no resolvable node"); |
| 329 } |
| 330 |
| 331 void _prepareResolutionContext(AstNode node) { |
| 332 if (_resolutionContext == null) { |
| 333 _resolutionContext = ResolutionContextBuilder.contextFor(node); |
| 334 } |
| 335 } |
| 336 |
| 337 _resolveReferences(AstNode node) { |
| 338 LoggingTimer timer = logger.startTimer(); |
| 339 try { |
| 340 _prepareResolutionContext(node); |
| 341 Scope scope = _resolutionContext.scope; |
| 342 // resolve types |
| 343 { |
| 344 TypeResolverVisitor visitor = new TypeResolverVisitor( |
| 345 _definingLibrary, _source, _typeProvider, errorListener, |
| 346 nameScope: scope); |
| 347 node.accept(visitor); |
| 348 } |
| 349 // resolve variables |
| 350 { |
| 351 VariableResolverVisitor visitor = new VariableResolverVisitor( |
| 352 _definingLibrary, _source, _typeProvider, errorListener, |
| 353 nameScope: scope); |
| 354 node.accept(visitor); |
| 355 } |
| 356 // resolve references |
| 357 { |
| 358 ResolverVisitor visitor = new ResolverVisitor( |
| 359 _definingLibrary, _source, _typeProvider, errorListener, |
| 360 nameScope: scope); |
| 361 if (_resolutionContext.enclosingClassDeclaration != null) { |
| 362 visitor.visitClassDeclarationIncrementally( |
| 363 _resolutionContext.enclosingClassDeclaration); |
| 364 } |
| 365 if (node is Comment) { |
| 366 visitor.resolveOnlyCommentInFunctionBody = true; |
| 367 node = node.parent; |
| 368 } |
| 369 visitor.initForIncrementalResolution(); |
| 370 node.accept(visitor); |
| 371 } |
| 372 } finally { |
| 373 timer.stop('resolve references'); |
| 374 } |
| 375 } |
| 376 |
| 377 void _shiftEntryErrors() { |
| 378 _shiftErrors_NEW(HINTS); |
| 379 _shiftErrors_NEW(LINTS); |
| 380 _shiftErrors_NEW(LIBRARY_UNIT_ERRORS); |
| 381 _shiftErrors_NEW(RESOLVE_TYPE_NAMES_ERRORS); |
| 382 _shiftErrors_NEW(RESOLVE_TYPE_BOUNDS_ERRORS); |
| 383 _shiftErrors_NEW(RESOLVE_UNIT_ERRORS); |
| 384 _shiftErrors_NEW(STATIC_VARIABLE_RESOLUTION_ERRORS_IN_UNIT); |
| 385 _shiftErrors_NEW(STRONG_MODE_ERRORS); |
| 386 _shiftErrors_NEW(VARIABLE_REFERENCE_ERRORS); |
| 387 _shiftErrors_NEW(VERIFY_ERRORS); |
| 388 } |
| 389 |
| 390 void _shiftErrors(List<AnalysisError> errors) { |
| 391 for (AnalysisError error in errors) { |
| 392 if (_alreadyShiftedErrors.add(error)) { |
| 393 int errorOffset = error.offset; |
| 394 if (errorOffset > _updateOffset) { |
| 395 error.offset += _updateDelta; |
| 396 } |
| 397 } |
| 398 } |
| 399 } |
| 400 |
| 401 void _shiftErrors_NEW(ResultDescriptor<List<AnalysisError>> descriptor) { |
| 402 List<AnalysisError> errors = newUnitEntry.getValue(descriptor); |
| 403 _shiftErrors(errors); |
| 404 } |
| 405 |
| 406 void _updateCache() { |
| 407 if (newSourceEntry != null) { |
| 408 LoggingTimer timer = logger.startTimer(); |
| 409 try { |
| 410 newSourceEntry.setState(CONTENT, CacheState.INVALID, |
| 411 delta: new IncrementalBodyDelta(_source, _updateOffset, |
| 412 _updateEndOld, _updateEndNew, _updateDelta)); |
| 413 } finally { |
| 414 timer.stop('invalidate cache with delta'); |
| 415 } |
| 416 } |
| 417 } |
| 418 |
| 419 void _updateElementNameOffsets() { |
| 420 LoggingTimer timer = logger.startTimer(); |
| 421 try { |
| 422 _definingUnit.accept( |
| 423 new _ElementOffsetUpdater(_updateOffset, _updateDelta, _cache)); |
| 424 _definingUnit.afterIncrementalResolution(); |
| 425 } finally { |
| 426 timer.stop('update element offsets'); |
| 427 } |
| 428 } |
| 429 |
| 430 void _updateEntry() { |
| 431 _updateErrors_NEW(RESOLVE_TYPE_NAMES_ERRORS, []); |
| 432 _updateErrors_NEW(RESOLVE_TYPE_BOUNDS_ERRORS, []); |
| 433 _updateErrors_NEW(RESOLVE_UNIT_ERRORS, _resolveErrors); |
| 434 _updateErrors_NEW(VARIABLE_REFERENCE_ERRORS, []); |
| 435 _updateErrors_NEW(VERIFY_ERRORS, _verifyErrors); |
| 436 // invalidate results we don't update incrementally |
| 437 newUnitEntry.setState(STRONG_MODE_ERRORS, CacheState.INVALID); |
| 438 newUnitEntry.setState(USED_IMPORTED_ELEMENTS, CacheState.INVALID); |
| 439 newUnitEntry.setState(USED_LOCAL_ELEMENTS, CacheState.INVALID); |
| 440 newUnitEntry.setState(HINTS, CacheState.INVALID); |
| 441 newUnitEntry.setState(LINTS, CacheState.INVALID); |
| 442 } |
| 443 |
| 444 List<AnalysisError> _updateErrors( |
| 445 List<AnalysisError> oldErrors, List<AnalysisError> newErrors) { |
| 446 List<AnalysisError> errors = new List<AnalysisError>(); |
| 447 // add updated old errors |
| 448 for (AnalysisError error in oldErrors) { |
| 449 int errorOffset = error.offset; |
| 450 if (errorOffset < _updateOffset) { |
| 451 errors.add(error); |
| 452 } else if (errorOffset > _updateEndOld) { |
| 453 error.offset += _updateDelta; |
| 454 errors.add(error); |
| 455 } |
| 456 } |
| 457 // add new errors |
| 458 for (AnalysisError error in newErrors) { |
| 459 int errorOffset = error.offset; |
| 460 if (errorOffset > _updateOffset && errorOffset < _updateEndNew) { |
| 461 errors.add(error); |
| 462 } |
| 463 } |
| 464 // done |
| 465 return errors; |
| 466 } |
| 467 |
| 468 void _updateErrors_NEW(ResultDescriptor<List<AnalysisError>> descriptor, |
| 469 List<AnalysisError> newErrors) { |
| 470 List<AnalysisError> oldErrors = newUnitEntry.getValue(descriptor); |
| 471 List<AnalysisError> errors = _updateErrors(oldErrors, newErrors); |
| 472 newUnitEntry.setValueIncremental(descriptor, errors, true); |
| 473 } |
| 474 |
| 475 void _verify(AstNode node) { |
| 476 LoggingTimer timer = logger.startTimer(); |
| 477 try { |
| 478 RecordingErrorListener errorListener = new RecordingErrorListener(); |
| 479 ErrorReporter errorReporter = new ErrorReporter(errorListener, _source); |
| 480 ErrorVerifier errorVerifier = new ErrorVerifier( |
| 481 errorReporter, |
| 482 _definingLibrary, |
| 483 _typeProvider, |
| 484 new InheritanceManager(_definingLibrary), |
| 485 _context.analysisOptions.enableSuperMixins); |
| 486 if (_resolutionContext.enclosingClassDeclaration != null) { |
| 487 errorVerifier.visitClassDeclarationIncrementally( |
| 488 _resolutionContext.enclosingClassDeclaration); |
| 489 } |
| 490 node.accept(errorVerifier); |
| 491 _verifyErrors = errorListener.getErrorsForSource(_source); |
| 492 } finally { |
| 493 timer.stop('verify'); |
| 494 } |
| 495 } |
| 496 } |
| 497 |
| 498 class PoorMansIncrementalResolver { |
| 499 final TypeProvider _typeProvider; |
| 500 final Source _unitSource; |
| 501 final AnalysisCache _cache; |
| 502 |
| 503 /** |
| 504 * The [CacheEntry] corresponding to the source being resolved. |
| 505 */ |
| 506 final CacheEntry _sourceEntry; |
| 507 |
| 508 /** |
| 509 * The [CacheEntry] corresponding to the [LibrarySpecificUnit] being resolved. |
| 510 */ |
| 511 final CacheEntry _unitEntry; |
| 512 |
| 513 final CompilationUnit _oldUnit; |
| 514 CompilationUnitElement _unitElement; |
| 515 |
| 516 int _updateOffset; |
| 517 int _updateDelta; |
| 518 int _updateEndOld; |
| 519 int _updateEndNew; |
| 520 |
| 521 LineInfo _newLineInfo; |
| 522 List<AnalysisError> _newScanErrors = <AnalysisError>[]; |
| 523 List<AnalysisError> _newParseErrors = <AnalysisError>[]; |
| 524 |
| 525 PoorMansIncrementalResolver( |
| 526 this._typeProvider, |
| 527 this._unitSource, |
| 528 this._cache, |
| 529 this._sourceEntry, |
| 530 this._unitEntry, |
| 531 this._oldUnit, |
| 532 bool resolveApiChanges); |
| 533 |
| 534 /** |
| 535 * Attempts to update [_oldUnit] to the state corresponding to [newCode]. |
| 536 * Returns `true` if success, or `false` otherwise. |
| 537 * The [_oldUnit] might be damaged. |
| 538 */ |
| 539 bool resolve(String newCode) { |
| 540 logger.enter('diff/resolve $_unitSource'); |
| 541 try { |
| 542 // prepare old unit |
| 543 if (!_areCurlyBracketsBalanced(_oldUnit.beginToken)) { |
| 544 logger.log('Unbalanced number of curly brackets in the old unit.'); |
| 545 return false; |
| 546 } |
| 547 _unitElement = _oldUnit.element; |
| 548 // prepare new unit |
| 549 CompilationUnit newUnit = _parseUnit(newCode); |
| 550 if (!_areCurlyBracketsBalanced(newUnit.beginToken)) { |
| 551 logger.log('Unbalanced number of curly brackets in the new unit.'); |
| 552 return false; |
| 553 } |
| 554 // find difference |
| 555 _TokenPair firstPair = |
| 556 _findFirstDifferentToken(_oldUnit.beginToken, newUnit.beginToken); |
| 557 _TokenPair lastPair = |
| 558 _findLastDifferentToken(_oldUnit.endToken, newUnit.endToken); |
| 559 if (firstPair != null && lastPair != null) { |
| 560 int firstOffsetOld = firstPair.oldToken.offset; |
| 561 int firstOffsetNew = firstPair.newToken.offset; |
| 562 int lastOffsetOld = lastPair.oldToken.end; |
| 563 int lastOffsetNew = lastPair.newToken.end; |
| 564 int beginOffsetOld = math.min(firstOffsetOld, lastOffsetOld); |
| 565 int endOffsetOld = math.max(firstOffsetOld, lastOffsetOld); |
| 566 int beginOffsetNew = math.min(firstOffsetNew, lastOffsetNew); |
| 567 int endOffsetNew = math.max(firstOffsetNew, lastOffsetNew); |
| 568 // A pure whitespace change. |
| 569 if (identical(firstPair.oldToken, lastPair.oldToken) && |
| 570 identical(firstPair.newToken, lastPair.newToken) && |
| 571 firstPair.kind == _TokenDifferenceKind.OFFSET) { |
| 572 _updateOffset = beginOffsetOld - 1; |
| 573 _updateEndOld = endOffsetOld; |
| 574 _updateEndNew = endOffsetNew; |
| 575 _updateDelta = newUnit.length - _oldUnit.length; |
| 576 logger.log('Whitespace change.'); |
| 577 _shiftTokens(firstPair.oldToken, true); |
| 578 IncrementalResolver incrementalResolver = new IncrementalResolver( |
| 579 _cache, |
| 580 _sourceEntry, |
| 581 _unitEntry, |
| 582 _unitElement, |
| 583 _updateOffset, |
| 584 _updateEndOld, |
| 585 _updateEndNew); |
| 586 incrementalResolver._updateCache(); |
| 587 incrementalResolver._updateElementNameOffsets(); |
| 588 incrementalResolver._shiftEntryErrors(); |
| 589 _updateEntry(); |
| 590 logger.log('Success.'); |
| 591 return true; |
| 592 } |
| 593 // A Dart documentation comment change. |
| 594 { |
| 595 Token firstOldToken = firstPair.oldToken; |
| 596 Token firstNewToken = firstPair.newToken; |
| 597 Token lastOldToken = lastPair.oldToken; |
| 598 Token lastNewToken = lastPair.newToken; |
| 599 if (firstOldToken is DocumentationCommentToken && |
| 600 firstNewToken is DocumentationCommentToken && |
| 601 lastOldToken is DocumentationCommentToken && |
| 602 lastNewToken is DocumentationCommentToken && |
| 603 identical(firstOldToken.parent, lastOldToken.parent) && |
| 604 identical(firstNewToken.parent, lastNewToken.parent)) { |
| 605 _updateOffset = beginOffsetOld; |
| 606 _updateEndOld = firstOldToken.parent.offset; |
| 607 _updateEndNew = firstNewToken.parent.offset; |
| 608 _updateDelta = newUnit.length - _oldUnit.length; |
| 609 bool success = |
| 610 _resolveCommentDoc(newUnit, firstOldToken, firstNewToken); |
| 611 logger.log('Documentation comment resolved: $success'); |
| 612 return success; |
| 613 } |
| 614 } |
| 615 // Find nodes covering the "old" and "new" token ranges. |
| 616 AstNode oldNode = |
| 617 _findNodeCovering(_oldUnit, beginOffsetOld, endOffsetOld - 1); |
| 618 AstNode newNode = |
| 619 _findNodeCovering(newUnit, beginOffsetNew, endOffsetNew - 1); |
| 620 logger.log(() => 'oldNode: $oldNode'); |
| 621 logger.log(() => 'newNode: $newNode'); |
| 622 // Try to find the smallest common node, a FunctionBody currently. |
| 623 { |
| 624 List<AstNode> oldParents = _getParents(oldNode); |
| 625 List<AstNode> newParents = _getParents(newNode); |
| 626 // fail if an initializer change |
| 627 if (oldParents.any((n) => n is ConstructorInitializer) || |
| 628 newParents.any((n) => n is ConstructorInitializer)) { |
| 629 logger.log('Failure: a change in a constructor initializer'); |
| 630 return false; |
| 631 } |
| 632 // find matching methods / bodies |
| 633 int length = math.min(oldParents.length, newParents.length); |
| 634 bool found = false; |
| 635 for (int i = 0; i < length; i++) { |
| 636 AstNode oldParent = oldParents[i]; |
| 637 AstNode newParent = newParents[i]; |
| 638 if (oldParent is CompilationUnit && newParent is CompilationUnit) { |
| 639 int oldLength = oldParent.declarations.length; |
| 640 int newLength = newParent.declarations.length; |
| 641 if (oldLength != newLength) { |
| 642 logger.log( |
| 643 'Failure: unit declarations mismatch $oldLength vs. $newLeng
th'); |
| 644 return false; |
| 645 } |
| 646 } else if (oldParent is ClassDeclaration && |
| 647 newParent is ClassDeclaration) { |
| 648 int oldLength = oldParent.members.length; |
| 649 int newLength = newParent.members.length; |
| 650 if (oldLength != newLength) { |
| 651 logger.log( |
| 652 'Failure: class declarations mismatch $oldLength vs. $newLen
gth'); |
| 653 return false; |
| 654 } |
| 655 } else if (oldParent is FunctionDeclaration && |
| 656 newParent is FunctionDeclaration || |
| 657 oldParent is ConstructorDeclaration && |
| 658 newParent is ConstructorDeclaration || |
| 659 oldParent is MethodDeclaration && |
| 660 newParent is MethodDeclaration) { |
| 661 if (oldParents.length == i || newParents.length == i) { |
| 662 return false; |
| 663 } |
| 664 } else if (oldParent is FunctionBody && newParent is FunctionBody) { |
| 665 if (oldParent is BlockFunctionBody && |
| 666 newParent is BlockFunctionBody) { |
| 667 if (oldParent.isAsynchronous != newParent.isAsynchronous) { |
| 668 logger.log('Failure: body async mismatch.'); |
| 669 return false; |
| 670 } |
| 671 if (oldParent.isGenerator != newParent.isGenerator) { |
| 672 logger.log('Failure: body generator mismatch.'); |
| 673 return false; |
| 674 } |
| 675 oldNode = oldParent; |
| 676 newNode = newParent; |
| 677 found = true; |
| 678 break; |
| 679 } |
| 680 logger.log('Failure: not a block function body.'); |
| 681 return false; |
| 682 } else if (oldParent is FunctionExpression && |
| 683 newParent is FunctionExpression) { |
| 684 // skip |
| 685 } else { |
| 686 logger.log('Failure: old and new parent mismatch' |
| 687 ' ${oldParent.runtimeType} vs. ${newParent.runtimeType}'); |
| 688 return false; |
| 689 } |
| 690 } |
| 691 if (!found) { |
| 692 logger.log('Failure: no enclosing function body or executable.'); |
| 693 return false; |
| 694 } |
| 695 } |
| 696 logger.log(() => 'oldNode: $oldNode'); |
| 697 logger.log(() => 'newNode: $newNode'); |
| 698 // prepare update range |
| 699 _updateOffset = oldNode.offset; |
| 700 _updateEndOld = oldNode.end; |
| 701 _updateEndNew = newNode.end; |
| 702 _updateDelta = _updateEndNew - _updateEndOld; |
| 703 // replace node |
| 704 NodeReplacer.replace(oldNode, newNode); |
| 705 // update token references |
| 706 { |
| 707 Token oldBeginToken = _getBeginTokenNotComment(oldNode); |
| 708 Token newBeginToken = _getBeginTokenNotComment(newNode); |
| 709 if (oldBeginToken.previous.type == TokenType.EOF) { |
| 710 _oldUnit.beginToken = newBeginToken; |
| 711 } else { |
| 712 oldBeginToken.previous.setNext(newBeginToken); |
| 713 } |
| 714 newNode.endToken.setNext(oldNode.endToken.next); |
| 715 _shiftTokens(oldNode.endToken.next); |
| 716 } |
| 717 // perform incremental resolution |
| 718 IncrementalResolver incrementalResolver = new IncrementalResolver( |
| 719 _cache, |
| 720 _sourceEntry, |
| 721 _unitEntry, |
| 722 _unitElement, |
| 723 _updateOffset, |
| 724 _updateEndOld, |
| 725 _updateEndNew); |
| 726 incrementalResolver.resolve(newNode); |
| 727 // update DartEntry |
| 728 _updateEntry(); |
| 729 logger.log('Success.'); |
| 730 return true; |
| 731 } |
| 732 } catch (e, st) { |
| 733 logger.logException(e, st); |
| 734 logger.log('Failure: exception.'); |
| 735 // The incremental resolver log is usually turned off, |
| 736 // so also log the exception to the instrumentation log. |
| 737 AnalysisEngine.instance.logger.logError( |
| 738 'Failure in incremental resolver', new CaughtException(e, st)); |
| 739 } finally { |
| 740 logger.exit(); |
| 741 } |
| 742 return false; |
| 743 } |
| 744 |
| 745 CompilationUnit _parseUnit(String code) { |
| 746 LoggingTimer timer = logger.startTimer(); |
| 747 try { |
| 748 Token token = _scan(code); |
| 749 RecordingErrorListener errorListener = new RecordingErrorListener(); |
| 750 Parser parser = new Parser(_unitSource, errorListener); |
| 751 AnalysisOptions options = _unitElement.context.analysisOptions; |
| 752 parser.parseGenericMethodComments = options.strongMode; |
| 753 CompilationUnit unit = parser.parseCompilationUnit(token); |
| 754 _newParseErrors = errorListener.errors; |
| 755 return unit; |
| 756 } finally { |
| 757 timer.stop('parse'); |
| 758 } |
| 759 } |
| 760 |
| 761 /** |
| 762 * Attempts to resolve a documentation comment change. |
| 763 * Returns `true` if success. |
| 764 */ |
| 765 bool _resolveCommentDoc( |
| 766 CompilationUnit newUnit, CommentToken oldToken, CommentToken newToken) { |
| 767 if (oldToken == null || newToken == null) { |
| 768 return false; |
| 769 } |
| 770 // find nodes |
| 771 int offset = oldToken.offset; |
| 772 logger.log('offset: $offset'); |
| 773 AstNode oldNode = _findNodeCovering(_oldUnit, offset, offset); |
| 774 AstNode newNode = _findNodeCovering(newUnit, offset, offset); |
| 775 if (oldNode is! Comment || newNode is! Comment) { |
| 776 return false; |
| 777 } |
| 778 Comment oldComment = oldNode; |
| 779 Comment newComment = newNode; |
| 780 logger.log('oldComment.beginToken: ${oldComment.beginToken}'); |
| 781 logger.log('newComment.beginToken: ${newComment.beginToken}'); |
| 782 // update token references |
| 783 _shiftTokens(oldToken.parent); |
| 784 _setPrecedingComments(oldToken.parent, newComment.tokens.first); |
| 785 // replace node |
| 786 NodeReplacer.replace(oldComment, newComment); |
| 787 // update elements |
| 788 IncrementalResolver incrementalResolver = new IncrementalResolver( |
| 789 _cache, |
| 790 _sourceEntry, |
| 791 _unitEntry, |
| 792 _unitElement, |
| 793 _updateOffset, |
| 794 _updateEndOld, |
| 795 _updateEndNew); |
| 796 incrementalResolver._updateCache(); |
| 797 incrementalResolver._updateElementNameOffsets(); |
| 798 incrementalResolver._shiftEntryErrors(); |
| 799 _updateEntry(); |
| 800 // resolve references in the comment |
| 801 incrementalResolver._resolveReferences(newComment); |
| 802 // update 'documentationComment' of the parent element(s) |
| 803 { |
| 804 AstNode parent = newComment.parent; |
| 805 if (parent is AnnotatedNode) { |
| 806 setElementDocumentationForVariables(VariableDeclarationList list) { |
| 807 for (VariableDeclaration variable in list.variables) { |
| 808 Element variableElement = variable.element; |
| 809 if (variableElement is ElementImpl) { |
| 810 setElementDocumentationComment(variableElement, parent); |
| 811 } |
| 812 } |
| 813 } |
| 814 |
| 815 Element parentElement = ElementLocator.locate(newComment.parent); |
| 816 if (parentElement is ElementImpl) { |
| 817 setElementDocumentationComment(parentElement, parent); |
| 818 } else if (parent is FieldDeclaration) { |
| 819 setElementDocumentationForVariables(parent.fields); |
| 820 } else if (parent is TopLevelVariableDeclaration) { |
| 821 setElementDocumentationForVariables(parent.variables); |
| 822 } |
| 823 } |
| 824 } |
| 825 // OK |
| 826 return true; |
| 827 } |
| 828 |
| 829 Token _scan(String code) { |
| 830 RecordingErrorListener errorListener = new RecordingErrorListener(); |
| 831 CharSequenceReader reader = new CharSequenceReader(code); |
| 832 Scanner scanner = new Scanner(_unitSource, reader, errorListener); |
| 833 AnalysisOptions options = _unitElement.context.analysisOptions; |
| 834 scanner.scanGenericMethodComments = options.strongMode; |
| 835 Token token = scanner.tokenize(); |
| 836 _newLineInfo = new LineInfo(scanner.lineStarts); |
| 837 _newScanErrors = errorListener.errors; |
| 838 return token; |
| 839 } |
| 840 |
| 841 /** |
| 842 * Set the given [comment] as a "precedingComments" for [token]. |
| 843 */ |
| 844 void _setPrecedingComments(Token token, CommentToken comment) { |
| 845 if (token is BeginTokenWithComment) { |
| 846 token.precedingComments = comment; |
| 847 } else if (token is KeywordTokenWithComment) { |
| 848 token.precedingComments = comment; |
| 849 } else if (token is StringTokenWithComment) { |
| 850 token.precedingComments = comment; |
| 851 } else if (token is TokenWithComment) { |
| 852 token.precedingComments = comment; |
| 853 } else { |
| 854 Type parentType = token?.runtimeType; |
| 855 throw new AnalysisException('Uknown parent token type: $parentType'); |
| 856 } |
| 857 } |
| 858 |
| 859 void _shiftTokens(Token token, [bool goUpComment = false]) { |
| 860 while (token != null) { |
| 861 if (goUpComment && token is CommentToken) { |
| 862 token = (token as CommentToken).parent; |
| 863 } |
| 864 if (token.offset > _updateOffset) { |
| 865 token.offset += _updateDelta; |
| 866 } |
| 867 // comments |
| 868 _shiftTokens(token.precedingComments); |
| 869 if (token is DocumentationCommentToken) { |
| 870 for (Token reference in token.references) { |
| 871 _shiftTokens(reference); |
| 872 } |
| 873 } |
| 874 // next |
| 875 if (token.type == TokenType.EOF) { |
| 876 break; |
| 877 } |
| 878 token = token.next; |
| 879 } |
| 880 } |
| 881 |
| 882 void _updateEntry() { |
| 883 // scan results |
| 884 _sourceEntry.setValueIncremental(SCAN_ERRORS, _newScanErrors, true); |
| 885 _sourceEntry.setValueIncremental(LINE_INFO, _newLineInfo, false); |
| 886 // parse results |
| 887 _sourceEntry.setValueIncremental(PARSE_ERRORS, _newParseErrors, true); |
| 888 _sourceEntry.setValueIncremental(PARSED_UNIT, _oldUnit, false); |
| 889 } |
| 890 |
| 891 /** |
| 892 * Checks if [token] has a balanced number of open and closed curly brackets. |
| 893 */ |
| 894 static bool _areCurlyBracketsBalanced(Token token) { |
| 895 int numOpen = _getTokenCount(token, TokenType.OPEN_CURLY_BRACKET); |
| 896 int numOpen2 = |
| 897 _getTokenCount(token, TokenType.STRING_INTERPOLATION_EXPRESSION); |
| 898 int numClosed = _getTokenCount(token, TokenType.CLOSE_CURLY_BRACKET); |
| 899 return numOpen + numOpen2 == numClosed; |
| 900 } |
| 901 |
| 902 static _TokenDifferenceKind _compareToken( |
| 903 Token oldToken, Token newToken, int delta) { |
| 904 if (oldToken == null && newToken == null) { |
| 905 return null; |
| 906 } |
| 907 if (oldToken == null || newToken == null) { |
| 908 return _TokenDifferenceKind.CONTENT; |
| 909 } |
| 910 if (oldToken.type != newToken.type) { |
| 911 return _TokenDifferenceKind.CONTENT; |
| 912 } |
| 913 if (oldToken.lexeme != newToken.lexeme) { |
| 914 return _TokenDifferenceKind.CONTENT; |
| 915 } |
| 916 if (newToken.offset - oldToken.offset != delta) { |
| 917 return _TokenDifferenceKind.OFFSET; |
| 918 } |
| 919 return null; |
| 920 } |
| 921 |
| 922 static _TokenPair _findFirstDifferentToken(Token oldToken, Token newToken) { |
| 923 while (oldToken.type != TokenType.EOF || newToken.type != TokenType.EOF) { |
| 924 if (oldToken.type == TokenType.EOF || newToken.type == TokenType.EOF) { |
| 925 return new _TokenPair(_TokenDifferenceKind.CONTENT, oldToken, newToken); |
| 926 } |
| 927 // compare comments |
| 928 { |
| 929 Token oldComment = oldToken.precedingComments; |
| 930 Token newComment = newToken.precedingComments; |
| 931 while (true) { |
| 932 _TokenDifferenceKind diffKind = |
| 933 _compareToken(oldComment, newComment, 0); |
| 934 if (diffKind != null) { |
| 935 return new _TokenPair( |
| 936 diffKind, oldComment ?? oldToken, newComment ?? newToken); |
| 937 } |
| 938 if (oldComment == null && newComment == null) { |
| 939 break; |
| 940 } |
| 941 oldComment = oldComment.next; |
| 942 newComment = newComment.next; |
| 943 } |
| 944 } |
| 945 // compare tokens |
| 946 _TokenDifferenceKind diffKind = _compareToken(oldToken, newToken, 0); |
| 947 if (diffKind != null) { |
| 948 return new _TokenPair(diffKind, oldToken, newToken); |
| 949 } |
| 950 // next tokens |
| 951 oldToken = oldToken.next; |
| 952 newToken = newToken.next; |
| 953 } |
| 954 // no difference |
| 955 return null; |
| 956 } |
| 957 |
| 958 static _TokenPair _findLastDifferentToken(Token oldToken, Token newToken) { |
| 959 int delta = newToken.offset - oldToken.offset; |
| 960 Token prevOldToken; |
| 961 Token prevNewToken; |
| 962 while (oldToken.previous != oldToken && newToken.previous != newToken) { |
| 963 // compare tokens |
| 964 _TokenDifferenceKind diffKind = _compareToken(oldToken, newToken, delta); |
| 965 if (diffKind != null) { |
| 966 return new _TokenPair(diffKind, prevOldToken, prevNewToken); |
| 967 } |
| 968 prevOldToken = oldToken; |
| 969 prevNewToken = newToken; |
| 970 // compare comments |
| 971 { |
| 972 Token oldComment = oldToken.precedingComments; |
| 973 Token newComment = newToken.precedingComments; |
| 974 while (oldComment?.next != null) { |
| 975 oldComment = oldComment.next; |
| 976 } |
| 977 while (newComment?.next != null) { |
| 978 newComment = newComment.next; |
| 979 } |
| 980 while (true) { |
| 981 _TokenDifferenceKind diffKind = |
| 982 _compareToken(oldComment, newComment, delta); |
| 983 if (diffKind != null) { |
| 984 return new _TokenPair( |
| 985 diffKind, oldComment ?? oldToken, newComment ?? newToken); |
| 986 } |
| 987 if (oldComment == null && newComment == null) { |
| 988 break; |
| 989 } |
| 990 prevOldToken = oldComment; |
| 991 prevNewToken = newComment; |
| 992 oldComment = oldComment.previous; |
| 993 newComment = newComment.previous; |
| 994 } |
| 995 } |
| 996 // next tokens |
| 997 oldToken = oldToken.previous; |
| 998 newToken = newToken.previous; |
| 999 } |
| 1000 return null; |
| 1001 } |
| 1002 |
| 1003 static AstNode _findNodeCovering(AstNode root, int offset, int end) { |
| 1004 NodeLocator nodeLocator = new NodeLocator(offset, end); |
| 1005 return nodeLocator.searchWithin(root); |
| 1006 } |
| 1007 |
| 1008 static Token _getBeginTokenNotComment(AstNode node) { |
| 1009 Token oldBeginToken = node.beginToken; |
| 1010 if (oldBeginToken is CommentToken) { |
| 1011 return oldBeginToken.parent; |
| 1012 } |
| 1013 return oldBeginToken; |
| 1014 } |
| 1015 |
| 1016 static List<AstNode> _getParents(AstNode node) { |
| 1017 List<AstNode> parents = <AstNode>[]; |
| 1018 while (node != null) { |
| 1019 parents.insert(0, node); |
| 1020 node = node.parent; |
| 1021 } |
| 1022 return parents; |
| 1023 } |
| 1024 |
| 1025 /** |
| 1026 * Returns number of tokens with the given [type]. |
| 1027 */ |
| 1028 static int _getTokenCount(Token token, TokenType type) { |
| 1029 int count = 0; |
| 1030 while (token.type != TokenType.EOF) { |
| 1031 if (token.type == type) { |
| 1032 count++; |
| 1033 } |
| 1034 token = token.next; |
| 1035 } |
| 1036 return count; |
| 1037 } |
| 1038 } |
| 11 | 1039 |
| 12 /** | 1040 /** |
| 13 * The context to resolve an [AstNode] in. | 1041 * The context to resolve an [AstNode] in. |
| 14 */ | 1042 */ |
| 15 class ResolutionContext { | 1043 class ResolutionContext { |
| 16 CompilationUnitElement enclosingUnit; | 1044 CompilationUnitElement enclosingUnit; |
| 17 ClassDeclaration enclosingClassDeclaration; | 1045 ClassDeclaration enclosingClassDeclaration; |
| 18 ClassElement enclosingClass; | 1046 ClassElement enclosingClass; |
| 19 Scope scope; | 1047 Scope scope; |
| 20 } | 1048 } |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 155 Scope scope = builder._scopeFor(node); | 1183 Scope scope = builder._scopeFor(node); |
| 156 // prepare context | 1184 // prepare context |
| 157 ResolutionContext context = new ResolutionContext(); | 1185 ResolutionContext context = new ResolutionContext(); |
| 158 context.scope = scope; | 1186 context.scope = scope; |
| 159 context.enclosingUnit = builder._enclosingUnit; | 1187 context.enclosingUnit = builder._enclosingUnit; |
| 160 context.enclosingClassDeclaration = builder._enclosingClassDeclaration; | 1188 context.enclosingClassDeclaration = builder._enclosingClassDeclaration; |
| 161 context.enclosingClass = builder._enclosingClass; | 1189 context.enclosingClass = builder._enclosingClass; |
| 162 return context; | 1190 return context; |
| 163 } | 1191 } |
| 164 } | 1192 } |
| 1193 |
| 1194 /** |
| 1195 * Adjusts the location of each Element that moved. |
| 1196 * |
| 1197 * Since `==` and `hashCode` of a local variable or function Element are based |
| 1198 * on the element name offsets, we also need to remove these elements from the |
| 1199 * cache to avoid a memory leak. TODO(scheglov) fix and remove this |
| 1200 */ |
| 1201 class _ElementOffsetUpdater extends GeneralizingElementVisitor { |
| 1202 final int updateOffset; |
| 1203 final int updateDelta; |
| 1204 final AnalysisCache cache; |
| 1205 |
| 1206 _ElementOffsetUpdater(this.updateOffset, this.updateDelta, this.cache); |
| 1207 |
| 1208 @override |
| 1209 visitElement(Element element) { |
| 1210 // name offset |
| 1211 int nameOffset = element.nameOffset; |
| 1212 if (nameOffset > updateOffset) { |
| 1213 (element as ElementImpl).nameOffset = nameOffset + updateDelta; |
| 1214 if (element is ConstVariableElement) { |
| 1215 Expression initializer = element.constantInitializer; |
| 1216 if (initializer != null) { |
| 1217 _shiftTokens(initializer.beginToken); |
| 1218 } |
| 1219 _shiftErrors(element.evaluationResult?.errors); |
| 1220 } |
| 1221 } |
| 1222 // code range |
| 1223 if (element is ElementImpl) { |
| 1224 int oldOffset = element.codeOffset; |
| 1225 int oldLength = element.codeLength; |
| 1226 if (oldOffset != null) { |
| 1227 int newOffset = oldOffset; |
| 1228 int newLength = oldLength; |
| 1229 newOffset += oldOffset > updateOffset ? updateDelta : 0; |
| 1230 if (oldOffset <= updateOffset && updateOffset < oldOffset + oldLength) { |
| 1231 newLength += updateDelta; |
| 1232 } |
| 1233 if (newOffset != oldOffset || newLength != oldLength) { |
| 1234 element.setCodeRange(newOffset, newLength); |
| 1235 } |
| 1236 } |
| 1237 } |
| 1238 // visible range |
| 1239 if (element is LocalElement) { |
| 1240 SourceRange visibleRange = element.visibleRange; |
| 1241 if (visibleRange != null) { |
| 1242 int oldOffset = visibleRange.offset; |
| 1243 int oldLength = visibleRange.length; |
| 1244 int newOffset = oldOffset; |
| 1245 int newLength = oldLength; |
| 1246 newOffset += oldOffset > updateOffset ? updateDelta : 0; |
| 1247 newLength += visibleRange.contains(updateOffset) ? updateDelta : 0; |
| 1248 if (newOffset != oldOffset || newLength != oldLength) { |
| 1249 if (element is FunctionElementImpl) { |
| 1250 element.setVisibleRange(newOffset, newLength); |
| 1251 } else if (element is LocalVariableElementImpl) { |
| 1252 element.setVisibleRange(newOffset, newLength); |
| 1253 } else if (element is ParameterElementImpl) { |
| 1254 element.setVisibleRange(newOffset, newLength); |
| 1255 } |
| 1256 } |
| 1257 } |
| 1258 } |
| 1259 super.visitElement(element); |
| 1260 } |
| 1261 |
| 1262 void _shiftErrors(List<AnalysisError> errors) { |
| 1263 if (errors != null) { |
| 1264 for (AnalysisError error in errors) { |
| 1265 int errorOffset = error.offset; |
| 1266 if (errorOffset > updateOffset) { |
| 1267 error.offset += updateDelta; |
| 1268 } |
| 1269 } |
| 1270 } |
| 1271 } |
| 1272 |
| 1273 void _shiftTokens(Token token) { |
| 1274 while (token != null) { |
| 1275 if (token.offset > updateOffset) { |
| 1276 token.offset += updateDelta; |
| 1277 } |
| 1278 // comments |
| 1279 _shiftTokens(token.precedingComments); |
| 1280 if (token is DocumentationCommentToken) { |
| 1281 for (Token reference in token.references) { |
| 1282 _shiftTokens(reference); |
| 1283 } |
| 1284 } |
| 1285 // next |
| 1286 if (token.type == TokenType.EOF) { |
| 1287 break; |
| 1288 } |
| 1289 token = token.next; |
| 1290 } |
| 1291 } |
| 1292 } |
| 1293 |
| 1294 /** |
| 1295 * Describes how two [Token]s are different. |
| 1296 */ |
| 1297 class _TokenDifferenceKind { |
| 1298 static const CONTENT = const _TokenDifferenceKind('CONTENT'); |
| 1299 static const OFFSET = const _TokenDifferenceKind('OFFSET'); |
| 1300 |
| 1301 final String name; |
| 1302 |
| 1303 const _TokenDifferenceKind(this.name); |
| 1304 |
| 1305 @override |
| 1306 String toString() => name; |
| 1307 } |
| 1308 |
| 1309 class _TokenPair { |
| 1310 final _TokenDifferenceKind kind; |
| 1311 final Token oldToken; |
| 1312 final Token newToken; |
| 1313 _TokenPair(this.kind, this.oldToken, this.newToken); |
| 1314 } |
| OLD | NEW |