blob: 989bdb48484592e42b0469b8d4ced7c57debc0ae [file] [log] [blame]
// Copyright (c) 2021, 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/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart' as ast;
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/src/dart/ast/ast.dart' as ast;
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/macro/api/code.dart';
import 'package:analyzer/src/macro/api/macro.dart';
import 'package:analyzer/src/summary2/informative_data.dart';
import 'package:analyzer/src/summary2/library_builder.dart';
class ClassDeclarationBuilderImpl extends DeclarationBuilderImpl
implements ClassDeclarationBuilder {
final LinkingUnit linkingUnit;
/// The index of [node] among other [ast.ClassDeclarationImpl]s.
final int nodeIndex;
final ast.ClassDeclarationImpl node;
ClassDeclarationBuilderImpl(
this.linkingUnit,
this.nodeIndex,
this.node,
);
@override
void addToClass(Declaration declaration) {
var declarationCode = declaration.code.trim();
// TODO(scheglov) feature set
// TODO(scheglov) throw if errors?
var parseResult = parseString(
content: 'class ${node.name.name} { $declarationCode }',
);
var parsedDeclarations = parseResult.unit.declarations;
var parsedClass = parsedDeclarations.single as ast.ClassDeclaration;
var parsedMember = parsedClass.members.single;
_rebaseOffsets(parsedMember);
node.members.add(parsedMember);
var macroGeneratedContent = linkingUnit.macroGeneratedContent;
var collected = _Declaration(
data: MacroGenerationData(
id: macroGeneratedContent.nextId++,
code: declarationCode,
informative: writeDeclarationInformative(parsedMember),
classDeclarationIndex: nodeIndex,
),
node: parsedMember,
);
macroGeneratedContent._declarations.add(collected);
}
/// We parsed [node] in the context of some synthetic code string, its
/// current offsets only have meaning relative to the begin offset of the
/// [node]. So, we update offsets accordingly.
static void _rebaseOffsets(ast.AstNode node) {
var baseOffset = node.offset;
for (Token? t = node.beginToken;
t != null && t != node.endToken;
t = t.next) {
t.offset -= baseOffset;
}
}
}
class DeclarationBuilderImpl implements DeclarationBuilder {
@override
void addToLibrary(Declaration declaration) {
// TODO: implement addToLibrary
}
@override
Code typeAnnotationCode(ast.TypeAnnotation node) {
return Fragment(node.toSource());
}
}
class MacroGeneratedContent {
final LinkingUnit linkingUnit;
final List<_Declaration> _declarations = [];
int nextId = 0;
MacroGeneratedContent(this.linkingUnit);
/// Finish building of elements: combine the source code of the unit with
/// macro-generated pieces of code; update offsets of macro-generated
/// elements to use offsets inside this combined code.
void finish() {
// Don't set empty values, keep it null.
if (_declarations.isEmpty) {
return;
}
// Sort declarations by:
// 1. The top-level declaration location.
// 2. The ordering in the top-level declaration.
// We need (2) because `sort` is not stable.
_declarations.sort((a, b) {
const indexForUnit = 1 << 24;
var aIndex = a.data.classDeclarationIndex ?? indexForUnit;
var bIndex = b.data.classDeclarationIndex ?? indexForUnit;
if (aIndex != bIndex) {
return aIndex - bIndex;
}
return a.data.id - b.data.id;
});
const classMemberCodePrefix = '\n';
const classMemberCodeSuffix = '\n';
// TODO(scheglov) make it required?
var generatedContent = linkingUnit.input.sourceContent!;
var shift = 0;
var classDeclarations = linkingUnit.input.unit.declarations
.whereType<ast.ClassDeclaration>()
.toList();
for (var declaration in _declarations) {
var classIndex = declaration.data.classDeclarationIndex;
if (classIndex != null) {
var targetClass = classDeclarations[classIndex];
var code = classMemberCodePrefix +
declaration.data.code +
classMemberCodeSuffix;
var insertOffset = shift + targetClass.rightBracket.offset;
declaration.data.offset = insertOffset + classMemberCodePrefix.length;
generatedContent = generatedContent.substring(0, insertOffset) +
code +
generatedContent.substring(insertOffset);
shift += code.length;
} else {
throw UnimplementedError();
}
var node = declaration.node;
if (node is ast.Declaration) {
var element = node.declaredElement as ElementImpl;
element.accept(
_ShiftOffsetsElementVisitor(declaration.data.offset),
);
if (element is HasMacroGenerationData) {
(element as HasMacroGenerationData).macro = declaration.data;
}
}
}
linkingUnit.element.macroGeneratedContent = generatedContent;
linkingUnit.element.macroGenerationDataList =
_declarations.map((e) => e.data).toList();
}
}
/// [MacroGenerationData] plus its linking [node] (to get the element).
class _Declaration {
final MacroGenerationData data;
final ast.AstNode node;
_Declaration({
required this.data,
required this.node,
});
}
/// TODO(scheglov) Enhance to support more nodes.
/// For now only nodes that are currently used in tests are supported.
/// Which is probably enough for experiments, but should be improved if this
/// is something we are going to do for real.
class _ShiftOffsetsAstVisitor extends RecursiveAstVisitor<void> {
final int shift;
_ShiftOffsetsAstVisitor(this.shift);
@override
void visitAnnotation(ast.Annotation node) {
_token(node.atSign);
super.visitAnnotation(node);
}
@override
void visitSimpleIdentifier(ast.SimpleIdentifier node) {
_token(node.token);
}
void _token(Token token) {
token.offset += shift;
}
}
/// Macro-generated elements are created from pieces of code that are rebased
/// to start at zero-th offset. When we later know that actual offset in the
/// combined (source + generated) code of the unit, we shift the offsets.
class _ShiftOffsetsElementVisitor extends GeneralizingElementVisitor<void> {
final int shift;
_ShiftOffsetsElementVisitor(this.shift);
@override
void visitElement(covariant ElementImpl element) {
element.nameOffset += shift;
_metadata(element.metadata);
super.visitElement(element);
}
void _metadata(List<ElementAnnotation> metadata) {
for (var annotation in metadata) {
annotation as ElementAnnotationImpl;
annotation.annotationAst.accept(
_ShiftOffsetsAstVisitor(shift),
);
}
}
}