blob: e40888e82ea3c2460081d2ae622ddaeaf646ee4d [file]
// 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);
}