// 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_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> get allTargets async { var astLibrary = await _getAstLibrary(); return [ 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 _formatSortCode(String path, String code) async { var server = Server(); await server.start(); server.listenToOutput(); await server.send('analysis.setAnalysisRoots', { 'included': [path], 'excluded': [], }); Future 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 _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 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 { '''); for (var node in astLibrary.nodes) { if (node.isConcrete) { var name = node.apiElementName; if (node.isExperimental) { out.writeln(' @experimental'); } if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } 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 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 implements AstVisitor { /// 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.apiElementName; var superNode = node.superNode; if (superNode == null) { continue; } if (node.isExperimental) { out.writeln('@experimental'); } if (node.isConcrete) { out.writeln('@override'); } if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } // TODO(fshcheglov): Remove special case after AST hierarchy is fixed. // https://github.com/dart-lang/sdk/issues/61224 if (node.apiElementName == 'FunctionDeclaration') { out.writeln(r''' R? visitFunctionDeclaration(FunctionDeclaration node) { if (node.parent is FunctionDeclarationStatement) { return visitNode(node); } return visitNamedCompilationUnitMember(node); }'''); continue; } if (superNode.implElement.isAstNodeImplExactly) { out.writeln(''' R? visit$name($name node) => visitNode(node); '''); } else { out.writeln(''' R? visit$name($name node) => visit${superNode.apiElementName}(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 implements AstVisitor { /// Initialize a newly created visitor. const RecursiveAstVisitor(); '''); for (var node in astLibrary.nodes) { if (node.isConcrete) { var name = node.apiElementName; if (node.isExperimental) { out.writeln(' @experimental'); } out.writeln(' @override'); if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } out.writeln(''' 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 implements AstVisitor { /// Initialize a newly created visitor. const SimpleAstVisitor(); '''); for (var node in astLibrary.nodes) { if (node.isConcrete) { var name = node.apiElementName; if (node.isExperimental) { out.writeln(' @experimental'); } out.writeln(' @override'); if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } out.writeln(''' 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 implements AstVisitor { /// 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.apiElementName; if (node.isExperimental) { out.writeln(' @experimental'); } out.writeln(' @override'); if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } out.writeln(''' 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 implements AstVisitor { /// The base visitor whose visit methods will be timed. final AstVisitor _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.apiElementName; if (node.isExperimental) { out.writeln(' @experimental'); } out.writeln(' @override'); if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } out.writeln(''' 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 implements AstVisitor { /// 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.apiElementName; if (node.isExperimental) { out.writeln(' @experimental'); } out.writeln(' @override'); if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } out.writeln(''' 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 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 { 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 node, List<_Subscription> 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.apiElementName; if (node.isExperimental) { out.writeln(' @experimental'); } out.writeln(' @override'); if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } out.writeln(''' 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.apiElementName; if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } out.writeln(' final List<_Subscription<$name>> _for$name = [];'); out.writeln(); out.writeln(' @override'); if (node.isDeprecated) { out.writeln(' // ignore: deprecated_member_use_from_same_package'); } out.writeln(''' void add$name(AbstractAnalysisRule rule, AstVisitor visitor) { _for$name.add(_Subscription(rule, visitor, _getTimer(rule))); } '''); } } out.writeln('}'); } } class _Node { final ClassElement implElement; final ClassElement apiElement; final String apiElementName; _Node({ required this.implElement, required this.apiElement, required this.apiElementName, }); bool get isConcrete => !implElement.isAbstract; bool get isDeprecated { return apiElement.metadata.hasDeprecated; } bool get isExperimental { return apiElement.metadata.hasExperimental; } _Node? get superNode { var superElement = implElement.supertype?.element; return superElement.ifTypeOrNull()?.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 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.apiElementName; if (node.isExperimental) { out.writeln('@experimental'); } 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 { if (isAstNodeImplSubtype || isAstNodeImplExactly) { var apiElement = interfaces.lastOrNull?.element; if (apiElement is ClassElement) { var apiElementName = apiElement.name!; if ('${apiElementName}Impl' == name) { return _Node( implElement: this, apiElement: apiElement, apiElementName: apiElementName, ); } } } 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(); } }