blob: 20859c2f4e53f9b2979f926f603b8ca584ac8bad [file] [log] [blame]
// Copyright (c) 2018, 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/src/dart/analysis/dependency/node.dart';
import 'package:analyzer/src/dart/analysis/dependency/reference_collector.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/summary/api_signature.dart';
/// Build [Library] that describes nodes and dependencies of the library
/// with the given [uri] and [units].
///
/// If the [units] are just parsed, then only token signatures and referenced
/// names of nodes can be computed. If the [units] are fully resolved, then
/// also class member references can be recorded.
Library buildLibrary(Uri uri, List<CompilationUnit> units) {
return _LibraryBuilder(uri, units).build();
}
/// The `show` or `hide` namespace combinator.
class Combinator {
final bool isShow;
final List<String> names;
Combinator(this.isShow, this.names);
@override
String toString() {
if (isShow) {
return 'show ' + names.join(', ');
} else {
return 'hide ' + names.join(', ');
}
}
}
/// The `export` directive.
class Export {
/// The absolute URI of the exported library.
final Uri uri;
/// The list of namespace combinators to apply, not `null`.
final List<Combinator> combinators;
Export(this.uri, this.combinators);
@override
String toString() {
return 'Export(uri: $uri, combinators: $combinators)';
}
}
/// The `import` directive.
class Import {
/// The absolute URI of the imported library.
final Uri uri;
/// The import prefix, or `null` if not specified.
final String prefix;
/// The list of namespace combinators to apply, not `null`.
final List<Combinator> combinators;
Import(this.uri, this.prefix, this.combinators);
@override
String toString() {
return 'Import(uri: $uri, prefix: $prefix, combinators: $combinators)';
}
}
/// The collection of imports, exports, and top-level nodes.
class Library {
/// The absolute URI of the library.
final Uri uri;
/// The list of imports in this library.
final List<Import> imports;
/// The list of exports in this library.
final List<Export> exports;
/// The list of libraries that correspond to the [imports].
List<Library> importedLibraries;
/// The list of top-level nodes defined in the library.
///
/// This list is sorted.
final List<Node> declaredNodes;
/// The map of [declaredNodes], used for fast search.
/// TODO(scheglov) consider using binary search instead.
final Map<LibraryQualifiedName, Node> declaredNodeMap = {};
/// The list of nodes exported from this library, either using `export`
/// directives, or declared in this library.
///
/// This list is sorted.
List<Node> exportedNodes;
/// The map of nodes that are visible in the library, either imported,
/// or declared in this library.
///
/// TODO(scheglov) support for imports with prefixes
Map<String, Node> libraryScope;
Library(this.uri, this.imports, this.exports, this.declaredNodes) {
for (var node in declaredNodes) {
declaredNodeMap[node.name] = node;
}
}
@override
String toString() => '$uri';
}
class _LibraryBuilder {
/// The URI of the library.
final Uri uri;
/// The units of the library, parsed or fully resolved.
final List<CompilationUnit> units;
/// The instance of the referenced names, class members collector.
final ReferenceCollector referenceCollector = ReferenceCollector();
/// The list of imports in the library.
final List<Import> imports = [];
/// The list of exports in the library.
final List<Export> exports = [];
/// The top-level nodes declared in the library.
final List<Node> declaredNodes = [];
/// The precomputed signature of the [uri].
///
/// It is mixed into every API token signature, because for example even
/// though types of two functions might be the same, their locations
/// are different.
List<int> uriSignature;
/// The name of the enclosing class name, or `null` if outside a class.
String enclosingClassName;
/// The super class of the enclosing class, or `null` if outside a class.
TypeName enclosingSuperClass;
/// The precomputed signature of the enclosing class name, or `null` if
/// outside a class.
///
/// It is mixed into every API token signature of every class member, because
/// for example even though types of two methods might be the same, their
/// locations are different.
List<int> enclosingClassNameSignature;
/// The node of the enclosing class.
Node enclosingClass;
_LibraryBuilder(this.uri, this.units);
Library build() {
uriSignature = (ApiSignature()..addString(uri.toString())).toByteList();
_addImports();
_addExports();
for (var unit in units) {
_addUnit(unit);
}
declaredNodes.sort(Node.compare);
return Library(uri, imports, exports, declaredNodes);
}
void _addClassOrMixin(ClassOrMixinDeclaration node) {
enclosingClassName = node.name.name;
if (node is ClassDeclaration) {
enclosingSuperClass = node.extendsClause?.superclass;
}
enclosingClassNameSignature =
(ApiSignature()..addString(enclosingClassName)).toByteList();
var apiTokenSignature = _computeTokenSignature(
node.beginToken,
node.leftBracket,
);
Dependencies api;
if (node is ClassDeclaration) {
api = referenceCollector.collect(
apiTokenSignature,
thisNodeName: enclosingClassName,
typeParameters: node.typeParameters,
extendsClause: node.extendsClause,
withClause: node.withClause,
implementsClause: node.implementsClause,
);
} else if (node is MixinDeclaration) {
api = referenceCollector.collect(
apiTokenSignature,
thisNodeName: enclosingClassName,
typeParameters: node.typeParameters,
onClause: node.onClause,
implementsClause: node.implementsClause,
);
} else {
throw UnimplementedError('(${node.runtimeType}) $node');
}
enclosingClass = Node(
LibraryQualifiedName(uri, enclosingClassName),
node is MixinDeclaration ? NodeKind.MIXIN : NodeKind.CLASS,
api,
Dependencies.none,
);
var hasConstConstructor = node.members.any(
(m) => m is ConstructorDeclaration && m.constKeyword != null,
);
// TODO(scheglov) do we need type parameters at all?
List<Node> classTypeParameters;
if (node.typeParameters != null) {
classTypeParameters = <Node>[];
for (var typeParameter in node.typeParameters.typeParameters) {
var api = referenceCollector.collect(
_computeNodeTokenSignature(typeParameter),
enclosingClassName: enclosingClassName,
thisNodeName: typeParameter.name.name,
type: typeParameter.bound,
);
classTypeParameters.add(Node(
LibraryQualifiedName(uri, typeParameter.name.name),
NodeKind.TYPE_PARAMETER,
api,
Dependencies.none,
enclosingClass: enclosingClass,
));
}
classTypeParameters.sort(Node.compare);
enclosingClass.setTypeParameters(classTypeParameters);
}
var classMembers = <Node>[];
var hasConstructor = false;
for (var member in node.members) {
if (member is ConstructorDeclaration) {
hasConstructor = true;
_addConstructor(classMembers, member);
} else if (member is FieldDeclaration) {
_addVariables(
classMembers,
member.metadata,
member.fields,
hasConstConstructor,
);
} else if (member is MethodDeclaration) {
_addMethod(classMembers, member);
} else {
throw UnimplementedError('(${member.runtimeType}) $member');
}
}
if (node is ClassDeclaration && !hasConstructor) {
classMembers.add(Node(
LibraryQualifiedName(uri, ''),
NodeKind.CONSTRUCTOR,
Dependencies.none,
Dependencies.none,
enclosingClass: enclosingClass,
));
}
classMembers.sort(Node.compare);
enclosingClass.setClassMembers(classMembers);
declaredNodes.add(enclosingClass);
enclosingClassName = null;
enclosingClassNameSignature = null;
enclosingSuperClass = null;
enclosingClass = null;
}
void _addClassTypeAlias(ClassTypeAlias node) {
var apiTokenSignature = _computeNodeTokenSignature(node);
var api = referenceCollector.collect(
apiTokenSignature,
typeParameters: node.typeParameters,
superClass: node.superclass,
withClause: node.withClause,
implementsClause: node.implementsClause,
);
declaredNodes.add(Node(
LibraryQualifiedName(uri, node.name.name),
NodeKind.CLASS_TYPE_ALIAS,
api,
Dependencies.none,
));
}
void _addConstructor(List<Node> classMembers, ConstructorDeclaration node) {
var builder = _newApiSignatureBuilder();
_appendMetadataTokens(builder, node.metadata);
_appendFormalParametersTokens(builder, node.parameters);
var apiTokenSignature = builder.toByteList();
var api = referenceCollector.collect(
apiTokenSignature,
enclosingClassName: enclosingClassName,
formalParameters: node.parameters,
);
var implTokenSignature = _computeNodeTokenSignature(node.body);
var impl = referenceCollector.collect(
implTokenSignature,
enclosingClassName: enclosingClassName,
enclosingSuperClass: enclosingSuperClass,
formalParametersForImpl: node.parameters,
constructorInitializers: node.initializers,
redirectedConstructor: node.redirectedConstructor,
functionBody: node.body,
);
classMembers.add(Node(
LibraryQualifiedName(uri, node.name?.name ?? ''),
NodeKind.CONSTRUCTOR,
api,
impl,
enclosingClass: enclosingClass,
));
}
void _addEnum(EnumDeclaration node) {
var enumTokenSignature = _newApiSignatureBuilder().toByteList();
var enumNode = Node(
LibraryQualifiedName(uri, node.name.name),
NodeKind.ENUM,
Dependencies(enumTokenSignature, [], [], [], [], []),
Dependencies.none,
);
Dependencies fieldDependencies;
{
var builder = _newApiSignatureBuilder();
builder.addString(node.name.name);
_appendTokens(builder, node.leftBracket, node.rightBracket);
var tokenSignature = builder.toByteList();
fieldDependencies = Dependencies(tokenSignature, [], [], [], [], []);
}
var members = <Node>[];
for (var constant in node.constants) {
members.add(Node(
LibraryQualifiedName(uri, constant.name.name),
NodeKind.GETTER,
fieldDependencies,
Dependencies.none,
enclosingClass: enumNode,
));
}
members.add(Node(
LibraryQualifiedName(uri, 'index'),
NodeKind.GETTER,
fieldDependencies,
Dependencies.none,
enclosingClass: enumNode,
));
members.add(Node(
LibraryQualifiedName(uri, 'values'),
NodeKind.GETTER,
fieldDependencies,
Dependencies.none,
enclosingClass: enumNode,
));
members.sort(Node.compare);
enumNode.setClassMembers(members);
declaredNodes.add(enumNode);
}
/// Fill [exports] with information about exports.
void _addExports() {
for (var directive in units.first.directives) {
if (directive is ExportDirective) {
var refUri = directive.uri.stringValue;
var importUri = uri.resolve(refUri);
var combinators = _getCombinators(directive);
exports.add(Export(importUri, combinators));
}
}
}
void _addFunction(FunctionDeclaration node) {
var functionExpression = node.functionExpression;
var builder = _newApiSignatureBuilder();
_appendMetadataTokens(builder, node.metadata);
_appendNodeTokens(builder, node.returnType);
_appendNodeTokens(builder, functionExpression.typeParameters);
_appendFormalParametersTokens(builder, functionExpression.parameters);
var apiTokenSignature = builder.toByteList();
var rawName = node.name.name;
var name = LibraryQualifiedName(uri, node.isSetter ? '$rawName=' : rawName);
NodeKind kind;
if (node.isGetter) {
kind = NodeKind.GETTER;
} else if (node.isSetter) {
kind = NodeKind.SETTER;
} else {
kind = NodeKind.FUNCTION;
}
var api = referenceCollector.collect(
apiTokenSignature,
thisNodeName: node.name.name,
typeParameters: functionExpression.typeParameters,
formalParameters: functionExpression.parameters,
returnType: node.returnType,
);
var body = functionExpression.body;
var implTokenSignature = _computeNodeTokenSignature(body);
var impl = referenceCollector.collect(
implTokenSignature,
thisNodeName: node.name.name,
formalParametersForImpl: functionExpression.parameters,
functionBody: body,
);
declaredNodes.add(Node(name, kind, api, impl));
}
void _addFunctionTypeAlias(FunctionTypeAlias node) {
var builder = _newApiSignatureBuilder();
_appendMetadataTokens(builder, node.metadata);
_appendNodeTokens(builder, node.typeParameters);
_appendNodeTokens(builder, node.returnType);
_appendFormalParametersTokens(builder, node.parameters);
var apiTokenSignature = builder.toByteList();
var api = referenceCollector.collect(
apiTokenSignature,
thisNodeName: node.name.name,
typeParameters: node.typeParameters,
formalParameters: node.parameters,
returnType: node.returnType,
);
declaredNodes.add(Node(
LibraryQualifiedName(uri, node.name.name),
NodeKind.FUNCTION_TYPE_ALIAS,
api,
Dependencies.none,
));
}
void _addGenericTypeAlias(GenericTypeAlias node) {
var functionType = node.functionType;
var builder = _newApiSignatureBuilder();
_appendMetadataTokens(builder, node.metadata);
_appendNodeTokens(builder, node.typeParameters);
_appendNodeTokens(builder, functionType.returnType);
_appendNodeTokens(builder, functionType.typeParameters);
_appendFormalParametersTokens(builder, functionType.parameters);
var apiTokenSignature = builder.toByteList();
var api = referenceCollector.collect(
apiTokenSignature,
typeParameters: node.typeParameters,
typeParameters2: functionType.typeParameters,
formalParameters: functionType.parameters,
returnType: functionType.returnType,
);
declaredNodes.add(Node(
LibraryQualifiedName(uri, node.name.name),
NodeKind.GENERIC_TYPE_ALIAS,
api,
Dependencies.none,
));
}
/// Fill [imports] with information about imports.
void _addImports() {
var hasDartCoreImport = false;
for (var directive in units.first.directives) {
if (directive is ImportDirective) {
var refUri = directive.uri.stringValue;
var importUri = uri.resolve(refUri);
if (importUri.toString() == 'dart:core') {
hasDartCoreImport = true;
}
var combinators = _getCombinators(directive);
imports.add(Import(importUri, directive.prefix?.name, combinators));
if (directive.prefix != null) {
referenceCollector.addImportPrefix(directive.prefix.name);
}
}
}
if (!hasDartCoreImport) {
imports.add(Import(Uri.parse('dart:core'), null, []));
}
}
void _addMethod(List<Node> classMembers, MethodDeclaration node) {
var builder = _newApiSignatureBuilder();
_appendMetadataTokens(builder, node.metadata);
_appendNodeTokens(builder, node.returnType);
_appendNodeTokens(builder, node.typeParameters);
_appendFormalParametersTokens(builder, node.parameters);
var apiTokenSignature = builder.toByteList();
NodeKind kind;
if (node.isGetter) {
kind = NodeKind.GETTER;
} else if (node.isSetter) {
kind = NodeKind.SETTER;
} else {
kind = NodeKind.METHOD;
}
// TODO(scheglov) metadata, here and everywhere
var api = referenceCollector.collect(
apiTokenSignature,
enclosingClassName: enclosingClassName,
thisNodeName: node.name.name,
typeParameters: node.typeParameters,
formalParameters: node.parameters,
returnType: node.returnType,
);
var implTokenSignature = _computeNodeTokenSignature(node.body);
var impl = referenceCollector.collect(
implTokenSignature,
enclosingClassName: enclosingClassName,
thisNodeName: node.name.name,
formalParametersForImpl: node.parameters,
functionBody: node.body,
);
var name = LibraryQualifiedName(uri, node.name.name);
classMembers.add(
Node(name, kind, api, impl, enclosingClass: enclosingClass),
);
}
void _addUnit(CompilationUnit unit) {
for (var declaration in unit.declarations) {
if (declaration is ClassOrMixinDeclaration) {
_addClassOrMixin(declaration);
} else if (declaration is ClassTypeAlias) {
_addClassTypeAlias(declaration);
} else if (declaration is EnumDeclaration) {
_addEnum(declaration);
} else if (declaration is FunctionDeclaration) {
_addFunction(declaration);
} else if (declaration is FunctionTypeAlias) {
_addFunctionTypeAlias(declaration);
} else if (declaration is GenericTypeAlias) {
_addGenericTypeAlias(declaration);
} else if (declaration is TopLevelVariableDeclaration) {
_addVariables(
declaredNodes,
declaration.metadata,
declaration.variables,
false,
);
} else {
throw UnimplementedError('(${declaration.runtimeType}) $declaration');
}
}
}
void _addVariables(List<Node> variableNodes, List<Annotation> metadata,
VariableDeclarationList variables, bool appendInitializerToApi) {
if (variables.isConst || variables.type == null) {
appendInitializerToApi = true;
}
for (var variable in variables.variables) {
var initializer = variable.initializer;
var builder = _newApiSignatureBuilder();
builder.addInt(variables.isConst ? 1 : 0); // const flag
_appendMetadataTokens(builder, metadata);
_appendNodeTokens(builder, variables.type);
if (appendInitializerToApi) {
_appendNodeTokens(builder, initializer);
}
var apiTokenSignature = builder.toByteList();
var api = referenceCollector.collect(
apiTokenSignature,
enclosingClassName: enclosingClassName,
thisNodeName: variable.name.name,
type: variables.type,
expression: appendInitializerToApi ? initializer : null,
);
var implTokenSignature = _computeNodeTokenSignature(initializer);
var impl = referenceCollector.collect(
implTokenSignature,
enclosingClassName: enclosingClassName,
thisNodeName: variable.name.name,
expression: initializer,
);
var rawName = variable.name.name;
variableNodes.add(Node(
LibraryQualifiedName(uri, rawName),
NodeKind.GETTER,
api,
impl,
enclosingClass: enclosingClass,
));
if (!variables.isConst && !variables.isFinal) {
// Note that one set of dependencies is enough for body.
// So, the setter has empty "impl" dependencies.
variableNodes.add(
Node(
LibraryQualifiedName(uri, '$rawName='),
NodeKind.SETTER,
api,
Dependencies.none,
enclosingClass: enclosingClass,
),
);
}
}
}
/// Return the signature for all tokens of the [node].
List<int> _computeNodeTokenSignature(AstNode node) {
if (node == null) {
return const <int>[];
}
return _computeTokenSignature(node.beginToken, node.endToken);
}
/// Return the signature for tokens from [begin] to [end] (both including).
List<int> _computeTokenSignature(Token begin, Token end) {
var signature = _newApiSignatureBuilder();
_appendTokens(signature, begin, end);
return signature.toByteList();
}
/// Return a new signature builder, primed with the current context salts.
ApiSignature _newApiSignatureBuilder() {
var builder = ApiSignature();
builder.addBytes(uriSignature);
if (enclosingClassNameSignature != null) {
builder.addBytes(enclosingClassNameSignature);
}
return builder;
}
/// Append tokens of the given [parameters] to the [signature].
static void _appendFormalParametersTokens(
ApiSignature signature, FormalParameterList parameters) {
if (parameters == null) return;
for (var parameter in parameters.parameters) {
if (parameter.isRequired) {
signature.addInt(1);
} else if (parameter.isOptionalPositional) {
signature.addInt(2);
} else {
signature.addInt(3);
}
// If a simple not named parameter, we don't need its name.
// We should be careful to include also annotations.
if (parameter is SimpleFormalParameter && parameter.type != null) {
_appendTokens(
signature,
parameter.beginToken,
parameter.type.endToken,
);
continue;
}
// We don't know anything better than adding the whole parameter.
_appendNodeTokens(signature, parameter);
}
}
static void _appendMetadataTokens(
ApiSignature signature, List<Annotation> metadata) {
if (metadata != null) {
for (var annotation in metadata) {
_appendNodeTokens(signature, annotation);
}
}
}
/// Append tokens of the given [node] to the [signature].
static void _appendNodeTokens(ApiSignature signature, AstNode node) {
if (node != null) {
_appendTokens(signature, node.beginToken, node.endToken);
}
}
/// Append tokens from [begin] to [end] (both including) to the [signature].
static void _appendTokens(ApiSignature signature, Token begin, Token end) {
if (begin is CommentToken) {
begin = (begin as CommentToken).parent;
}
Token token = begin;
while (token != null) {
signature.addString(token.lexeme);
if (token == end) {
break;
}
var nextToken = token.next;
if (nextToken == token) {
break;
}
token = nextToken;
}
}
/// Return [Combinator]s for the given import or export [directive].
static List<Combinator> _getCombinators(NamespaceDirective directive) {
var combinators = <Combinator>[];
for (var combinator in directive.combinators) {
if (combinator is ShowCombinator) {
combinators.add(
Combinator(
true,
combinator.shownNames.map((id) => id.name).toList(),
),
);
}
if (combinator is HideCombinator) {
combinators.add(
Combinator(
false,
combinator.hiddenNames.map((id) => id.name).toList(),
),
);
}
}
return combinators;
}
}