blob: db659c7b528b1a1cceab0ef16989068161c5a26a [file] [log] [blame]
// Copyright (c) 2016, 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.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/summary/format.dart';
import 'package:analyzer/src/summary/idl.dart';
/**
* Object that gathers information about the whole package index and then uses
* it to assemble a new [PackageIndexBuilder]. Call [index] on each compilation
* unit to be indexed, then call [assemble] to retrieve the complete index for
* the package.
*/
class PackageIndexAssembler {
/**
* Map associating referenced elements with their [_ElementInfo]s.
*/
final Map<Element, _ElementInfo> _elementMap = <Element, _ElementInfo>{};
/**
* Map associating [CompilationUnitElement]s with their identifiers, which
* are indices into [_unitLibraryUris] and [_unitUnitUris].
*/
final Map<CompilationUnitElement, int> _unitMap =
<CompilationUnitElement, int>{};
/**
* Each item of this list corresponds to the library URI of a unique
* [CompilationUnitElement]. It is an index into [_strings].
*/
final List<int> _unitLibraryUris = <int>[];
/**
* Each item of this list corresponds to the unit URI of a unique
* [CompilationUnitElement]. It is an index into [_strings].
*/
final List<int> _unitUnitUris = <int>[];
/**
* Map associating strings with their identifiers, which are indices
* into [_strings].
*/
final Map<String, int> _stringMap = <String, int>{};
/**
* List of unique strings used in this index.
*/
final List<String> _strings = <String>[];
/**
* List of information about each unit indexed in this index.
*/
final List<_UnitIndexAssembler> _units = <_UnitIndexAssembler>[];
/**
* Assemble a new [PackageIndexBuilder] using the information gathered by
* [index].
*/
PackageIndexBuilder assemble() {
List<_ElementInfo> elementInfoList = _elementMap.values.toList();
elementInfoList.sort((a, b) {
return a.offset - b.offset;
});
for (int i = 0; i < elementInfoList.length; i++) {
elementInfoList[i].id = i;
}
return new PackageIndexBuilder(
unitLibraryUris: _unitLibraryUris,
unitUnitUris: _unitUnitUris,
elementUnits: elementInfoList.map((e) => e.unitId).toList(),
elementOffsets: elementInfoList.map((e) => e.offset).toList(),
elementKinds: elementInfoList.map((e) => e.kind).toList(),
strings: _strings,
units: _units.map((unit) => unit.assemble()).toList());
}
/**
* Index the given fully resolved [unit].
*/
void index(CompilationUnit unit) {
int unitId = _getUnitId(unit.element);
_UnitIndexAssembler assembler = new _UnitIndexAssembler(this, unitId);
_units.add(assembler);
unit.accept(new _IndexContributor(assembler));
}
/**
* Return the unique [_ElementInfo] corresponding the [element]. The field
* [_ElementInfo.id] is filled by [assemble] during final sorting.
*/
_ElementInfo _getElementInfo(Element element) {
if (element is Member) {
element = (element as Member).baseElement;
}
return _elementMap.putIfAbsent(element, () {
CompilationUnitElement unitElement = getUnitElement(element);
int unitId = _getUnitId(unitElement);
int offset = element.nameOffset;
if (element is LibraryElement || element is CompilationUnitElement) {
offset = 0;
}
IndexSyntheticElementKind kind = getIndexElementKind(element);
return new _ElementInfo(unitId, offset, kind);
});
}
/**
* Add information about [str] to [_strings] if necessary, and return the
* location in this array representing [str].
*/
int _getStringId(String str) {
return _stringMap.putIfAbsent(str, () {
int id = _strings.length;
_strings.add(str);
return id;
});
}
/**
* Add information about [unitElement] to [_unitUnitUris] and
* [_unitLibraryUris] if necessary, and return the location in those
* arrays representing [unitElement].
*/
int _getUnitId(CompilationUnitElement unitElement) {
return _unitMap.putIfAbsent(unitElement, () {
assert(_unitLibraryUris.length == _unitUnitUris.length);
int id = _unitUnitUris.length;
_unitLibraryUris.add(_getUriId(unitElement.library.source.uri));
_unitUnitUris.add(_getUriId(unitElement.source.uri));
return id;
});
}
/**
* Return the identifier corresponding to [uri].
*/
int _getUriId(Uri uri) {
String str = uri.toString();
return _getStringId(str);
}
/**
* Return the kind of the given [element].
*/
static IndexSyntheticElementKind getIndexElementKind(Element element) {
if (element.isSynthetic) {
if (element is ConstructorElement) {
return IndexSyntheticElementKind.constructor;
}
if (element is PropertyAccessorElement) {
return element.isGetter
? IndexSyntheticElementKind.getter
: IndexSyntheticElementKind.setter;
}
}
return IndexSyntheticElementKind.notSynthetic;
}
/**
* Return the [CompilationUnitElement] that should be used for [element].
* Throw [StateError] if the [element] is not linked into a unit.
*/
static CompilationUnitElement getUnitElement(Element element) {
for (Element e = element; e != null; e = e.enclosingElement) {
if (e is CompilationUnitElement) {
return e;
}
if (e is LibraryElement) {
return e.definingCompilationUnit;
}
}
throw new StateError(element.toString());
}
}
/**
* Information about a single defined name. Any [_DefinedNameInfo] is always
* part of a [_UnitIndexAssembler], so [offset] should be understood within the
* context of the compilation unit pointed to by the [_UnitIndexAssembler].
*/
class _DefinedNameInfo {
/**
* The identifier of the name returned [PackageIndexAssembler._getStringId].
*/
final int nameId;
/**
* The coarse-grained kind of the defined name.
*/
final IndexNameKind kind;
/**
* The name offset of the defined element.
*/
final int offset;
_DefinedNameInfo(this.nameId, this.kind, this.offset);
}
/**
* Information about an element referenced in index.
*/
class _ElementInfo {
/**
* The identifier of the [CompilationUnitElement] containing this element.
*/
final int unitId;
/**
* The name offset of the element.
*/
final int offset;
/**
* The kind of the element.
*/
final IndexSyntheticElementKind kind;
/**
* The unique id of the element. It is set after indexing of the whole
* package is done and we are assembling the full package index.
*/
int id;
_ElementInfo(this.unitId, this.offset, this.kind);
}
/**
* Information about a single relation. Any [_ElementRelationInfo] is always
* part of a [_UnitIndexAssembler], so [offset] and [length] should be
* understood within the context of the compilation unit pointed to by the
* [_UnitIndexAssembler].
*/
class _ElementRelationInfo {
final _ElementInfo elementInfo;
final IndexRelationKind kind;
final int offset;
final int length;
final bool isQualified;
_ElementRelationInfo(
this.elementInfo, this.kind, this.offset, this.length, this.isQualified);
}
/**
* Visits a resolved AST and adds relationships into [_UnitIndexAssembler].
*/
class _IndexContributor extends GeneralizingAstVisitor {
final _UnitIndexAssembler assembler;
_IndexContributor(this.assembler);
/**
* Record definition of the given [element].
*/
void recordDefinedElement(Element element) {
if (element != null) {
String name = element.displayName;
int offset = element.nameOffset;
Element enclosing = element.enclosingElement;
if (enclosing is CompilationUnitElement) {
assembler.defineName(name, IndexNameKind.topLevel, offset);
} else if (enclosing is ClassElement) {
assembler.defineName(name, IndexNameKind.classMember, offset);
}
}
}
/**
* Record that the name [node] has a relation of the given [kind].
*/
void recordNameRelation(SimpleIdentifier node, IndexRelationKind kind) {
if (node != null) {
assembler.addNameRelation(node.name, kind, node.offset);
}
}
/**
* Record reference to the given operator [Element].
*/
void recordOperatorReference(Token operator, Element element) {
recordRelationToken(element, IndexRelationKind.IS_INVOKED_BY, operator);
}
/**
* Record that [element] has a relation of the given [kind] at the location
* of the given [node]. The flag [isQualified] is `true` if [node] has an
* explicit or implicit qualifier, so cannot be shadowed by a local
* declaration.
*/
void recordRelation(
Element element, IndexRelationKind kind, AstNode node, bool isQualified) {
if (element != null && node != null) {
recordRelationOffset(
element, kind, node.offset, node.length, isQualified);
}
}
/**
* Record that [element] has a relation of the given [kind] at the given
* [offset] and [length]. The flag [isQualified] is `true` if the relation
* has an explicit or implicit qualifier, so [element] cannot be shadowed by
* a local declaration.
*/
void recordRelationOffset(Element element, IndexRelationKind kind, int offset,
int length, bool isQualified) {
// Ignore elements that can't be referenced outside of the unit.
if (element == null ||
element is FunctionElement &&
element.enclosingElement is ExecutableElement ||
element is LabelElement ||
element is LocalVariableElement ||
element is ParameterElement &&
element.parameterKind != ParameterKind.NAMED ||
element is PrefixElement ||
element is TypeParameterElement) {
return;
}
// Add the relation.
assembler.addElementRelation(element, kind, offset, length, isQualified);
}
/**
* Record that [element] has a relation of the given [kind] at the location
* of the given [token].
*/
void recordRelationToken(
Element element, IndexRelationKind kind, Token token) {
if (element != null && token != null) {
recordRelationOffset(element, kind, token.offset, token.length, true);
}
}
/**
* Record a relation between a super [typeName] and its [Element].
*/
void recordSuperType(TypeName typeName, IndexRelationKind kind) {
Identifier name = typeName?.name;
if (name != null) {
Element element = name.staticElement;
SimpleIdentifier relNode =
name is PrefixedIdentifier ? name.identifier : name;
recordRelation(element, kind, relNode, true);
recordRelation(
element, IndexRelationKind.IS_REFERENCED_BY, relNode, true);
typeName.typeArguments?.accept(this);
}
}
void recordUriReference(Element element, UriBasedDirective directive) {
recordRelation(
element, IndexRelationKind.IS_REFERENCED_BY, directive.uri, true);
}
@override
visitAssignmentExpression(AssignmentExpression node) {
recordOperatorReference(node.operator, node.bestElement);
super.visitAssignmentExpression(node);
}
@override
visitBinaryExpression(BinaryExpression node) {
recordOperatorReference(node.operator, node.bestElement);
super.visitBinaryExpression(node);
}
@override
visitClassDeclaration(ClassDeclaration node) {
if (node.extendsClause == null) {
ClassElement objectElement = node.element.supertype?.element;
recordRelationOffset(objectElement, IndexRelationKind.IS_EXTENDED_BY,
node.name.offset, 0, true);
}
super.visitClassDeclaration(node);
}
@override
visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
SimpleIdentifier fieldName = node.fieldName;
if (fieldName != null) {
Element element = fieldName.staticElement;
recordRelation(
element, IndexRelationKind.IS_REFERENCED_BY, fieldName, true);
}
node.expression?.accept(this);
}
@override
visitConstructorName(ConstructorName node) {
ConstructorElement element = node.staticElement;
element = _getActualConstructorElement(element);
// record relation
if (node.name != null) {
int offset = node.period.offset;
int length = node.name.end - offset;
recordRelationOffset(
element, IndexRelationKind.IS_REFERENCED_BY, offset, length, true);
} else {
int offset = node.type.end;
recordRelationOffset(
element, IndexRelationKind.IS_REFERENCED_BY, offset, 0, true);
}
super.visitConstructorName(node);
}
@override
visitExportDirective(ExportDirective node) {
ExportElement element = node.element;
recordUriReference(element?.exportedLibrary, node);
super.visitExportDirective(node);
}
@override
visitExtendsClause(ExtendsClause node) {
recordSuperType(node.superclass, IndexRelationKind.IS_EXTENDED_BY);
}
@override
visitImplementsClause(ImplementsClause node) {
for (TypeName typeName in node.interfaces) {
recordSuperType(typeName, IndexRelationKind.IS_IMPLEMENTED_BY);
}
}
@override
visitImportDirective(ImportDirective node) {
ImportElement element = node.element;
recordUriReference(element?.importedLibrary, node);
super.visitImportDirective(node);
}
@override
visitIndexExpression(IndexExpression node) {
MethodElement element = node.bestElement;
if (element is MethodElement) {
Token operator = node.leftBracket;
recordRelationToken(element, IndexRelationKind.IS_INVOKED_BY, operator);
}
super.visitIndexExpression(node);
}
@override
visitMethodInvocation(MethodInvocation node) {
SimpleIdentifier name = node.methodName;
Element element = name.bestElement;
// qualified unresolved name invocation
bool isQualified = node.realTarget != null;
if (isQualified && element == null) {
recordNameRelation(name, IndexRelationKind.IS_INVOKED_BY);
}
// element invocation
IndexRelationKind kind = element is ClassElement
? IndexRelationKind.IS_REFERENCED_BY
: IndexRelationKind.IS_INVOKED_BY;
recordRelation(element, kind, name, isQualified);
node.target?.accept(this);
node.argumentList?.accept(this);
}
@override
visitPartDirective(PartDirective node) {
Element element = node.element;
recordUriReference(element, node);
super.visitPartDirective(node);
}
@override
visitPostfixExpression(PostfixExpression node) {
recordOperatorReference(node.operator, node.bestElement);
super.visitPostfixExpression(node);
}
@override
visitPrefixExpression(PrefixExpression node) {
recordOperatorReference(node.operator, node.bestElement);
super.visitPrefixExpression(node);
}
@override
visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
ConstructorElement element = node.staticElement;
if (node.constructorName != null) {
int offset = node.period.offset;
int length = node.constructorName.end - offset;
recordRelationOffset(
element, IndexRelationKind.IS_REFERENCED_BY, offset, length, true);
} else {
int offset = node.thisKeyword.end;
recordRelationOffset(
element, IndexRelationKind.IS_REFERENCED_BY, offset, 0, true);
}
super.visitRedirectingConstructorInvocation(node);
}
@override
visitSimpleIdentifier(SimpleIdentifier node) {
Element element = node.bestElement;
// name in declaration
if (node.inDeclarationContext()) {
recordDefinedElement(element);
return;
}
// record qualified unresolved name reference
bool isQualified = _isQualified(node);
if (isQualified && element == null) {
recordNameRelation(node, IndexRelationKind.IS_REFERENCED_BY);
}
// this.field parameter
if (element is FieldFormalParameterElement) {
recordRelation(
element.field, IndexRelationKind.IS_REFERENCED_BY, node, true);
return;
}
// record specific relations
recordRelation(
element, IndexRelationKind.IS_REFERENCED_BY, node, isQualified);
}
@override
visitSuperConstructorInvocation(SuperConstructorInvocation node) {
ConstructorElement element = node.staticElement;
if (node.constructorName != null) {
int offset = node.period.offset;
int length = node.constructorName.end - offset;
recordRelationOffset(
element, IndexRelationKind.IS_REFERENCED_BY, offset, length, true);
} else {
int offset = node.superKeyword.end;
recordRelationOffset(
element, IndexRelationKind.IS_REFERENCED_BY, offset, 0, true);
}
super.visitSuperConstructorInvocation(node);
}
@override
visitTypeName(TypeName node) {
AstNode parent = node.parent;
if (parent is ClassTypeAlias && parent.superclass == node) {
recordSuperType(node, IndexRelationKind.IS_EXTENDED_BY);
} else {
super.visitTypeName(node);
}
}
@override
visitWithClause(WithClause node) {
for (TypeName typeName in node.mixinTypes) {
recordSuperType(typeName, IndexRelationKind.IS_MIXED_IN_BY);
}
}
/**
* If the given [constructor] is a synthetic constructor created for a
* [ClassTypeAlias], return the actual constructor of a [ClassDeclaration]
* which is invoked. Return `null` if a redirection cycle is detected.
*/
ConstructorElement _getActualConstructorElement(
ConstructorElement constructor) {
Set<ConstructorElement> seenConstructors = new Set<ConstructorElement>();
while (constructor != null &&
constructor.isSynthetic &&
constructor.redirectedConstructor != null) {
constructor = constructor.redirectedConstructor;
// fail if a cycle is detected
if (!seenConstructors.add(constructor)) {
return null;
}
}
return constructor;
}
/**
* Return `true` if [node] has an explicit or implicit qualifier, so that it
* cannot be shadowed by a local declaration.
*/
bool _isQualified(SimpleIdentifier node) {
if (node.isQualified) {
return true;
}
AstNode parent = node.parent;
return parent is Combinator || parent is Label;
}
}
/**
* Information about a single name relation. Any [_NameRelationInfo] is always
* part of a [_UnitIndexAssembler], so [offset] should be understood within the
* context of the compilation unit pointed to by the [_UnitIndexAssembler].
*/
class _NameRelationInfo {
/**
* The identifier of the name returned [PackageIndexAssembler._getStringId].
*/
final int nameId;
final IndexRelationKind kind;
final int offset;
_NameRelationInfo(this.nameId, this.kind, this.offset);
}
/**
* Assembler of a single [CompilationUnit] index. The intended usage sequence:
*
* - Call [defineName] for each name defined in the compilation unit.
* - Call [addElementRelation] for each element relation found in the
* compilation unit.
* - Call [addNameRelation] for each name relation found in the
* compilation unit.
* - Assign ids to all the [_ElementInfo] objects reachable from
* [elementRelations].
* - Call [assemble] to produce the final unit index.
*/
class _UnitIndexAssembler {
final PackageIndexAssembler pkg;
final int unitId;
final List<_DefinedNameInfo> definedNames = <_DefinedNameInfo>[];
final List<_ElementRelationInfo> elementRelations = <_ElementRelationInfo>[];
final List<_NameRelationInfo> nameRelations = <_NameRelationInfo>[];
_UnitIndexAssembler(this.pkg, this.unitId);
void addElementRelation(Element element, IndexRelationKind kind, int offset,
int length, bool isQualified) {
try {
_ElementInfo elementInfo = pkg._getElementInfo(element);
elementRelations.add(new _ElementRelationInfo(
elementInfo, kind, offset, length, isQualified));
} on StateError {}
}
void addNameRelation(String name, IndexRelationKind kind, int offset) {
int nameId = pkg._getStringId(name);
nameRelations.add(new _NameRelationInfo(nameId, kind, offset));
}
/**
* Assemble a new [UnitIndexBuilder] using the information gathered
* by [addElementRelation] and [defineName].
*/
UnitIndexBuilder assemble() {
definedNames.sort((a, b) {
return a.nameId - b.nameId;
});
elementRelations.sort((a, b) {
return a.elementInfo.id - b.elementInfo.id;
});
nameRelations.sort((a, b) {
return a.nameId - b.nameId;
});
return new UnitIndexBuilder(
unit: unitId,
definedNames: definedNames.map((n) => n.nameId).toList(),
definedNameKinds: definedNames.map((n) => n.kind).toList(),
definedNameOffsets: definedNames.map((n) => n.offset).toList(),
usedElements: elementRelations.map((r) => r.elementInfo.id).toList(),
usedElementKinds: elementRelations.map((r) => r.kind).toList(),
usedElementOffsets: elementRelations.map((r) => r.offset).toList(),
usedElementLengths: elementRelations.map((r) => r.length).toList(),
usedElementIsQualifiedFlags:
elementRelations.map((r) => r.isQualified).toList(),
usedNames: nameRelations.map((r) => r.nameId).toList(),
usedNameKinds: nameRelations.map((r) => r.kind).toList(),
usedNameOffsets: nameRelations.map((r) => r.offset).toList());
}
void defineName(String name, IndexNameKind kind, int offset) {
int nameId = pkg._getStringId(name);
definedNames.add(new _DefinedNameInfo(nameId, kind, offset));
}
}