blob: 5acbdc419da708b65c1a3d06fca6dd86765828cd [file] [log] [blame] [edit]
// 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/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,
ReferenceCollector referenceCollector,
) {
return _LibraryBuilder(uri, units, referenceCollector).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<DependencyNode> declaredNodes;
/// The map of [declaredNodes], used for fast search.
/// TODO(scheglov) consider using binary search instead.
final Map<DependencyName, DependencyNode> declaredNodeMap = {};
/// The list of nodes exported from this library, either using `export`
/// directives, or declared in this library.
///
/// This list is sorted.
List<DependencyNode> 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, DependencyNode> libraryScope;
Library(this.uri, this.imports, this.exports, this.declaredNodes) {
for (var node in declaredNodes) {
declaredNodeMap[node.name] = node;
}
}
@override
String toString() => '$uri';
}
/// The interface for a class that collects information about external nodes
/// referenced by a node.
///
/// The workflow for using it is that the library builder creates a new
/// instance, fills it with names of import prefixes using [addImportPrefix].
/// Then for each node defined in the library, methods `appendXyz` called
/// zero or more times to record references to external names to record API or
/// implementation dependencies. When all dependencies of a node are appended,
/// [finish] is invoked to construct the full [DependencyNodeDependencies].
/// TODO(scheglov) In following CLs we will provide single implementation.
abstract class ReferenceCollector {
final Uri libraryUri;
ReferenceCollector(this.libraryUri);
/// Record that the [name] is a name of an import prefix.
///
/// So, when we see code like `prefix.foo` we know that `foo` should be
/// resolved in the import scope that corresponds to `prefix` (unless the
/// name `prefix` is shadowed by a local declaration).
void addImportPrefix(String name);
/// Collect external nodes referenced from the given [node].
void appendExpression(Expression node);
/// Collect external nodes referenced from the given [node].
void appendFormalParameters(FormalParameterList node);
/// Collect external nodes referenced from the given [node].
void appendFunctionBody(FunctionBody node);
/// Collect external nodes referenced from the given [node].
void appendTypeAnnotation(TypeAnnotation node);
/// Construct and return a new [DependencyNodeDependencies] with the given
/// [tokenSignature] and all recorded references to external nodes. Clear
/// data structures with recorded references and be ready to start recording
/// references for a new node.
DependencyNodeDependencies finish(List<int> tokenSignature);
}
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;
/// 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<DependencyNode> declaredNodes = [];
_LibraryBuilder(this.uri, this.units, this.referenceCollector);
Library build() {
_addImports();
_addExports();
// TODO(scheglov) import prefixes are shadowed by class members
for (var unit in units) {
_addUnit(unit);
}
declaredNodes.sort(DependencyNode.compare);
return Library(uri, imports, exports, declaredNodes);
}
void _addClassOrMixin(ClassOrMixinDeclaration node) {
var hasConstConstructor = node.members.any(
(m) => m is ConstructorDeclaration && m.constKeyword != null,
);
List<DependencyNode> classTypeParameters;
if (node.typeParameters != null) {
classTypeParameters = <DependencyNode>[];
for (var typeParameter in node.typeParameters.typeParameters) {
classTypeParameters.add(DependencyNode(
DependencyName(uri, typeParameter.name.name),
DependencyNodeKind.TYPE_PARAMETER,
_computeApiDependencies(
_computeNodeTokenSignature(typeParameter),
typeParameter,
),
DependencyNodeDependencies.none,
));
}
classTypeParameters.sort(DependencyNode.compare);
}
var classMembers = <DependencyNode>[];
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(DependencyNode(
DependencyName(uri, ''),
DependencyNodeKind.CONSTRUCTOR,
DependencyNodeDependencies.none,
DependencyNodeDependencies.none,
));
}
var classTokenSignature = _computeTokenSignature(
node.beginToken,
node.leftBracket,
);
// TODO(scheglov) add library URI
var classNode = DependencyNode(
DependencyName(uri, node.name.name),
node is MixinDeclaration
? DependencyNodeKind.MIXIN
: DependencyNodeKind.CLASS,
_computeApiDependencies(classTokenSignature, node),
DependencyNodeDependencies.none,
classTypeParameters: classTypeParameters,
);
classMembers.sort(DependencyNode.compare);
classNode.setClassMembers(classMembers);
declaredNodes.add(classNode);
}
void _addClassTypeAlias(ClassTypeAlias node) {
var tokenSignature = _computeNodeTokenSignature(node);
declaredNodes.add(DependencyNode(
DependencyName(uri, node.name.name),
DependencyNodeKind.CLASS_TYPE_ALIAS,
_computeApiDependencies(tokenSignature, node),
DependencyNodeDependencies.none,
));
}
void _addConstructor(
List<DependencyNode> classMembers, ConstructorDeclaration node) {
var signature = ApiSignature();
_appendMetadataTokens(signature, node.metadata);
_appendFormalParametersTokens(signature, node.parameters);
var tokenSignature = signature.toByteList();
classMembers.add(DependencyNode(
DependencyName(uri, node.name?.name ?? ''),
DependencyNodeKind.CONSTRUCTOR,
_computeApiDependencies(tokenSignature, node),
DependencyNodeDependencies.none,
));
}
void _addEnum(EnumDeclaration node) {
var classMembers = <DependencyNode>[];
for (var constant in node.constants) {
classMembers.add(DependencyNode(
DependencyName(uri, constant.name.name),
DependencyNodeKind.GETTER,
DependencyNodeDependencies.none,
DependencyNodeDependencies.none,
));
}
classMembers.add(DependencyNode(
DependencyName(uri, 'index'),
DependencyNodeKind.GETTER,
DependencyNodeDependencies.none,
DependencyNodeDependencies.none,
));
classMembers.add(DependencyNode(
DependencyName(uri, 'values'),
DependencyNodeKind.GETTER,
DependencyNodeDependencies.none,
DependencyNodeDependencies.none,
));
classMembers.sort(DependencyNode.compare);
var enumNode = DependencyNode(
DependencyName(uri, node.name.name),
DependencyNodeKind.ENUM,
DependencyNodeDependencies.none,
DependencyNodeDependencies.none,
);
enumNode.setClassMembers(classMembers);
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 signature = ApiSignature();
_appendMetadataTokens(signature, node.metadata);
_appendNodeTokens(signature, node.returnType);
_appendNodeTokens(signature, functionExpression.typeParameters);
_appendFormalParametersTokens(signature, functionExpression.parameters);
var tokenSignature = signature.toByteList();
var rawName = node.name.name;
var name = DependencyName(uri, node.isSetter ? '$rawName=' : rawName);
DependencyNodeKind kind;
if (node.isGetter) {
kind = DependencyNodeKind.GETTER;
} else if (node.isSetter) {
kind = DependencyNodeKind.SETTER;
} else {
kind = DependencyNodeKind.FUNCTION;
}
referenceCollector.appendTypeAnnotation(node.returnType);
referenceCollector.appendFormalParameters(
node.functionExpression.parameters,
);
var api = referenceCollector.finish(tokenSignature);
var bodyNode = node.functionExpression.body;
var implTokenSignature = _computeNodeTokenSignature(bodyNode);
referenceCollector.appendFunctionBody(bodyNode);
var impl = referenceCollector.finish(implTokenSignature);
declaredNodes.add(DependencyNode(name, kind, api, impl));
}
void _addFunctionTypeAlias(FunctionTypeAlias node) {
var signature = ApiSignature();
_appendMetadataTokens(signature, node.metadata);
_appendNodeTokens(signature, node.typeParameters);
_appendNodeTokens(signature, node.returnType);
_appendFormalParametersTokens(signature, node.parameters);
var tokenSignature = signature.toByteList();
declaredNodes.add(DependencyNode(
DependencyName(uri, node.name.name),
DependencyNodeKind.FUNCTION_TYPE_ALIAS,
_computeApiDependencies(tokenSignature, node),
DependencyNodeDependencies.none,
));
}
void _addGenericTypeAlias(GenericTypeAlias node) {
var functionType = node.functionType;
var signature = ApiSignature();
_appendMetadataTokens(signature, node.metadata);
_appendNodeTokens(signature, node.typeParameters);
_appendNodeTokens(signature, functionType.returnType);
_appendNodeTokens(signature, functionType.typeParameters);
_appendFormalParametersTokens(signature, functionType.parameters);
var tokenSignature = signature.toByteList();
declaredNodes.add(DependencyNode(
DependencyName(uri, node.name.name),
DependencyNodeKind.GENERIC_TYPE_ALIAS,
_computeApiDependencies(tokenSignature, node),
DependencyNodeDependencies.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<DependencyNode> classMembers, MethodDeclaration node) {
var signature = ApiSignature();
_appendMetadataTokens(signature, node.metadata);
_appendNodeTokens(signature, node.returnType);
_appendNodeTokens(signature, node.typeParameters);
_appendFormalParametersTokens(signature, node.parameters);
var tokenSignature = signature.toByteList();
DependencyNodeKind kind;
if (node.isGetter) {
kind = DependencyNodeKind.GETTER;
} else if (node.isSetter) {
kind = DependencyNodeKind.SETTER;
} else {
kind = DependencyNodeKind.METHOD;
}
referenceCollector.appendTypeAnnotation(node.returnType);
referenceCollector.appendFormalParameters(node.parameters);
var api = referenceCollector.finish(tokenSignature);
var implTokenSignature = _computeNodeTokenSignature(node.body);
referenceCollector.appendFunctionBody(node.body);
var impl = referenceCollector.finish(implTokenSignature);
classMembers.add(
DependencyNode(DependencyName(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<DependencyNode> variableNodes,
List<Annotation> metadata,
VariableDeclarationList variables,
bool appendInitializerToApi) {
if (variables.isConst || variables.type == null) {
appendInitializerToApi = true;
}
for (var variable in variables.variables) {
var signature = ApiSignature();
signature.addInt(variables.isConst ? 1 : 0); // const flag
_appendMetadataTokens(signature, metadata);
_appendNodeTokens(signature, variables.type);
referenceCollector.appendTypeAnnotation(variables.type);
if (appendInitializerToApi) {
_appendNodeTokens(signature, variable.initializer);
referenceCollector.appendExpression(variable.initializer);
}
var tokenSignature = signature.toByteList();
var api = referenceCollector.finish(tokenSignature);
var rawName = variable.name.name;
variableNodes.add(
DependencyNode(
DependencyName(uri, rawName),
DependencyNodeKind.GETTER,
api,
DependencyNodeDependencies.none,
),
);
if (!variables.isConst && !variables.isFinal) {
variableNodes.add(
DependencyNode(
DependencyName(uri, '$rawName='),
DependencyNodeKind.SETTER,
api,
DependencyNodeDependencies.none,
),
);
}
}
}
/// 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 DependencyNodeDependencies _computeApiDependencies(
List<int> tokenSignature, AstNode node,
[AstNode node2]) {
List<String> importPrefixes = [];
List<String> unprefixedReferencedNames = [];
List<List<String>> importPrefixedReferencedNames = [];
return DependencyNodeDependencies(
tokenSignature,
unprefixedReferencedNames,
importPrefixes,
importPrefixedReferencedNames,
const [],
);
}
/// Return the signature for all tokens of the [node].
static 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).
static List<int> _computeTokenSignature(Token begin, Token end) {
var signature = ApiSignature();
_appendTokens(signature, begin, end);
return signature.toByteList();
}
/// 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;
}
}