blob: b26d939c3f74e412bd26a7c06d399a18a352b1a3 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library analyzer.src.generated.incremental_resolver;
import 'dart:collection';
import 'dart:math' as math;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/src/context/cache.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/element/builder.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/resolver/inheritance_manager.dart';
import 'package:analyzer/src/dart/scanner/reader.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer/src/generated/constant.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error_verifier.dart';
import 'package:analyzer/src/generated/incremental_logger.dart'
show logger, LoggingTimer;
import 'package:analyzer/src/generated/parser.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/task/dart.dart';
import 'package:analyzer/task/dart.dart';
import 'package:analyzer/task/general.dart' show CONTENT, LINE_INFO;
import 'package:analyzer/task/model.dart';
/**
* The [Delta] implementation used by incremental resolver.
* It keeps Dart results that are either don't change or are updated.
*/
class IncrementalBodyDelta extends Delta {
/**
* The offset of the changed contents.
*/
final int updateOffset;
/**
* The end of the changed contents in the old unit.
*/
final int updateEndOld;
/**
* The end of the changed contents in the new unit.
*/
final int updateEndNew;
/**
* The delta between [updateEndNew] and [updateEndOld].
*/
final int updateDelta;
IncrementalBodyDelta(Source source, this.updateOffset, this.updateEndOld,
this.updateEndNew, this.updateDelta)
: super(source);
@override
DeltaResult validate(InternalAnalysisContext context, AnalysisTarget target,
ResultDescriptor descriptor, Object value) {
// A body change delta should never leak outside its source.
// It can cause invalidation of results (e.g. hints) in other sources,
// but only when a result in the updated source is INVALIDATE_NO_DELTA.
if (target.source != source) {
return DeltaResult.STOP;
}
// don't invalidate results of standard Dart tasks
bool isByTask(TaskDescriptor taskDescriptor) {
return taskDescriptor.results.contains(descriptor);
}
if (descriptor == CONTENT) {
return DeltaResult.KEEP_CONTINUE;
}
if (target is LibrarySpecificUnit && target.unit != source) {
if (isByTask(GatherUsedLocalElementsTask.DESCRIPTOR) ||
isByTask(GatherUsedImportedElementsTask.DESCRIPTOR)) {
return DeltaResult.KEEP_CONTINUE;
}
}
if (isByTask(BuildCompilationUnitElementTask.DESCRIPTOR) ||
isByTask(BuildDirectiveElementsTask.DESCRIPTOR) ||
isByTask(BuildEnumMemberElementsTask.DESCRIPTOR) ||
isByTask(BuildExportNamespaceTask.DESCRIPTOR) ||
isByTask(BuildLibraryElementTask.DESCRIPTOR) ||
isByTask(BuildPublicNamespaceTask.DESCRIPTOR) ||
isByTask(BuildSourceExportClosureTask.DESCRIPTOR) ||
isByTask(ComputeConstantDependenciesTask.DESCRIPTOR) ||
isByTask(ComputeConstantValueTask.DESCRIPTOR) ||
isByTask(ComputeInferableStaticVariableDependenciesTask.DESCRIPTOR) ||
isByTask(ComputeLibraryCycleTask.DESCRIPTOR) ||
isByTask(DartErrorsTask.DESCRIPTOR) ||
isByTask(ReadyLibraryElement2Task.DESCRIPTOR) ||
isByTask(ReadyLibraryElement5Task.DESCRIPTOR) ||
isByTask(ReadyLibraryElement7Task.DESCRIPTOR) ||
isByTask(ReadyResolvedUnitTask.DESCRIPTOR) ||
isByTask(EvaluateUnitConstantsTask.DESCRIPTOR) ||
isByTask(GenerateHintsTask.DESCRIPTOR) ||
isByTask(InferInstanceMembersInUnitTask.DESCRIPTOR) ||
isByTask(InferStaticVariableTypesInUnitTask.DESCRIPTOR) ||
isByTask(InferStaticVariableTypeTask.DESCRIPTOR) ||
isByTask(LibraryErrorsReadyTask.DESCRIPTOR) ||
isByTask(LibraryUnitErrorsTask.DESCRIPTOR) ||
isByTask(ParseDartTask.DESCRIPTOR) ||
isByTask(PartiallyResolveUnitReferencesTask.DESCRIPTOR) ||
isByTask(ScanDartTask.DESCRIPTOR) ||
isByTask(ResolveConstantExpressionTask.DESCRIPTOR) ||
isByTask(ResolveDirectiveElementsTask.DESCRIPTOR) ||
isByTask(ResolvedUnit7InLibraryClosureTask.DESCRIPTOR) ||
isByTask(ResolvedUnit7InLibraryTask.DESCRIPTOR) ||
isByTask(ResolveInstanceFieldsInUnitTask.DESCRIPTOR) ||
isByTask(ResolveLibraryReferencesTask.DESCRIPTOR) ||
isByTask(ResolveLibraryTask.DESCRIPTOR) ||
isByTask(ResolveLibraryTypeNamesTask.DESCRIPTOR) ||
isByTask(ResolveTopLevelLibraryTypeBoundsTask.DESCRIPTOR) ||
isByTask(ResolveTopLevelUnitTypeBoundsTask.DESCRIPTOR) ||
isByTask(ResolveUnitTask.DESCRIPTOR) ||
isByTask(ResolveUnitTypeNamesTask.DESCRIPTOR) ||
isByTask(ResolveVariableReferencesTask.DESCRIPTOR) ||
isByTask(StrongModeVerifyUnitTask.DESCRIPTOR) ||
isByTask(VerifyUnitTask.DESCRIPTOR)) {
return DeltaResult.KEEP_CONTINUE;
}
// invalidate all the other results
return DeltaResult.INVALIDATE_NO_DELTA;
}
}
/**
* Instances of the class [IncrementalResolver] resolve the smallest portion of
* an AST structure that we currently know how to resolve.
*/
class IncrementalResolver {
/**
* The element of the compilation unit being resolved.
*/
final CompilationUnitElementImpl _definingUnit;
/**
* The context the compilation unit being resolved in.
*/
final AnalysisContext _context;
/**
* The object used to access the types from the core library.
*/
final TypeProvider _typeProvider;
/**
* The type system primitives.
*/
final TypeSystem _typeSystem;
/**
* The element for the library containing the compilation unit being resolved.
*/
final LibraryElementImpl _definingLibrary;
final AnalysisCache _cache;
/**
* The [CacheEntry] corresponding to the source being resolved.
*/
final CacheEntry newSourceEntry;
/**
* The [CacheEntry] corresponding to the [LibrarySpecificUnit] being resolved.
*/
final CacheEntry newUnitEntry;
/**
* The source representing the compilation unit being visited.
*/
final Source _source;
/**
* The source representing the library of the compilation unit being visited.
*/
final Source _librarySource;
/**
* The offset of the changed contents.
*/
final int _updateOffset;
/**
* The end of the changed contents in the old unit.
*/
final int _updateEndOld;
/**
* The end of the changed contents in the new unit.
*/
final int _updateEndNew;
/**
* The delta between [_updateEndNew] and [_updateEndOld].
*/
final int _updateDelta;
/**
* The set of [AnalysisError]s that have been already shifted.
*/
final Set<AnalysisError> _alreadyShiftedErrors = new HashSet.identity();
final RecordingErrorListener errorListener = new RecordingErrorListener();
ResolutionContext _resolutionContext;
List<AnalysisError> _resolveErrors = AnalysisError.NO_ERRORS;
List<AnalysisError> _verifyErrors = AnalysisError.NO_ERRORS;
/**
* Initialize a newly created incremental resolver to resolve a node in the
* given source in the given library.
*/
IncrementalResolver(
this._cache,
this.newSourceEntry,
this.newUnitEntry,
CompilationUnitElementImpl definingUnit,
this._updateOffset,
int updateEndOld,
int updateEndNew)
: _definingUnit = definingUnit,
_context = definingUnit.context,
_typeProvider = definingUnit.context.typeProvider,
_typeSystem = definingUnit.context.typeSystem,
_definingLibrary = definingUnit.library,
_source = definingUnit.source,
_librarySource = definingUnit.library.source,
_updateEndOld = updateEndOld,
_updateEndNew = updateEndNew,
_updateDelta = updateEndNew - updateEndOld;
/**
* Resolve [body], reporting any errors or warnings to the given listener.
*
* [body] - the root of the AST structure to be resolved.
*/
void resolve(BlockFunctionBody body) {
logger.enter('resolve: $_definingUnit');
try {
Declaration executable = _findResolutionRoot(body);
_prepareResolutionContext(executable);
// update elements
_updateCache();
_updateElementNameOffsets();
_buildElements(executable, body);
// resolve
_resolveReferences(executable);
_computeConstants(executable);
_resolveErrors = errorListener.getErrorsForSource(_source);
// verify
_verify(executable);
_context.invalidateLibraryHints(_librarySource);
// update entry errors
_updateEntry();
} finally {
logger.exit();
}
}
void _buildElements(Declaration executable, AstNode node) {
LoggingTimer timer = logger.startTimer();
try {
ElementHolder holder = new ElementHolder();
node.accept(new LocalElementBuilder(holder, _definingUnit));
// Move local elements into the ExecutableElementImpl.
ExecutableElementImpl executableElement =
executable.element as ExecutableElementImpl;
executableElement.localVariables = holder.localVariables;
executableElement.functions = holder.functions;
executableElement.labels = holder.labels;
holder.validate();
} finally {
timer.stop('build elements');
}
}
/**
* Compute a value for all of the constants in the given [node].
*/
void _computeConstants(AstNode node) {
// compute values
{
CompilationUnit unit = node.getAncestor((n) => n is CompilationUnit);
ConstantValueComputer computer = new ConstantValueComputer(
_typeProvider, _context.declaredVariables, null, _typeSystem);
computer.add(unit);
computer.computeValues();
}
// validate
{
ErrorReporter errorReporter = new ErrorReporter(errorListener, _source);
ConstantVerifier constantVerifier = new ConstantVerifier(errorReporter,
_definingLibrary, _typeProvider, _context.declaredVariables);
node.accept(constantVerifier);
}
}
/**
* Starting at [node], find the smallest AST node that can be resolved
* independently of any other nodes. Return the node that was found.
*
* [node] - the node at which the search is to begin
*
* Throws [AnalysisException] if there is no such node.
*/
Declaration _findResolutionRoot(AstNode node) {
while (node != null) {
if (node is ConstructorDeclaration ||
node is FunctionDeclaration ||
node is MethodDeclaration) {
return node;
}
node = node.parent;
}
throw new AnalysisException("Cannot resolve node: no resolvable node");
}
void _prepareResolutionContext(AstNode node) {
if (_resolutionContext == null) {
_resolutionContext = ResolutionContextBuilder.contextFor(node);
}
}
_resolveReferences(AstNode node) {
LoggingTimer timer = logger.startTimer();
try {
_prepareResolutionContext(node);
Scope scope = _resolutionContext.scope;
// resolve types
{
TypeResolverVisitor visitor = new TypeResolverVisitor(
_definingLibrary, _source, _typeProvider, errorListener,
nameScope: scope);
node.accept(visitor);
}
// resolve variables
{
VariableResolverVisitor visitor = new VariableResolverVisitor(
_definingLibrary, _source, _typeProvider, errorListener,
nameScope: scope);
node.accept(visitor);
}
// resolve references
{
ResolverVisitor visitor = new ResolverVisitor(
_definingLibrary, _source, _typeProvider, errorListener,
nameScope: scope);
if (_resolutionContext.enclosingClassDeclaration != null) {
visitor.visitClassDeclarationIncrementally(
_resolutionContext.enclosingClassDeclaration);
}
if (node is Comment) {
visitor.resolveOnlyCommentInFunctionBody = true;
node = node.parent;
}
visitor.initForIncrementalResolution();
node.accept(visitor);
}
} finally {
timer.stop('resolve references');
}
}
void _shiftEntryErrors() {
_shiftErrors_NEW(HINTS);
_shiftErrors_NEW(LINTS);
_shiftErrors_NEW(LIBRARY_UNIT_ERRORS);
_shiftErrors_NEW(RESOLVE_TYPE_NAMES_ERRORS);
_shiftErrors_NEW(RESOLVE_TYPE_BOUNDS_ERRORS);
_shiftErrors_NEW(RESOLVE_UNIT_ERRORS);
_shiftErrors_NEW(STATIC_VARIABLE_RESOLUTION_ERRORS_IN_UNIT);
_shiftErrors_NEW(STRONG_MODE_ERRORS);
_shiftErrors_NEW(VARIABLE_REFERENCE_ERRORS);
_shiftErrors_NEW(VERIFY_ERRORS);
}
void _shiftErrors(List<AnalysisError> errors) {
for (AnalysisError error in errors) {
if (_alreadyShiftedErrors.add(error)) {
int errorOffset = error.offset;
if (errorOffset > _updateOffset) {
error.offset += _updateDelta;
}
}
}
}
void _shiftErrors_NEW(ResultDescriptor<List<AnalysisError>> descriptor) {
List<AnalysisError> errors = newUnitEntry.getValue(descriptor);
_shiftErrors(errors);
}
void _updateCache() {
if (newSourceEntry != null) {
LoggingTimer timer = logger.startTimer();
try {
newSourceEntry.setState(CONTENT, CacheState.INVALID,
delta: new IncrementalBodyDelta(_source, _updateOffset,
_updateEndOld, _updateEndNew, _updateDelta));
} finally {
timer.stop('invalidate cache with delta');
}
}
}
void _updateElementNameOffsets() {
LoggingTimer timer = logger.startTimer();
try {
_definingUnit.accept(
new _ElementOffsetUpdater(_updateOffset, _updateDelta, _cache));
_definingUnit.afterIncrementalResolution();
} finally {
timer.stop('update element offsets');
}
}
void _updateEntry() {
_updateErrors_NEW(RESOLVE_TYPE_NAMES_ERRORS, []);
_updateErrors_NEW(RESOLVE_TYPE_BOUNDS_ERRORS, []);
_updateErrors_NEW(RESOLVE_UNIT_ERRORS, _resolveErrors);
_updateErrors_NEW(VARIABLE_REFERENCE_ERRORS, []);
_updateErrors_NEW(VERIFY_ERRORS, _verifyErrors);
// invalidate results we don't update incrementally
newUnitEntry.setState(STRONG_MODE_ERRORS, CacheState.INVALID);
newUnitEntry.setState(USED_IMPORTED_ELEMENTS, CacheState.INVALID);
newUnitEntry.setState(USED_LOCAL_ELEMENTS, CacheState.INVALID);
newUnitEntry.setState(HINTS, CacheState.INVALID);
newUnitEntry.setState(LINTS, CacheState.INVALID);
}
List<AnalysisError> _updateErrors(
List<AnalysisError> oldErrors, List<AnalysisError> newErrors) {
List<AnalysisError> errors = new List<AnalysisError>();
// add updated old errors
for (AnalysisError error in oldErrors) {
int errorOffset = error.offset;
if (errorOffset < _updateOffset) {
errors.add(error);
} else if (errorOffset > _updateEndOld) {
error.offset += _updateDelta;
errors.add(error);
}
}
// add new errors
for (AnalysisError error in newErrors) {
int errorOffset = error.offset;
if (errorOffset > _updateOffset && errorOffset < _updateEndNew) {
errors.add(error);
}
}
// done
return errors;
}
void _updateErrors_NEW(ResultDescriptor<List<AnalysisError>> descriptor,
List<AnalysisError> newErrors) {
List<AnalysisError> oldErrors = newUnitEntry.getValue(descriptor);
List<AnalysisError> errors = _updateErrors(oldErrors, newErrors);
newUnitEntry.setValueIncremental(descriptor, errors, true);
}
void _verify(AstNode node) {
LoggingTimer timer = logger.startTimer();
try {
RecordingErrorListener errorListener = new RecordingErrorListener();
ErrorReporter errorReporter = new ErrorReporter(errorListener, _source);
ErrorVerifier errorVerifier = new ErrorVerifier(
errorReporter,
_definingLibrary,
_typeProvider,
new InheritanceManager(_definingLibrary),
_context.analysisOptions.enableSuperMixins);
if (_resolutionContext.enclosingClassDeclaration != null) {
errorVerifier.visitClassDeclarationIncrementally(
_resolutionContext.enclosingClassDeclaration);
}
node.accept(errorVerifier);
_verifyErrors = errorListener.getErrorsForSource(_source);
} finally {
timer.stop('verify');
}
}
}
class PoorMansIncrementalResolver {
final TypeProvider _typeProvider;
final Source _unitSource;
final AnalysisCache _cache;
/**
* The [CacheEntry] corresponding to the source being resolved.
*/
final CacheEntry _sourceEntry;
/**
* The [CacheEntry] corresponding to the [LibrarySpecificUnit] being resolved.
*/
final CacheEntry _unitEntry;
final CompilationUnit _oldUnit;
CompilationUnitElement _unitElement;
int _updateOffset;
int _updateDelta;
int _updateEndOld;
int _updateEndNew;
LineInfo _newLineInfo;
List<AnalysisError> _newScanErrors = <AnalysisError>[];
List<AnalysisError> _newParseErrors = <AnalysisError>[];
PoorMansIncrementalResolver(
this._typeProvider,
this._unitSource,
this._cache,
this._sourceEntry,
this._unitEntry,
this._oldUnit,
bool resolveApiChanges);
/**
* Attempts to update [_oldUnit] to the state corresponding to [newCode].
* Returns `true` if success, or `false` otherwise.
* The [_oldUnit] might be damaged.
*/
bool resolve(String newCode) {
logger.enter('diff/resolve $_unitSource');
try {
// prepare old unit
if (!_areCurlyBracketsBalanced(_oldUnit.beginToken)) {
logger.log('Unbalanced number of curly brackets in the old unit.');
return false;
}
_unitElement = _oldUnit.element;
// prepare new unit
CompilationUnit newUnit = _parseUnit(newCode);
if (!_areCurlyBracketsBalanced(newUnit.beginToken)) {
logger.log('Unbalanced number of curly brackets in the new unit.');
return false;
}
// find difference
_TokenPair firstPair =
_findFirstDifferentToken(_oldUnit.beginToken, newUnit.beginToken);
_TokenPair lastPair =
_findLastDifferentToken(_oldUnit.endToken, newUnit.endToken);
if (firstPair != null && lastPair != null) {
int firstOffsetOld = firstPair.oldToken.offset;
int firstOffsetNew = firstPair.newToken.offset;
int lastOffsetOld = lastPair.oldToken.end;
int lastOffsetNew = lastPair.newToken.end;
int beginOffsetOld = math.min(firstOffsetOld, lastOffsetOld);
int endOffsetOld = math.max(firstOffsetOld, lastOffsetOld);
int beginOffsetNew = math.min(firstOffsetNew, lastOffsetNew);
int endOffsetNew = math.max(firstOffsetNew, lastOffsetNew);
// A pure whitespace change.
if (identical(firstPair.oldToken, lastPair.oldToken) &&
identical(firstPair.newToken, lastPair.newToken) &&
firstPair.kind == _TokenDifferenceKind.OFFSET) {
_updateOffset = beginOffsetOld - 1;
_updateEndOld = endOffsetOld;
_updateEndNew = endOffsetNew;
_updateDelta = newUnit.length - _oldUnit.length;
logger.log('Whitespace change.');
_shiftTokens(firstPair.oldToken, true);
IncrementalResolver incrementalResolver = new IncrementalResolver(
_cache,
_sourceEntry,
_unitEntry,
_unitElement,
_updateOffset,
_updateEndOld,
_updateEndNew);
incrementalResolver._updateCache();
incrementalResolver._updateElementNameOffsets();
incrementalResolver._shiftEntryErrors();
_updateEntry();
logger.log('Success.');
return true;
}
// A Dart documentation comment change.
{
Token firstOldToken = firstPair.oldToken;
Token firstNewToken = firstPair.newToken;
Token lastOldToken = lastPair.oldToken;
Token lastNewToken = lastPair.newToken;
if (firstOldToken is DocumentationCommentToken &&
firstNewToken is DocumentationCommentToken &&
lastOldToken is DocumentationCommentToken &&
lastNewToken is DocumentationCommentToken &&
identical(firstOldToken.parent, lastOldToken.parent) &&
identical(firstNewToken.parent, lastNewToken.parent)) {
_updateOffset = beginOffsetOld;
_updateEndOld = firstOldToken.parent.offset;
_updateEndNew = firstNewToken.parent.offset;
_updateDelta = newUnit.length - _oldUnit.length;
bool success =
_resolveCommentDoc(newUnit, firstOldToken, firstNewToken);
logger.log('Documentation comment resolved: $success');
return success;
}
}
// Find nodes covering the "old" and "new" token ranges.
AstNode oldNode =
_findNodeCovering(_oldUnit, beginOffsetOld, endOffsetOld - 1);
AstNode newNode =
_findNodeCovering(newUnit, beginOffsetNew, endOffsetNew - 1);
logger.log(() => 'oldNode: $oldNode');
logger.log(() => 'newNode: $newNode');
// Try to find the smallest common node, a FunctionBody currently.
{
List<AstNode> oldParents = _getParents(oldNode);
List<AstNode> newParents = _getParents(newNode);
// fail if an initializer change
if (oldParents.any((n) => n is ConstructorInitializer) ||
newParents.any((n) => n is ConstructorInitializer)) {
logger.log('Failure: a change in a constructor initializer');
return false;
}
// find matching methods / bodies
int length = math.min(oldParents.length, newParents.length);
bool found = false;
for (int i = 0; i < length; i++) {
AstNode oldParent = oldParents[i];
AstNode newParent = newParents[i];
if (oldParent is CompilationUnit && newParent is CompilationUnit) {
int oldLength = oldParent.declarations.length;
int newLength = newParent.declarations.length;
if (oldLength != newLength) {
logger.log(
'Failure: unit declarations mismatch $oldLength vs. $newLength');
return false;
}
} else if (oldParent is ClassDeclaration &&
newParent is ClassDeclaration) {
int oldLength = oldParent.members.length;
int newLength = newParent.members.length;
if (oldLength != newLength) {
logger.log(
'Failure: class declarations mismatch $oldLength vs. $newLength');
return false;
}
} else if (oldParent is FunctionDeclaration &&
newParent is FunctionDeclaration ||
oldParent is ConstructorDeclaration &&
newParent is ConstructorDeclaration ||
oldParent is MethodDeclaration &&
newParent is MethodDeclaration) {
if (oldParents.length == i || newParents.length == i) {
return false;
}
} else if (oldParent is FunctionBody && newParent is FunctionBody) {
if (oldParent is BlockFunctionBody &&
newParent is BlockFunctionBody) {
if (oldParent.isAsynchronous != newParent.isAsynchronous) {
logger.log('Failure: body async mismatch.');
return false;
}
if (oldParent.isGenerator != newParent.isGenerator) {
logger.log('Failure: body generator mismatch.');
return false;
}
oldNode = oldParent;
newNode = newParent;
found = true;
break;
}
logger.log('Failure: not a block function body.');
return false;
} else if (oldParent is FunctionExpression &&
newParent is FunctionExpression) {
// skip
} else {
logger.log('Failure: old and new parent mismatch'
' ${oldParent.runtimeType} vs. ${newParent.runtimeType}');
return false;
}
}
if (!found) {
logger.log('Failure: no enclosing function body or executable.');
return false;
}
}
logger.log(() => 'oldNode: $oldNode');
logger.log(() => 'newNode: $newNode');
// prepare update range
_updateOffset = oldNode.offset;
_updateEndOld = oldNode.end;
_updateEndNew = newNode.end;
_updateDelta = _updateEndNew - _updateEndOld;
// replace node
NodeReplacer.replace(oldNode, newNode);
// update token references
{
Token oldBeginToken = _getBeginTokenNotComment(oldNode);
Token newBeginToken = _getBeginTokenNotComment(newNode);
if (oldBeginToken.previous.type == TokenType.EOF) {
_oldUnit.beginToken = newBeginToken;
} else {
oldBeginToken.previous.setNext(newBeginToken);
}
newNode.endToken.setNext(oldNode.endToken.next);
_shiftTokens(oldNode.endToken.next);
}
// perform incremental resolution
IncrementalResolver incrementalResolver = new IncrementalResolver(
_cache,
_sourceEntry,
_unitEntry,
_unitElement,
_updateOffset,
_updateEndOld,
_updateEndNew);
incrementalResolver.resolve(newNode);
// update DartEntry
_updateEntry();
logger.log('Success.');
return true;
}
} catch (e, st) {
logger.logException(e, st);
logger.log('Failure: exception.');
// The incremental resolver log is usually turned off,
// so also log the exception to the instrumentation log.
AnalysisEngine.instance.logger.logError(
'Failure in incremental resolver', new CaughtException(e, st));
} finally {
logger.exit();
}
return false;
}
CompilationUnit _parseUnit(String code) {
LoggingTimer timer = logger.startTimer();
try {
Token token = _scan(code);
RecordingErrorListener errorListener = new RecordingErrorListener();
Parser parser = new Parser(_unitSource, errorListener);
AnalysisOptions options = _unitElement.context.analysisOptions;
parser.parseGenericMethodComments = options.strongMode;
CompilationUnit unit = parser.parseCompilationUnit(token);
_newParseErrors = errorListener.errors;
return unit;
} finally {
timer.stop('parse');
}
}
/**
* Attempts to resolve a documentation comment change.
* Returns `true` if success.
*/
bool _resolveCommentDoc(
CompilationUnit newUnit, CommentToken oldToken, CommentToken newToken) {
if (oldToken == null || newToken == null) {
return false;
}
// find nodes
int offset = oldToken.offset;
logger.log('offset: $offset');
AstNode oldNode = _findNodeCovering(_oldUnit, offset, offset);
AstNode newNode = _findNodeCovering(newUnit, offset, offset);
if (oldNode is! Comment || newNode is! Comment) {
return false;
}
Comment oldComment = oldNode;
Comment newComment = newNode;
logger.log('oldComment.beginToken: ${oldComment.beginToken}');
logger.log('newComment.beginToken: ${newComment.beginToken}');
// update token references
_shiftTokens(oldToken.parent);
_setPrecedingComments(oldToken.parent, newComment.tokens.first);
// replace node
NodeReplacer.replace(oldComment, newComment);
// update elements
IncrementalResolver incrementalResolver = new IncrementalResolver(
_cache,
_sourceEntry,
_unitEntry,
_unitElement,
_updateOffset,
_updateEndOld,
_updateEndNew);
incrementalResolver._updateCache();
incrementalResolver._updateElementNameOffsets();
incrementalResolver._shiftEntryErrors();
_updateEntry();
// resolve references in the comment
incrementalResolver._resolveReferences(newComment);
// update 'documentationComment' of the parent element(s)
{
AstNode parent = newComment.parent;
if (parent is AnnotatedNode) {
setElementDocumentationForVariables(VariableDeclarationList list) {
for (VariableDeclaration variable in list.variables) {
Element variableElement = variable.element;
if (variableElement is ElementImpl) {
setElementDocumentationComment(variableElement, parent);
}
}
}
Element parentElement = ElementLocator.locate(newComment.parent);
if (parentElement is ElementImpl) {
setElementDocumentationComment(parentElement, parent);
} else if (parent is FieldDeclaration) {
setElementDocumentationForVariables(parent.fields);
} else if (parent is TopLevelVariableDeclaration) {
setElementDocumentationForVariables(parent.variables);
}
}
}
// OK
return true;
}
Token _scan(String code) {
RecordingErrorListener errorListener = new RecordingErrorListener();
CharSequenceReader reader = new CharSequenceReader(code);
Scanner scanner = new Scanner(_unitSource, reader, errorListener);
AnalysisOptions options = _unitElement.context.analysisOptions;
scanner.scanGenericMethodComments = options.strongMode;
Token token = scanner.tokenize();
_newLineInfo = new LineInfo(scanner.lineStarts);
_newScanErrors = errorListener.errors;
return token;
}
/**
* Set the given [comment] as a "precedingComments" for [token].
*/
void _setPrecedingComments(Token token, CommentToken comment) {
if (token is BeginTokenWithComment) {
token.precedingComments = comment;
} else if (token is KeywordTokenWithComment) {
token.precedingComments = comment;
} else if (token is StringTokenWithComment) {
token.precedingComments = comment;
} else if (token is TokenWithComment) {
token.precedingComments = comment;
} else {
Type parentType = token?.runtimeType;
throw new AnalysisException('Uknown parent token type: $parentType');
}
}
void _shiftTokens(Token token, [bool goUpComment = false]) {
while (token != null) {
if (goUpComment && token is CommentToken) {
token = (token as CommentToken).parent;
}
if (token.offset > _updateOffset) {
token.offset += _updateDelta;
}
// comments
_shiftTokens(token.precedingComments);
if (token is DocumentationCommentToken) {
for (Token reference in token.references) {
_shiftTokens(reference);
}
}
// next
if (token.type == TokenType.EOF) {
break;
}
token = token.next;
}
}
void _updateEntry() {
// scan results
_sourceEntry.setValueIncremental(SCAN_ERRORS, _newScanErrors, true);
_sourceEntry.setValueIncremental(LINE_INFO, _newLineInfo, false);
// parse results
_sourceEntry.setValueIncremental(PARSE_ERRORS, _newParseErrors, true);
_sourceEntry.setValueIncremental(PARSED_UNIT, _oldUnit, false);
// referenced names
ReferencedNames referencedNames = new ReferencedNames(_unitSource);
new ReferencedNamesBuilder(referencedNames).build(_oldUnit);
_sourceEntry.setValueIncremental(REFERENCED_NAMES, referencedNames, false);
}
/**
* Checks if [token] has a balanced number of open and closed curly brackets.
*/
static bool _areCurlyBracketsBalanced(Token token) {
int numOpen = _getTokenCount(token, TokenType.OPEN_CURLY_BRACKET);
int numOpen2 =
_getTokenCount(token, TokenType.STRING_INTERPOLATION_EXPRESSION);
int numClosed = _getTokenCount(token, TokenType.CLOSE_CURLY_BRACKET);
return numOpen + numOpen2 == numClosed;
}
static _TokenDifferenceKind _compareToken(
Token oldToken, Token newToken, int delta) {
if (oldToken == null && newToken == null) {
return null;
}
if (oldToken == null || newToken == null) {
return _TokenDifferenceKind.CONTENT;
}
if (oldToken.type != newToken.type) {
return _TokenDifferenceKind.CONTENT;
}
if (oldToken.lexeme != newToken.lexeme) {
return _TokenDifferenceKind.CONTENT;
}
if (newToken.offset - oldToken.offset != delta) {
return _TokenDifferenceKind.OFFSET;
}
return null;
}
static _TokenPair _findFirstDifferentToken(Token oldToken, Token newToken) {
while (oldToken.type != TokenType.EOF || newToken.type != TokenType.EOF) {
if (oldToken.type == TokenType.EOF || newToken.type == TokenType.EOF) {
return new _TokenPair(_TokenDifferenceKind.CONTENT, oldToken, newToken);
}
// compare comments
{
Token oldComment = oldToken.precedingComments;
Token newComment = newToken.precedingComments;
while (true) {
_TokenDifferenceKind diffKind =
_compareToken(oldComment, newComment, 0);
if (diffKind != null) {
return new _TokenPair(
diffKind, oldComment ?? oldToken, newComment ?? newToken);
}
if (oldComment == null && newComment == null) {
break;
}
oldComment = oldComment.next;
newComment = newComment.next;
}
}
// compare tokens
_TokenDifferenceKind diffKind = _compareToken(oldToken, newToken, 0);
if (diffKind != null) {
return new _TokenPair(diffKind, oldToken, newToken);
}
// next tokens
oldToken = oldToken.next;
newToken = newToken.next;
}
// no difference
return null;
}
static _TokenPair _findLastDifferentToken(Token oldToken, Token newToken) {
int delta = newToken.offset - oldToken.offset;
Token prevOldToken;
Token prevNewToken;
while (oldToken.previous != oldToken && newToken.previous != newToken) {
// compare tokens
_TokenDifferenceKind diffKind = _compareToken(oldToken, newToken, delta);
if (diffKind != null) {
return new _TokenPair(diffKind, prevOldToken, prevNewToken);
}
prevOldToken = oldToken;
prevNewToken = newToken;
// compare comments
{
Token oldComment = oldToken.precedingComments;
Token newComment = newToken.precedingComments;
while (oldComment?.next != null) {
oldComment = oldComment.next;
}
while (newComment?.next != null) {
newComment = newComment.next;
}
while (true) {
_TokenDifferenceKind diffKind =
_compareToken(oldComment, newComment, delta);
if (diffKind != null) {
return new _TokenPair(
diffKind, oldComment ?? oldToken, newComment ?? newToken);
}
if (oldComment == null && newComment == null) {
break;
}
prevOldToken = oldComment;
prevNewToken = newComment;
oldComment = oldComment.previous;
newComment = newComment.previous;
}
}
// next tokens
oldToken = oldToken.previous;
newToken = newToken.previous;
}
return null;
}
static AstNode _findNodeCovering(AstNode root, int offset, int end) {
NodeLocator nodeLocator = new NodeLocator(offset, end);
return nodeLocator.searchWithin(root);
}
static Token _getBeginTokenNotComment(AstNode node) {
Token oldBeginToken = node.beginToken;
if (oldBeginToken is CommentToken) {
return oldBeginToken.parent;
}
return oldBeginToken;
}
static List<AstNode> _getParents(AstNode node) {
List<AstNode> parents = <AstNode>[];
while (node != null) {
parents.insert(0, node);
node = node.parent;
}
return parents;
}
/**
* Returns number of tokens with the given [type].
*/
static int _getTokenCount(Token token, TokenType type) {
int count = 0;
while (token.type != TokenType.EOF) {
if (token.type == type) {
count++;
}
token = token.next;
}
return count;
}
}
/**
* The context to resolve an [AstNode] in.
*/
class ResolutionContext {
CompilationUnitElement enclosingUnit;
ClassDeclaration enclosingClassDeclaration;
ClassElement enclosingClass;
Scope scope;
}
/**
* Instances of the class [ResolutionContextBuilder] build the context for a
* given node in an AST structure. At the moment, this class only handles
* top-level and class-level declarations.
*/
class ResolutionContextBuilder {
/**
* The class containing the enclosing [CompilationUnitElement].
*/
CompilationUnitElement _enclosingUnit;
/**
* The class containing the enclosing [ClassDeclaration], or `null` if we are
* not in the scope of a class.
*/
ClassDeclaration _enclosingClassDeclaration;
/**
* The class containing the enclosing [ClassElement], or `null` if we are not
* in the scope of a class.
*/
ClassElement _enclosingClass;
Scope _scopeFor(AstNode node) {
if (node is CompilationUnit) {
return _scopeForAstNode(node);
}
AstNode parent = node.parent;
if (parent == null) {
throw new AnalysisException(
"Cannot create scope: node is not part of a CompilationUnit");
}
return _scopeForAstNode(parent);
}
/**
* Return the scope in which the given AST structure should be resolved.
*
* *Note:* This method needs to be kept in sync with
* [IncrementalResolver.canBeResolved].
*
* [node] - the root of the AST structure to be resolved.
*
* Throws [AnalysisException] if the AST structure has not been resolved or
* is not part of a [CompilationUnit]
*/
Scope _scopeForAstNode(AstNode node) {
if (node is CompilationUnit) {
return _scopeForCompilationUnit(node);
}
AstNode parent = node.parent;
if (parent == null) {
throw new AnalysisException(
"Cannot create scope: node is not part of a CompilationUnit");
}
Scope scope = _scopeForAstNode(parent);
if (node is ClassDeclaration) {
_enclosingClassDeclaration = node;
_enclosingClass = node.element;
if (_enclosingClass == null) {
throw new AnalysisException(
"Cannot build a scope for an unresolved class");
}
scope = new ClassScope(
new TypeParameterScope(scope, _enclosingClass), _enclosingClass);
} else if (node is ClassTypeAlias) {
ClassElement element = node.element;
if (element == null) {
throw new AnalysisException(
"Cannot build a scope for an unresolved class type alias");
}
scope = new ClassScope(new TypeParameterScope(scope, element), element);
} else if (node is ConstructorDeclaration) {
ConstructorElement element = node.element;
if (element == null) {
throw new AnalysisException(
"Cannot build a scope for an unresolved constructor");
}
FunctionScope functionScope = new FunctionScope(scope, element);
functionScope.defineParameters();
scope = functionScope;
} else if (node is FunctionDeclaration) {
ExecutableElement element = node.element;
if (element == null) {
throw new AnalysisException(
"Cannot build a scope for an unresolved function");
}
FunctionScope functionScope = new FunctionScope(scope, element);
functionScope.defineParameters();
scope = functionScope;
} else if (node is FunctionTypeAlias) {
scope = new FunctionTypeScope(scope, node.element);
} else if (node is MethodDeclaration) {
ExecutableElement element = node.element;
if (element == null) {
throw new AnalysisException(
"Cannot build a scope for an unresolved method");
}
FunctionScope functionScope = new FunctionScope(scope, element);
functionScope.defineParameters();
scope = functionScope;
}
return scope;
}
Scope _scopeForCompilationUnit(CompilationUnit node) {
_enclosingUnit = node.element;
if (_enclosingUnit == null) {
throw new AnalysisException(
"Cannot create scope: compilation unit is not resolved");
}
LibraryElement libraryElement = _enclosingUnit.library;
if (libraryElement == null) {
throw new AnalysisException(
"Cannot create scope: compilation unit is not part of a library");
}
return new LibraryScope(libraryElement);
}
/**
* Return the context in which the given AST structure should be resolved.
*
* [node] - the root of the AST structure to be resolved.
*
* Throws [AnalysisException] if the AST structure has not been resolved or
* is not part of a [CompilationUnit]
*/
static ResolutionContext contextFor(AstNode node) {
if (node == null) {
throw new AnalysisException("Cannot create context: node is null");
}
// build scope
ResolutionContextBuilder builder = new ResolutionContextBuilder();
Scope scope = builder._scopeFor(node);
// prepare context
ResolutionContext context = new ResolutionContext();
context.scope = scope;
context.enclosingUnit = builder._enclosingUnit;
context.enclosingClassDeclaration = builder._enclosingClassDeclaration;
context.enclosingClass = builder._enclosingClass;
return context;
}
}
/**
* Adjusts the location of each Element that moved.
*
* Since `==` and `hashCode` of a local variable or function Element are based
* on the element name offsets, we also need to remove these elements from the
* cache to avoid a memory leak. TODO(scheglov) fix and remove this
*/
class _ElementOffsetUpdater extends GeneralizingElementVisitor {
final int updateOffset;
final int updateDelta;
final AnalysisCache cache;
_ElementOffsetUpdater(this.updateOffset, this.updateDelta, this.cache);
@override
visitElement(Element element) {
// name offset
int nameOffset = element.nameOffset;
if (nameOffset > updateOffset) {
(element as ElementImpl).nameOffset = nameOffset + updateDelta;
if (element is ConstVariableElement) {
Expression initializer = element.constantInitializer;
if (initializer != null) {
_shiftTokens(initializer.beginToken);
}
_shiftErrors(element.evaluationResult?.errors);
}
}
// code range
if (element is ElementImpl) {
int oldOffset = element.codeOffset;
int oldLength = element.codeLength;
if (oldOffset != null) {
int newOffset = oldOffset;
int newLength = oldLength;
newOffset += oldOffset > updateOffset ? updateDelta : 0;
if (oldOffset <= updateOffset && updateOffset < oldOffset + oldLength) {
newLength += updateDelta;
}
if (newOffset != oldOffset || newLength != oldLength) {
element.setCodeRange(newOffset, newLength);
}
}
}
// visible range
if (element is LocalElement) {
SourceRange visibleRange = element.visibleRange;
if (visibleRange != null) {
int oldOffset = visibleRange.offset;
int oldLength = visibleRange.length;
int newOffset = oldOffset;
int newLength = oldLength;
newOffset += oldOffset > updateOffset ? updateDelta : 0;
newLength += visibleRange.contains(updateOffset) ? updateDelta : 0;
if (newOffset != oldOffset || newLength != oldLength) {
if (element is FunctionElementImpl) {
element.setVisibleRange(newOffset, newLength);
} else if (element is LocalVariableElementImpl) {
element.setVisibleRange(newOffset, newLength);
} else if (element is ParameterElementImpl) {
element.setVisibleRange(newOffset, newLength);
}
}
}
}
super.visitElement(element);
}
void _shiftErrors(List<AnalysisError> errors) {
if (errors != null) {
for (AnalysisError error in errors) {
int errorOffset = error.offset;
if (errorOffset > updateOffset) {
error.offset += updateDelta;
}
}
}
}
void _shiftTokens(Token token) {
while (token != null) {
if (token.offset > updateOffset) {
token.offset += updateDelta;
}
// comments
_shiftTokens(token.precedingComments);
if (token is DocumentationCommentToken) {
for (Token reference in token.references) {
_shiftTokens(reference);
}
}
// next
if (token.type == TokenType.EOF) {
break;
}
token = token.next;
}
}
}
/**
* Describes how two [Token]s are different.
*/
class _TokenDifferenceKind {
static const CONTENT = const _TokenDifferenceKind('CONTENT');
static const OFFSET = const _TokenDifferenceKind('OFFSET');
final String name;
const _TokenDifferenceKind(this.name);
@override
String toString() => name;
}
class _TokenPair {
final _TokenDifferenceKind kind;
final Token oldToken;
final Token newToken;
_TokenPair(this.kind, this.oldToken, this.newToken);
}