blob: 20b101bc050f3a7f9cba81363888be44edfb7f2c [file] [log] [blame]
// Copyright (c) 2019, 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 'dart:typed_data';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/src/dart/ast/invokes_super_self.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/summary/api_signature.dart';
import 'package:collection/collection.dart';
/// Return the bytes of the unlinked API signature of the given [unit].
///
/// If API signatures of two units are different, they may have different APIs.
Uint8List computeUnlinkedApiSignature(CompilationUnit unit) {
var computer = _UnitApiSignatureComputer();
computer.compute(unit);
return computer.signature.toByteList();
}
class _UnitApiSignatureComputer {
static const int _kindConstructorDeclaration = 1;
static const int _kindFieldDeclaration = 2;
static const int _kindMethodDeclaration = 3;
static const int _nullNode = 0;
static const int _notNullNode = 1;
static const int _nullToken = 0;
static const int _notNullToken = 1;
final ApiSignature signature = ApiSignature();
void compute(CompilationUnit unit) {
signature.addFeatureSet(unit.featureSet);
signature.addInt(unit.directives.length);
unit.directives.forEach(_addNode);
signature.addInt(unit.declarations.length);
for (var declaration in unit.declarations) {
if (declaration is ClassOrMixinDeclaration) {
_addClassOrMixin(declaration);
} else if (declaration is EnumDeclaration) {
_addEnum(declaration);
} else if (declaration is ExtensionDeclaration) {
_addExtension(declaration);
} else if (declaration is FunctionDeclaration) {
var functionExpression = declaration.functionExpression;
_addTokens(
declaration.beginToken,
(functionExpression.parameters ?? declaration.name).endToken,
);
_addFunctionBodyModifiers(functionExpression.body);
} else if (declaration is TopLevelVariableDeclaration) {
_topLevelVariableDeclaration(declaration);
} else {
_addNode(declaration);
}
}
}
void _addClassMembers(List<ClassMember> members, bool hasConstConstructor) {
signature.addInt(members.length);
for (var member in members) {
if (member is ConstructorDeclaration) {
_addConstructorDeclaration(member);
} else if (member is FieldDeclaration) {
_addFieldDeclaration(member, hasConstConstructor);
} else if (member is MethodDeclaration) {
_addMethodDeclaration(member);
} else {
throw UnimplementedError('(${member.runtimeType}) $member');
}
}
}
void _addClassOrMixin(ClassOrMixinDeclaration node) {
_addTokens(node.beginToken, node.leftBracket);
bool hasConstConstructor = node.members
.any((m) => m is ConstructorDeclaration && m.constKeyword != null);
_addClassMembers(node.members, hasConstConstructor);
}
void _addConstructorDeclaration(ConstructorDeclaration node) {
signature.addInt(_kindConstructorDeclaration);
_addTokens(node.beginToken, node.parameters.endToken);
_addNodeList(node.initializers);
_addNode(node.redirectedConstructor);
}
void _addEnum(EnumDeclaration node) {
var members = node.members;
// If not enhanced, include the whole node.
var firstMember = members.firstOrNull;
if (firstMember == null) {
_addNode(node);
return;
}
_addTokens(node.beginToken, firstMember.beginToken);
_addClassMembers(members, true);
}
void _addExtension(ExtensionDeclaration node) {
_addTokens(node.beginToken, node.leftBracket);
_addClassMembers(node.members, false);
}
void _addFieldDeclaration(FieldDeclaration node, bool hasConstConstructor) {
signature.addInt(_kindFieldDeclaration);
_addToken(node.abstractKeyword);
_addToken(node.covariantKeyword);
_addToken(node.externalKeyword);
_addToken(node.staticKeyword);
_addNodeList(node.metadata);
var variableList = node.fields;
var includeInitializers = variableList.type == null ||
variableList.isConst ||
hasConstConstructor && !node.isStatic && variableList.isFinal;
_variableList(variableList, includeInitializers);
}
void _addFunctionBodyModifiers(FunctionBody? node) {
if (node != null) {
signature.addBool(node.isSynchronous);
signature.addBool(node.isGenerator);
signature.addBool(node is NativeFunctionBody);
}
}
void _addMethodDeclaration(MethodDeclaration node) {
signature.addInt(_kindMethodDeclaration);
_addTokens(
node.beginToken,
(node.parameters ?? node.name).endToken,
);
signature.addBool(node.body is EmptyFunctionBody);
_addFunctionBodyModifiers(node.body);
signature.addBool(node.invokesSuperSelf);
}
void _addNode(AstNode? node) {
if (node != null) {
signature.addInt(_notNullNode);
_addTokens(node.beginToken, node.endToken);
} else {
signature.addInt(_nullNode);
}
}
void _addNodeList(List<AstNode> nodes) {
for (var node in nodes) {
_addNode(node);
}
}
void _addToken(Token? token) {
if (token != null) {
signature.addInt(_notNullToken);
signature.addString(token.lexeme);
} else {
signature.addInt(_nullToken);
}
}
/// Appends tokens from [begin] (including), to [end] (also including).
void _addTokens(Token begin, Token end) {
if (begin is CommentToken) {
begin = begin.parent!;
}
Token? token = begin;
while (token != null) {
_addToken(token);
if (token == end) {
break;
}
var nextToken = token.next;
// Stop if EOF.
if (nextToken == token) {
break;
}
token = nextToken;
}
}
void _topLevelVariableDeclaration(TopLevelVariableDeclaration node) {
_addToken(node.externalKeyword);
_addNodeList(node.metadata);
var variableList = node.variables;
var includeInitializers = variableList.type == null || variableList.isConst;
_variableList(variableList, includeInitializers);
}
void _variableList(VariableDeclarationList node, bool includeInitializers) {
_addToken(node.keyword);
_addToken(node.lateKeyword);
_addNode(node.type);
var variables = node.variables;
signature.addInt(variables.length);
for (var variable in variables) {
_addNode(variable.name);
signature.addBool(variable.initializer != null);
if (includeInitializers) {
_addNode(variable.initializer);
}
}
}
}