| // Copyright (c) 2026, 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:convert'; |
| import 'dart:io'; |
| |
| import '../lsp_server/integration_tests.dart'; |
| |
| /// A mixin that can create simple plugins that use |
| /// 'package:analysis_server_plugin' for use in integration tests. |
| mixin PluginMixin on AbstractLspAnalysisServerIntegrationTest { |
| late var pluginsFolderPath = Directory.systemTemp |
| .createTempSync('analysisServer_test_integration_plugins') |
| .resolveSymbolicLinksSync(); |
| |
| /// A simple plugin that always produces a diagnostic at 0:0 for every file |
| /// that is analyzed. |
| late final alwaysProduceDiagnosticPlugin = TestPlugin( |
| 'always_produce_diagnostic', |
| _createPluginSourceCode( |
| className: 'AlwaysProduceDiagnostic', |
| ruleBody: 'rule.reportAtOffset(0, 0);', |
| ), |
| ); |
| |
| /// A simple plugin that throws in its constructor. |
| late final throwInConstructorPlugin = TestPlugin( |
| 'throw_in_constructor', |
| _createPluginSourceCode( |
| className: 'ThrowInConstructor', |
| ruleBody: '', |
| constructorBody: 'throw UnimplementedError();', |
| ), |
| ); |
| |
| /// A simple plugin that throws an exception asynchronously. |
| late final throwAsynchronouslyPlugin = TestPlugin( |
| 'throw_async', |
| _createPluginSourceCode( |
| className: 'ThrowAsync', |
| ruleBody: 'Future.value().then(() => throw UnimplementedError());', |
| ), |
| ); |
| |
| /// A simple plugin that throws during analysis. |
| late final throwDuringAnalysisPlugin = TestPlugin( |
| 'throw_during_analysis', |
| _createPluginSourceCode( |
| className: 'ThrowDuringAnalysis', |
| ruleBody: 'throw UnimplementedError();', |
| ), |
| ); |
| |
| void createPlugins(List<TestPlugin> plugins) { |
| var analysisOptionsContent = StringBuffer()..writeln('plugins:'); |
| for (var plugin in plugins) { |
| var name = plugin.name; |
| var pluginPath = _createPlugin(plugin); |
| analysisOptionsContent |
| ..writeln(' $name:') |
| ..writeln(' path: ${jsonEncode(pluginPath)}'); |
| } |
| |
| newFile(analysisOptionsPath, analysisOptionsContent.toString()); |
| } |
| |
| @override |
| void tearDown() { |
| super.tearDown(); |
| Directory(pluginsFolderPath).deleteSync(recursive: true); |
| } |
| |
| /// Creates a plugin in the temporary directory and returns its path. |
| String _createPlugin(TestPlugin plugin) { |
| var name = plugin.name; |
| var sourceCode = plugin.sourceCode; |
| |
| var pluginPath = pathContext.join(pluginsFolderPath, name); |
| var libFolder = pathContext.join(pluginPath, 'lib'); |
| |
| newFolder(pluginPath); |
| newFolder(libFolder); |
| newFile(pathContext.join(libFolder, 'main.dart'), sourceCode); |
| newFile(pathContext.join(pluginPath, 'pubspec.yaml'), ''' |
| name: $name |
| description: A simple plugin. |
| version: 1.0.0 |
| |
| environment: |
| sdk: ^3.12.0 |
| '''); |
| |
| return pluginPath; |
| } |
| |
| /// Creates the source for a simple plugin. |
| String _createPluginSourceCode({ |
| required String className, |
| String code = 'my_diagnostic', |
| String diagnosticMessage = 'A diagnostic generated by a plugin.', |
| String ruleName = 'my_diagnostic_rule', |
| String ruleDescription = 'A diagnostic rule', |
| required String ruleBody, |
| String constructorBody = '', |
| }) { |
| return ''' |
| import 'package:analysis_server_plugin/plugin.dart'; |
| import 'package:analysis_server_plugin/registry.dart'; |
| import 'package:analyzer/analysis_rule/analysis_rule.dart'; |
| import 'package:analyzer/analysis_rule/rule_context.dart'; |
| import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/error/error.dart'; |
| |
| final plugin = ${className}Plugin(); |
| |
| class ${className}Plugin extends Plugin { |
| @override |
| String get name => 'plugin: $className'; |
| |
| ${className}Plugin() { |
| $constructorBody |
| } |
| |
| @override |
| void register(PluginRegistry registry) { |
| registry.registerWarningRule(${className}Rule()); |
| } |
| } |
| |
| class ${className}Rule extends AnalysisRule { |
| static const LintCode code = LintCode('$code', ${jsonEncode(diagnosticMessage)}); |
| |
| ${className}Rule() |
| : super( |
| name: '$ruleName', |
| description: ${jsonEncode(ruleDescription)}, |
| ); |
| |
| @override |
| LintCode get diagnosticCode => code; |
| |
| @override |
| void registerNodeProcessors( |
| RuleVisitorRegistry registry, |
| RuleContext context, |
| ) { |
| var visitor = _${className}Visitor(this); |
| registry.addCompilationUnit(this, visitor); |
| } |
| } |
| |
| class _${className}Visitor extends SimpleAstVisitor<void> { |
| final AnalysisRule rule; |
| |
| _${className}Visitor(this.rule); |
| |
| @override |
| void visitCompilationUnit(CompilationUnit node) { |
| $ruleBody |
| } |
| } |
| '''; |
| } |
| } |
| |
| class TestPlugin { |
| final String name; |
| final String sourceCode; |
| |
| new(this.name, this.sourceCode); |
| } |