-
Notifications
You must be signed in to change notification settings - Fork 215
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce an analyzer plugin for the test package.
- Loading branch information
Showing
6 changed files
with
193 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# test_analyzer_plugin | ||
|
||
This package is an analyzer plugin that provides additional static analysis for | ||
usage of the test package. | ||
|
||
This analyzer plugin provides the following additional analysis: | ||
|
||
* Report a warning when a `test` or a `group` is declared inside a `test` | ||
declaration. This can _sometimes_ be detected at runtime. This warning is | ||
reported statically. | ||
|
||
* Offer a quick fix in the IDE for the above warning, which moves the violating | ||
`test` or `group` declaration below the containing `test` declaration. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// 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_plugin/plugin.dart'; | ||
import 'package:analysis_server_plugin/registry.dart'; | ||
|
||
import 'src/fixes.dart'; | ||
import 'src/rules.dart'; | ||
|
||
final plugin = TestPackagePlugin(); | ||
|
||
class TestPackagePlugin extends Plugin { | ||
@override | ||
void register(PluginRegistry registry) { | ||
registry.registerWarningRule(TestInTestRule()); | ||
registry.registerFixForRule( | ||
TestInTestRule.code, MoveBelowEnclosingTestCall.new); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// 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_plugin/edit/dart/correction_producer.dart'; | ||
import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart'; | ||
import 'package:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; | ||
import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; | ||
import 'package:analyzer_plugin/utilities/range_factory.dart'; | ||
|
||
import 'utilities.dart'; | ||
|
||
class MoveBelowEnclosingTestCall extends ResolvedCorrectionProducer { | ||
static const _wrapInQuotesKind = FixKind( | ||
'dart.fix.moveBelowEnclosingTestCall', | ||
DartFixKindPriority.standard, | ||
"Move below the enclosing 'test' call"); | ||
|
||
MoveBelowEnclosingTestCall({required super.context}); | ||
|
||
@override | ||
CorrectionApplicability get applicability => | ||
// This fix may break code by moving references to variables away from the | ||
// scope in which they are declared. | ||
CorrectionApplicability.singleLocation; | ||
|
||
@override | ||
FixKind get fixKind => _wrapInQuotesKind; | ||
|
||
@override | ||
Future<void> compute(ChangeBuilder builder) async { | ||
var methodCall = node; | ||
if (methodCall is! MethodInvocation) return; | ||
AstNode? enclosingTestCall = findEnclosingTestCall(methodCall); | ||
if (enclosingTestCall == null) return; | ||
|
||
if (enclosingTestCall.parent is ExpressionStatement) { | ||
// Move the 'test' call to below the outer 'test' call _statement_. | ||
enclosingTestCall = enclosingTestCall.parent!; | ||
} | ||
|
||
if (methodCall.parent is ExpressionStatement) { | ||
// Move the whole statement (don't leave the semicolon dangling). | ||
methodCall = methodCall.parent!; | ||
} | ||
|
||
await builder.addDartFileEdit(file, (builder) { | ||
var indent = utils.getLinePrefix(enclosingTestCall!.offset); | ||
var source = utils.getRangeText(range.node(methodCall)); | ||
|
||
// Move the source for `methodCall` wholsale to be just after `enclosingTestCall`. | ||
builder.addDeletion(range.deletionRange(methodCall)); | ||
builder.addSimpleInsertion( | ||
enclosingTestCall.end, '$eol$eol$indent$source'); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// 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:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer/dart/ast/visitor.dart'; | ||
import 'package:analyzer/src/dart/error/lint_codes.dart'; | ||
import 'package:analyzer/src/lint/linter.dart'; | ||
|
||
import 'utilities.dart'; | ||
|
||
class TestInTestRule extends AnalysisRule { | ||
static const LintCode code = LintCode( | ||
'test_in_test', | ||
"Do not declare a 'test' or a 'group' inside a 'test'", | ||
correctionMessage: "Try moving 'test' or 'group' outside of 'test'", | ||
); | ||
|
||
TestInTestRule() | ||
: super( | ||
name: 'test_in_test', | ||
description: | ||
'Tests and groups declared inside of a test are not properly ' | ||
'registered in the test framework.', | ||
); | ||
|
||
@override | ||
LintCode get lintCode => code; | ||
|
||
@override | ||
void registerNodeProcessors( | ||
NodeLintRegistry registry, LinterContext context) { | ||
var visitor = _Visitor(this); | ||
registry.addMethodInvocation(this, visitor); | ||
} | ||
} | ||
|
||
class _Visitor extends SimpleAstVisitor<void> { | ||
final AnalysisRule rule; | ||
|
||
_Visitor(this.rule); | ||
|
||
@override | ||
void visitMethodInvocation(MethodInvocation node) { | ||
if (!node.methodName.isTest && !node.methodName.isGroup) { | ||
return; | ||
} | ||
var enclosingTestCall = findEnclosingTestCall(node); | ||
if (enclosingTestCall != null) { | ||
rule.reportLint(node); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// 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:analyzer/dart/ast/ast.dart'; | ||
|
||
/// Finds an enclosing call to the 'test' function, if there is one. | ||
MethodInvocation? findEnclosingTestCall(MethodInvocation node) { | ||
var ancestor = node.parent?.thisOrAncestorOfType<MethodInvocation>(); | ||
while (ancestor != null) { | ||
if (ancestor.methodName.isTest) { | ||
return ancestor; | ||
} | ||
ancestor = ancestor.parent?.thisOrAncestorOfType<MethodInvocation>(); | ||
} | ||
return null; | ||
} | ||
|
||
extension SimpleIdentifierExtension on SimpleIdentifier { | ||
/// Whether this identifier represents the 'test' function from the | ||
/// 'test_core' package. | ||
bool get isTest { | ||
final element = this.element; | ||
if (element == null) return false; | ||
if (element.name3 != 'test') return false; | ||
return element.library2?.uri.path.startsWith('test_core/') ?? false; | ||
} | ||
|
||
/// Whether this identifier represents the 'group' function from the | ||
/// 'test_core' package. | ||
bool get isGroup { | ||
final element = this.element; | ||
if (element == null) return false; | ||
if (element.name3 != 'group') return false; | ||
return element.library2?.uri.path.startsWith('test_core/') ?? false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
name: test_analyzer_plugin | ||
description: An analyzer plugin to report improper usage of the test package. | ||
version: 1.0.0 | ||
publish_to: none | ||
|
||
environment: | ||
sdk: '>=3.6.0 <4.0.0' | ||
|
||
dependencies: | ||
analysis_server_plugin: any | ||
analyzer: ^7.2.0 | ||
|