blob: a01122dae50b314def9fa90d85546fffcd756be4 [file] [log] [blame]
// Copyright (c) 2015, 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.task.incremental_element_builder;
import 'dart:collection';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/scanner.dart';
import 'package:analyzer/src/generated/source.dart';
/**
* Incrementally updates the existing [unitElement] and builds elements for
* the [newUnit].
*/
class IncrementalCompilationUnitElementBuilder {
final Source source;
final Source librarySource;
final CompilationUnit oldUnit;
final CompilationUnitElementImpl unitElement;
final CompilationUnit newUnit;
final ElementHolder holder = new ElementHolder();
IncrementalCompilationUnitElementBuilder(
CompilationUnit oldUnit, this.newUnit)
: oldUnit = oldUnit,
unitElement = oldUnit.element,
source = oldUnit.element.source,
librarySource = (oldUnit.element as CompilationUnitElementImpl).librarySource;
void build() {
new CompilationUnitBuilder().buildCompilationUnit(
source, newUnit, librarySource);
_processDirectives();
_processUnitMembers();
newUnit.element = unitElement;
}
void _addElementsToHolder(CompilationUnitMember node) {
List<Element> elements = _getElements(node);
elements.forEach(_addElementToHolder);
}
void _addElementToHolder(Element element) {
if (element is PropertyAccessorElement) {
holder.addAccessor(element);
} else if (element is ClassElement) {
if (element.isEnum) {
holder.addEnum(element);
} else {
holder.addType(element);
}
} else if (element is FunctionElement) {
holder.addFunction(element);
} else if (element is FunctionTypeAliasElement) {
holder.addTypeAlias(element);
} else if (element is TopLevelVariableElement) {
holder.addTopLevelVariable(element);
}
}
void _processDirectives() {
Map<String, Directive> oldDirectiveMap = new HashMap<String, Directive>();
for (Directive oldDirective in oldUnit.directives) {
String code = TokenUtils.getFullCode(oldDirective);
oldDirectiveMap[code] = oldDirective;
}
// Replace new nodes with the identical old nodes.
for (Directive newDirective in newUnit.directives) {
String code = TokenUtils.getFullCode(newDirective);
// Prepare an old directive.
Directive oldDirective = oldDirectiveMap[code];
if (oldDirective == null) {
continue;
}
// URI's must be resolved to the same sources.
if (newDirective is UriBasedDirective &&
oldDirective is UriBasedDirective) {
if (oldDirective.source != newDirective.source) {
continue;
}
}
// Do replacement.
_replaceNode(newDirective, oldDirective);
}
}
void _processUnitMembers() {
Map<String, CompilationUnitMember> oldNodeMap =
new HashMap<String, CompilationUnitMember>();
for (CompilationUnitMember oldNode in oldUnit.declarations) {
String code = TokenUtils.getFullCode(oldNode);
oldNodeMap[code] = oldNode;
}
// Replace new nodes with the identical old nodes.
for (CompilationUnitMember newNode in newUnit.declarations) {
String code = TokenUtils.getFullCode(newNode);
// Prepare an old node.
CompilationUnitMember oldNode = oldNodeMap[code];
if (oldNode == null) {
_addElementsToHolder(newNode);
continue;
}
// Do replacement.
_replaceNode(newNode, oldNode);
_addElementsToHolder(oldNode);
}
// Update CompilationUnitElement.
unitElement.accessors = holder.accessors;
unitElement.enums = holder.enums;
unitElement.functions = holder.functions;
unitElement.typeAliases = holder.typeAliases;
unitElement.types = holder.types;
unitElement.topLevelVariables = holder.topLevelVariables;
holder.validate();
}
/**
* Replaces [newNode] with [oldNode], updates tokens and elements.
* The nodes must have the same tokens, but offsets may be different.
*/
void _replaceNode(AstNode newNode, AstNode oldNode) {
// Replace node.
NodeReplacer.replace(newNode, oldNode);
// Replace tokens.
{
Token oldBeginToken = TokenUtils.getBeginTokenNotComment(newNode);
Token newBeginToken = TokenUtils.getBeginTokenNotComment(oldNode);
oldBeginToken.previous.setNext(newBeginToken);
oldNode.endToken.setNext(newNode.endToken.next);
}
// Change tokens offsets.
Map<int, int> offsetMap = new HashMap<int, int>();
TokenUtils.copyTokenOffsets(offsetMap, oldNode.beginToken,
newNode.beginToken, oldNode.endToken, newNode.endToken, true);
// Change elements offsets.
{
var visitor = new _UpdateElementOffsetsVisitor(offsetMap);
List<Element> elements = _getElements(oldNode);
for (Element element in elements) {
element.accept(visitor);
}
}
}
/**
* Returns [Element]s that are declared directly by the given [node].
* This does not include any child elements - parameters, local variables.
*
* Usually just one [Element] is returned, but [VariableDeclarationList]
* nodes may declare more than one.
*/
static List<Element> _getElements(AstNode node) {
List<Element> elements = <Element>[];
if (node is TopLevelVariableDeclaration) {
VariableDeclarationList variableList = node.variables;
if (variableList != null) {
for (VariableDeclaration variable in variableList.variables) {
TopLevelVariableElement element = variable.element;
elements.add(element);
elements.add(element.getter);
elements.add(element.setter);
}
}
} else if (node is PartDirective || node is PartOfDirective) {
} else if (node is Directive && node.element != null) {
elements.add(node.element);
} else if (node is Declaration && node.element != null) {
Element element = node.element;
elements.add(element);
if (element is PropertyAccessorElement) {
elements.add(element.variable);
}
}
return elements;
}
}
/**
* Utilities for [Token] manipulations.
*/
class TokenUtils {
static const String _SEPARATOR = "\uFFFF";
/**
* Copy offsets from [newToken]s to [oldToken]s.
*/
static void copyTokenOffsets(Map<int, int> offsetMap, Token oldToken,
Token newToken, Token oldEndToken, Token newEndToken,
[bool goUpComment = false]) {
if (oldToken is CommentToken && newToken is CommentToken) {
if (goUpComment) {
copyTokenOffsets(offsetMap, (oldToken as CommentToken).parent,
(newToken as CommentToken).parent, oldEndToken, newEndToken);
}
while (oldToken != null) {
offsetMap[oldToken.offset] = newToken.offset;
oldToken.offset = newToken.offset;
oldToken = oldToken.next;
newToken = newToken.next;
}
assert(oldToken == null);
assert(newToken == null);
return;
}
while (true) {
if (oldToken.precedingComments != null) {
assert(newToken.precedingComments != null);
copyTokenOffsets(offsetMap, oldToken.precedingComments,
newToken.precedingComments, oldEndToken, newEndToken);
}
offsetMap[oldToken.offset] = newToken.offset;
oldToken.offset = newToken.offset;
if (oldToken == oldEndToken) {
assert(newToken == newEndToken);
break;
}
oldToken = oldToken.next;
newToken = newToken.next;
}
}
static Token getBeginTokenNotComment(AstNode node) {
Token oldBeginToken = node.beginToken;
if (oldBeginToken is CommentToken) {
oldBeginToken = (oldBeginToken as CommentToken).parent;
}
return oldBeginToken;
}
/**
* Return the token string of all the [node] tokens.
*/
static String getFullCode(AstNode node) {
List<Token> tokens = getTokens(node);
return joinTokens(tokens);
}
/**
* Returns all tokends (including comments) of the given [node].
*/
static List<Token> getTokens(AstNode node) {
List<Token> tokens = <Token>[];
Token token = getBeginTokenNotComment(node);
Token endToken = node.endToken;
while (true) {
// append comment tokens
for (Token commentToken = token.precedingComments;
commentToken != null;
commentToken = commentToken.next) {
tokens.add(commentToken);
}
// append token
tokens.add(token);
// next token
if (token == endToken) {
break;
}
token = token.next;
}
return tokens;
}
static String joinTokens(List<Token> tokens) {
return tokens.map((token) => token.lexeme).join(_SEPARATOR);
}
}
/**
* Updates name offsets of [Element]s according to the [map].
*/
class _UpdateElementOffsetsVisitor extends GeneralizingElementVisitor {
final Map<int, int> map;
_UpdateElementOffsetsVisitor(this.map);
void visitElement(Element element) {
if (element is CompilationUnitElement) {
return;
}
if (element.isSynthetic) {
return;
}
int oldOffset = element.nameOffset;
int newOffset = map[oldOffset];
assert(newOffset != null);
(element as ElementImpl).nameOffset = newOffset;
if (element is! LibraryElement) {
super.visitElement(element);
}
}
}