blob: ef57ebf823a94bbde260c71e2a5d0c4623a5bd90 [file] [log] [blame]
// Copyright (c) 2025, 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:analysis_server_client/protocol.dart';
import 'package:analysis_server_client/server.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';
import 'package:analyzer/src/utilities/extensions/string.dart';
import 'package:analyzer_testing/package_root.dart' as pkg_root;
import 'package:analyzer_utilities/tools.dart';
import 'package:path/path.dart';
void main() async {
await GeneratedContent.generateAll(pkg_root.packageRoot, await allTargets);
}
Future<List<GeneratedContent>> get allTargets async {
var astLibrary = await _getAstLibrary();
return <GeneratedContent>[
GeneratedFile('analyzer/lib/dart/ast/visitor.g.dart', (_) async {
var generator = _ConcreteAstVisitorGenerator(astLibrary);
return await generator.generate();
}),
GeneratedFile('analyzer/lib/src/dart/ast/ast.g.dart', (_) async {
var generator = _AstVisitorGenerator(astLibrary);
return await generator.generate();
}),
GeneratedFile('analyzer/lib/src/lint/linter_visitor.g.dart', (_) async {
var generator = _LinterVisitorGenerator(astLibrary);
return await generator.generate();
}),
GeneratedFile('analyzer/lib/analysis_rule/rule_visitor_registry.g.dart', (
_,
) async {
var generator = _RuleVisitorGenerator(astLibrary);
return await generator.generate();
}),
];
}
String get _analyzerPath => normalize(join(pkg_root.packageRoot, 'analyzer'));
Future<String> _formatSortCode(String path, String code) async {
var server = Server();
await server.start();
server.listenToOutput();
await server.send('analysis.setAnalysisRoots', {
'included': [path],
'excluded': [],
});
Future<void> updateContent() async {
await server.send('analysis.updateContent', {
'files': {
path: {'type': 'add', 'content': code},
},
});
}
await updateContent();
var formatResponse = await server.send('edit.format', {
'file': path,
'selectionOffset': 0,
'selectionLength': code.length,
});
var formatResult = EditFormatResult.fromJson(
ResponseDecoder(null),
'result',
formatResponse,
);
code = SourceEdit.applySequence(code, formatResult.edits);
await updateContent();
var sortResponse = await server.send('edit.sortMembers', {'file': path});
var sortResult = EditSortMembersResult.fromJson(
ResponseDecoder(null),
'result',
sortResponse,
);
code = SourceEdit.applySequence(code, sortResult.edit.edits);
await server.kill();
return code;
}
Future<LibraryElement> _getAstLibrary() async {
var collection = AnalysisContextCollection(includedPaths: [_analyzerPath]);
var analysisContext = collection.contextFor(_analyzerPath);
var analysisSession = analysisContext.currentSession;
var libraryResult = await analysisSession.getLibraryByUri(
'package:analyzer/src/dart/ast/ast.dart',
);
libraryResult as LibraryElementResult;
return libraryResult.element;
}
class _AstVisitorGenerator {
final LibraryElement astLibrary;
final StringBuffer out = StringBuffer('''
// Copyright (c) 2025, 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.
// THIS FILE IS GENERATED. DO NOT EDIT.
//
// Run 'dart pkg/analyzer/tool/ast/generate.dart' to update.
part of 'ast.dart';
''');
_AstVisitorGenerator(this.astLibrary);
Future<String> generate() async {
_writeAstVisitor();
var resultPath = normalize(
join(_analyzerPath, 'lib', 'src', 'dart', 'ast', 'ast.g.dart'),
);
return _formatSortCode(resultPath, out.toString());
}
void _writeAstVisitor() {
out.write('''
/// An object that can be used to visit an AST structure.
///
/// Clients may not extend, implement or mix-in this class. There are classes
/// that implement this interface that provide useful default behaviors in
/// `package:analyzer/dart/ast/visitor.dart`. A couple of the most useful
/// include
/// - SimpleAstVisitor which implements every visit method by doing nothing,
/// - RecursiveAstVisitor which causes every node in a structure to be visited,
/// and
/// - ThrowingAstVisitor which implements every visit method by throwing an
/// exception.
@AnalyzerPublicApi(message: 'exported by lib/dart/ast/ast.dart')
abstract class AstVisitor<R> {
''');
for (var node in astLibrary.nodes) {
if (node.isConcrete) {
var name = node.name;
out.writeln('''
R? visit$name($name node);
''');
}
}
out.writeln('}');
}
}
class _ConcreteAstVisitorGenerator {
final LibraryElement astLibrary;
final StringBuffer out = StringBuffer('''
// Copyright (c) 2025, 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.
// THIS FILE IS GENERATED. DO NOT EDIT.
//
// Run 'dart pkg/analyzer/tool/ast/generate.dart' to update.
part of 'visitor.dart';
''');
_ConcreteAstVisitorGenerator(this.astLibrary);
Future<String> generate() async {
_writeGeneralizing();
_writeRecursive();
_writeSimple();
_writeTimed();
_writeThrowing();
_writeUnifying();
var resultPath = normalize(
join(_analyzerPath, 'lib', 'dart', 'ast', 'visitor.g.dart'),
);
return _formatSortCode(resultPath, out.toString());
}
void _writeGeneralizing() {
out.write('''
/// An AST visitor that will recursively visit all of the nodes in an AST
/// structure (like instances of the class [RecursiveAstVisitor]). In addition,
/// when a node of a specific type is visited not only will the visit method for
/// that specific type of node be invoked, but additional methods for the
/// superclasses of that node will also be invoked. For example, using an
/// instance of this class to visit a [Block] will cause the method [visitBlock]
/// to be invoked but will also cause the methods [visitStatement] and
/// [visitNode] to be subsequently invoked. This allows visitors to be written
/// that visit all statements without needing to override the visit method for
/// each of the specific subclasses of [Statement].
///
/// Subclasses that override a visit method must either invoke the overridden
/// visit method or explicitly invoke the more general visit method. Failure to
/// do so will cause the visit methods for superclasses of the node to not be
/// invoked and will cause the children of the visited node to not be visited.
///
/// Clients may extend this class.
class GeneralizingAstVisitor<R> implements AstVisitor<R> {
/// Initialize a newly created visitor.
const GeneralizingAstVisitor();
R? visitNode(AstNode node) {
node.visitChildren(this);
return null;
}
''');
for (var node in astLibrary.nodes) {
var name = node.name;
var superNode = node.superNode;
if (superNode == null) {
continue;
}
if (node.isConcrete) {
out.writeln('@override');
}
// TODO(fshcheglov): Remove special case after AST hierarchy is fixed.
// https://github.com/dart-lang/sdk/issues/61224
if (node.name == 'FunctionDeclaration') {
out.writeln(r'''
R? visitFunctionDeclaration(FunctionDeclaration node) {
if (node.parent is FunctionDeclarationStatement) {
return visitNode(node);
}
return visitNamedCompilationUnitMember(node);
}''');
continue;
}
if (superNode.element.isAstNodeImplExactly) {
out.writeln('''
R? visit$name($name node) => visitNode(node);
''');
} else {
out.writeln('''
R? visit$name($name node) => visit${superNode.name}(node);
''');
}
}
out.writeln('}');
}
void _writeRecursive() {
out.writeln(r'''
/// An AST visitor that will recursively visit all of the nodes in an AST
/// structure. For example, using an instance of this class to visit a [Block]
/// will also cause all of the statements in the block to be visited.
///
/// Subclasses that override a visit method must either invoke the overridden
/// visit method or must explicitly ask the visited node to visit its children.
/// Failure to do so will cause the children of the visited node to not be
/// visited.
///
/// Clients may extend this class.
class RecursiveAstVisitor<R> implements AstVisitor<R> {
/// Initialize a newly created visitor.
const RecursiveAstVisitor();
''');
for (var node in astLibrary.nodes) {
if (node.isConcrete) {
var name = node.name;
out.writeln('''
@override
R? visit$name($name node) {
node.visitChildren(this);
return null;
}
''');
}
}
out.writeln('}');
}
void _writeSimple() {
out.write('''
/// An AST visitor that will do nothing when visiting an AST node. It is
/// intended to be a superclass for classes that use the visitor pattern
/// primarily as a dispatch mechanism (and hence don't need to recursively visit
/// a whole structure) and that only need to visit a small number of node types.
///
/// Clients may extend this class.
class SimpleAstVisitor<R> implements AstVisitor<R> {
/// Initialize a newly created visitor.
const SimpleAstVisitor();
''');
for (var node in astLibrary.nodes) {
if (node.isConcrete) {
var name = node.name;
out.writeln('''
@override
R? visit$name($name node) => null;
''');
}
}
out.writeln('}');
}
void _writeThrowing() {
out.writeln(r'''
/// An AST visitor that will throw an exception if any of the visit methods that
/// are invoked have not been overridden. It is intended to be a superclass for
/// classes that implement the visitor pattern and need to (a) override all of
/// the visit methods or (b) need to override a subset of the visit method and
/// want to catch when any other visit methods have been invoked.
///
/// Clients may extend this class.
class ThrowingAstVisitor<R> implements AstVisitor<R> {
/// Initialize a newly created visitor.
const ThrowingAstVisitor();
Never _throw(AstNode node) {
var typeName = node.runtimeType.toString();
if (typeName.endsWith('Impl')) {
typeName = typeName.substring(0, typeName.length - 4);
}
throw Exception('Missing implementation of visit$typeName');
}
''');
for (var node in astLibrary.nodes) {
if (node.isConcrete) {
var name = node.name;
out.writeln('''
@override
R? visit$name($name node) => _throw(node);
''');
}
}
out.writeln('}');
}
void _writeTimed() {
out.writeln(r'''
/// An AST visitor that captures visit call timings.
///
/// Clients may not extend, implement or mix-in this class.
class TimedAstVisitor<T> implements AstVisitor<T> {
/// The base visitor whose visit methods will be timed.
final AstVisitor<T> _baseVisitor;
/// Collects elapsed time for visit calls.
final Stopwatch stopwatch;
/// Initialize a newly created visitor to time calls to the given base
/// visitor's visits.
TimedAstVisitor(this._baseVisitor, [Stopwatch? watch])
: stopwatch = watch ?? Stopwatch();
''');
for (var node in astLibrary.nodes) {
if (node.isConcrete) {
var name = node.name;
out.writeln('''
@override
T? visit$name($name node) {
stopwatch.start();
T? result = _baseVisitor.visit$name(node);
stopwatch.stop();
return result;
}
''');
}
}
out.writeln('}');
}
void _writeUnifying() {
out.writeln(r'''
/// An AST visitor that will recursively visit all of the nodes in an AST
/// structure (like instances of the class [RecursiveAstVisitor]). In addition,
/// every node will also be visited by using a single unified [visitNode]
/// method.
///
/// Subclasses that override a visit method must either invoke the overridden
/// visit method or explicitly invoke the more general [visitNode] method.
/// Failure to do so will cause the children of the visited node to not be
/// visited.
///
/// Clients may extend this class.
class UnifyingAstVisitor<R> implements AstVisitor<R> {
/// Initialize a newly created visitor.
const UnifyingAstVisitor();
R? visitNode(AstNode node) {
node.visitChildren(this);
return null;
}
''');
for (var node in astLibrary.nodes) {
if (node.isConcrete) {
var name = node.name;
out.writeln('''
@override
R? visit$name($name node) => visitNode(node);
''');
}
}
out.writeln('}');
}
}
class _LinterVisitorGenerator {
final LibraryElement astLibrary;
final StringBuffer out = StringBuffer('''
// Copyright (c) 2025, 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.
// THIS FILE IS GENERATED. DO NOT EDIT.
//
// Run 'dart pkg/analyzer/tool/ast/generate.dart' to update.
part of 'linter_visitor.dart';
''');
_LinterVisitorGenerator(this.astLibrary);
Future<String> generate() async {
_writeLinterVisitor();
_writeRuleVisitorRegistryImpl();
var resultPath = normalize(
join(_analyzerPath, 'lib', 'src', 'lint', 'linter_visitor.g.dart'),
);
return _formatSortCode(resultPath, out.toString());
}
void _writeLinterVisitor() {
out.write('''
/// The AST visitor that runs handlers for nodes from the [_registry].
class AnalysisRuleVisitor implements AstVisitor<void> {
final RuleVisitorRegistryImpl _registry;
/// Whether exceptions should be propagated (by rethrowing them).
final bool _shouldPropagateExceptions;
AnalysisRuleVisitor(this._registry, {bool shouldPropagateExceptions = false})
: _shouldPropagateExceptions = shouldPropagateExceptions;
void afterLibrary() {
_runAfterLibrarySubscriptions(_registry._afterLibrary);
}
void _runAfterLibrarySubscriptions(
List<_AfterLibrarySubscription> subscriptions,
) {
for (var subscription in subscriptions) {
var timer = subscription.timer;
timer?.start();
subscription.callback();
timer?.stop();
}
}
void _runSubscriptions<T extends AstNode>(
T node,
List<_Subscription<T>> subscriptions,
) {
for (var subscription in subscriptions) {
var timer = subscription.timer;
timer?.start();
try {
node.accept(subscription.visitor);
} catch (exception, stackTrace) {
_logException(node, subscription.rule, exception, stackTrace);
if (_shouldPropagateExceptions) {
rethrow;
}
}
timer?.stop();
}
}
/// Handles exceptions that occur during the execution of an [AnalysisRule].
void _logException(
AstNode node,
AbstractAnalysisRule visitor,
Object exception,
StackTrace stackTrace,
) {
var buffer = StringBuffer();
buffer.write('Exception while using a \${visitor.runtimeType} to visit a ');
AstNode? currentNode = node;
var first = true;
while (currentNode != null) {
if (first) {
first = false;
} else {
buffer.write(' in ');
}
buffer.write(currentNode.runtimeType);
currentNode = currentNode.parent;
}
// TODO(39284): should this exception be silent?
AnalysisEngine.instance.instrumentationService.logException(
SilentException(buffer.toString(), exception, stackTrace),
);
}
''');
for (var node in astLibrary.nodes) {
if (node.isConcrete) {
var name = node.name;
out.writeln('''
@override
void visit$name($name node) {
_runSubscriptions(node, _registry._for$name);
node.visitChildren(this);
}
''');
}
}
out.writeln('}');
}
void _writeRuleVisitorRegistryImpl() {
out.write('''
class RuleVisitorRegistryImpl implements RuleVisitorRegistry {
final bool _enableTiming;
final List<_AfterLibrarySubscription> _afterLibrary = [];
RuleVisitorRegistryImpl({required bool enableTiming})
: _enableTiming = enableTiming;
/// Get the timer associated with the given [rule].
Stopwatch? _getTimer(AbstractAnalysisRule rule) {
if (_enableTiming) {
return analysisRuleTimers.getTimer(rule);
} else {
return null;
}
}
@override
void afterLibrary(AbstractAnalysisRule rule, void Function() callback) {
_afterLibrary.add(
_AfterLibrarySubscription(rule, callback, _getTimer(rule)),
);
}
''');
for (var node in astLibrary.nodes) {
if (node.isConcrete) {
var name = node.name;
out.writeln('''
final List<_Subscription<$name>> _for$name = [];
@override
void add$name(AbstractAnalysisRule rule, AstVisitor visitor) {
_for$name.add(_Subscription(rule, visitor, _getTimer(rule)));
}
''');
}
}
out.writeln('}');
}
}
class _Node {
final ClassElement element;
final String name;
_Node(this.element, this.name);
bool get isConcrete => !element.isAbstract;
_Node? get superNode {
return element.supertype?.element.ifTypeOrNull<ClassElement>()?.asNode;
}
}
class _RuleVisitorGenerator {
final LibraryElement astLibrary;
final StringBuffer out = StringBuffer('''
// Copyright (c) 2025, 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.
// THIS FILE IS GENERATED. DO NOT EDIT.
//
// Run 'dart pkg/analyzer/tool/ast/generate.dart' to update.
part of 'rule_visitor_registry.dart';
''');
_RuleVisitorGenerator(this.astLibrary);
Future<String> generate() async {
_writeRuleVisitor();
var resultPath = normalize(
join(
_analyzerPath,
'lib',
'analysis_rule',
'rule_visitor_registry.g.dart',
),
);
return _formatSortCode(resultPath, out.toString());
}
void _writeRuleVisitor() {
out.write('''
/// The container to register visitors for separate AST node types.
///
/// Source files are visited by the Dart analysis server recursively. Only the
/// visitors that are registered to visit a given node type will visit such
/// nodes. Each analysis rule overrides
/// [AbstractAnalysisRule.registerNodeProcessors] and calls `add*` for each of
/// the node types it needs to visit with an [AstVisitor], which registers that
/// visitor.
abstract class RuleVisitorRegistry {
void afterLibrary(AbstractAnalysisRule rule, void Function() callback);
''');
for (var node in astLibrary.nodes) {
if (node.isConcrete) {
var name = node.name;
out.writeln('''
void add$name(AbstractAnalysisRule rule, AstVisitor visitor);
''');
}
}
out.writeln('}');
}
}
extension on InterfaceElement {
/// Whether the class is [AstNodeImpl].
bool get isAstNodeImplExactly => name == 'AstNodeImpl';
}
extension on ClassElement {
_Node? get asNode {
var interfaceName = name?.removeSuffix('Impl');
if ((isAstNodeImplSubtype || isAstNodeImplExactly) &&
interfaceName != null) {
return _Node(this, interfaceName);
}
return null;
}
/// Whether the class is a subtype of [AstNodeImpl].
bool get isAstNodeImplSubtype {
// Not a real AST node.
if (name == 'ConstantContextForExpressionImpl') {
return false;
}
return allSupertypes.any((type) => type.element.isAstNodeImplExactly);
}
}
extension on LibraryElement {
List<_Node> get nodes {
return classes.map((element) => element.asNode).nonNulls.toList();
}
}