blob: 308b816bb5d7821be5ac73aea54ab8e79b1bbda6 [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 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;
_LibraryBuilder(this.uri, this.units);
Library build() {
uriSignature = (ApiSignature()..addString(uri.toString())).toByteList();
_addImports();
_addExports();
// TODO(scheglov) import prefixes are shadowed by class members
for (var unit in units) {
_addUnit(unit);
}
declaredNodes.sort(Node.compare);
return Library(uri, imports, exports, declaredNodes);
}
void _addClassOrMixin(ClassOrMixinDeclaration node) {
enclosingClassNameSignature =
(ApiSignature()..addString(node.name.name)).toByteList();
var hasConstConstructor = node.members.any(
(m) => m is ConstructorDeclaration && m.constKeyword != null,
);
List<Node> classTypeParameters;
if (node.typeParameters != null) {
classTypeParameters = <Node>[];
for (var typeParameter in node.typeParameters.typeParameters) {
classTypeParameters.add(Node(
LibraryQualifiedName(uri, typeParameter.name.name),
NodeKind.TYPE_PARAMETER,
_computeApiDependencies(
_computeNodeTokenSignature(typeParameter),
typeParameter,
),
Dependencies.none,
));
}
classTypeParameters.sort(Node.compare);
}
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 (!hasConstructor && node is ClassDeclaration) {
classMembers.add(Node(
LibraryQualifiedName(uri, ''),
NodeKind.CONSTRUCTOR,
Dependencies.none,
Dependencies.none,
));
}
var apiTokenSignature = _computeTokenSignature(
node.beginToken,
node.leftBracket,
);
var classNode = Node(
LibraryQualifiedName(uri, node.name.name),
node is MixinDeclaration ? NodeKind.MIXIN : NodeKind.CLASS,
_computeApiDependencies(apiTokenSignature, node),
Dependencies.none,
classTypeParameters: classTypeParameters,
);
classMembers.sort(Node.compare);
classNode.setClassMembers(classMembers);
declaredNodes.add(classNode);
enclosingClassNameSignature = null;
}
void _addClassTypeAlias(ClassTypeAlias node) {
var apiTokenSignature = _computeNodeTokenSignature(node);
declaredNodes.add(Node(
LibraryQualifiedName(uri, node.name.name),
NodeKind.CLASS_TYPE_ALIAS,
_computeApiDependencies(apiTokenSignature, node),
Dependencies.none,
));
}
void _addConstructor(List<Node> classMembers, ConstructorDeclaration node) {
var builder = _newApiSignatureBuilder();
_appendMetadataTokens(builder, node.metadata);
_appendFormalParametersTokens(builder, node.parameters);
var apiTokenSignature = builder.toByteList();
classMembers.add(Node(
LibraryQualifiedName(uri, node.name?.name ?? ''),
NodeKind.CONSTRUCTOR,
_computeApiDependencies(apiTokenSignature, node),
Dependencies.none,
));
}
void _addEnum(EnumDeclaration node) {
var enumTokenSignature = _newApiSignatureBuilder().toByteList();
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,
));
}
members.add(Node(
LibraryQualifiedName(uri, 'index'),
NodeKind.GETTER,
fieldDependencies,
Dependencies.none,
));
members.add(Node(
LibraryQualifiedName(uri, 'values'),
NodeKind.GETTER,
fieldDependencies,
Dependencies.none,
));
members.sort(Node.compare);
var enumNode = Node(
LibraryQualifiedName(uri, node.name.name),
NodeKind.ENUM,
Dependencies(enumTokenSignature, [], [], [], [], []),
Dependencies.none,
);
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;
}
// TODO(scheglov) type parameters (their bounds)
var api = referenceCollector.collect(
apiTokenSignature,
formalParameters: node.functionExpression.parameters,
returnType: node.returnType,
);
var body = node.functionExpression.body;
var implTokenSignature = _computeNodeTokenSignature(body);
var impl = referenceCollector.collect(
implTokenSignature,
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();
declaredNodes.add(Node(
LibraryQualifiedName(uri, node.name.name),
NodeKind.FUNCTION_TYPE_ALIAS,
_computeApiDependencies(apiTokenSignature, node),
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();
declaredNodes.add(Node(
LibraryQualifiedName(uri, node.name.name),
NodeKind.GENERIC_TYPE_ALIAS,
_computeApiDependencies(apiTokenSignature, node),
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) type parameters (their bounds)
var api = referenceCollector.collect(
apiTokenSignature,
formalParameters: node.parameters,
returnType: node.returnType,
);
var implTokenSignature = _computeNodeTokenSignature(node.body);
var impl = referenceCollector.collect(
implTokenSignature,
functionBody: node.body,
);
classMembers
.add(Node(LibraryQualifiedName(uri, node.name.name), kind, api, impl));
}
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 builder = _newApiSignatureBuilder();
builder.addInt(variables.isConst ? 1 : 0); // const flag
_appendMetadataTokens(builder, metadata);
_appendNodeTokens(builder, variables.type);
if (appendInitializerToApi) {
_appendNodeTokens(builder, variable.initializer);
}
var apiTokenSignature = builder.toByteList();
var api = referenceCollector.collect(
apiTokenSignature,
type: variables.type,
expression: appendInitializerToApi ? variable.initializer : null,
);
// TODO(scheglov) variable "impl"
var rawName = variable.name.name;
variableNodes.add(
Node(
LibraryQualifiedName(uri, rawName),
NodeKind.GETTER,
api,
Dependencies.none,
),
);
if (!variables.isConst && !variables.isFinal) {
variableNodes.add(
Node(
LibraryQualifiedName(uri, '$rawName='),
NodeKind.SETTER,
api,
Dependencies.none,
),
);
}
}
}
/// 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;
}
}
/// TODO(scheglov) Replace all uses with [referenceCollector].
static Dependencies _computeApiDependencies(
List<int> tokenSignature, AstNode node,
[AstNode node2]) {
List<String> importPrefixes = [];
List<String> unprefixedReferencedNames = [];
List<List<String>> importPrefixedReferencedNames = [];
return Dependencies(
tokenSignature,
unprefixedReferencedNames,
importPrefixes,
importPrefixedReferencedNames,
const [],
const [],
);
}
/// 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;
}
}