CQ. Index and search '// [diag.fooBar]' inside analyzer tests.
Teach the analysis index and referenced-name computation to recognize `// [diag.foo]` expectation comments embedded in string literals in analyzer tests. Resolve `foo` through the analyzer diagnostic library and record it as a qualified reference to the diagnostic variable. This lets reference search find diagnostics that are used only in test expectation strings, when developing analyzer itself. Change-Id: I91fa020ccd2cfe49fa6e890c8f6796a5b8ca4d1f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/503663 Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
a667cde7c1
commit
61a266f8dc
@@ -109,7 +109,7 @@ testFineAfterLibraryAnalyzerHook;
|
||||
// TODO(scheglov): Clean up the list of implicitly analyzed files.
|
||||
class AnalysisDriver {
|
||||
/// The version of data format, should be incremented on every format change.
|
||||
static const int DATA_VERSION = 632;
|
||||
static const int DATA_VERSION = 633;
|
||||
|
||||
/// The number of exception contexts allowed to write. Once this field is
|
||||
/// zero, we stop writing any new exception contexts in this process.
|
||||
|
||||
@@ -324,7 +324,7 @@ class _IndexAssembler {
|
||||
|
||||
/// Index the [unit] and assemble a new [AnalysisDriverUnitIndexBuilder].
|
||||
AnalysisDriverUnitIndexBuilder assemble(CompilationUnit unit) {
|
||||
unit.accept(_IndexContributor(this));
|
||||
unit.accept(_IndexContributor(this, unit));
|
||||
|
||||
// Sort strings and set IDs.
|
||||
List<_StringInfo> stringInfoList = stringMap.values.toList(growable: false);
|
||||
@@ -506,9 +506,20 @@ class _IndexAssembler {
|
||||
|
||||
/// Visits a resolved AST and adds relationships into the [assembler].
|
||||
class _IndexContributor extends GeneralizingAstVisitor {
|
||||
final _IndexAssembler assembler;
|
||||
static final _expectationPattern = RegExp(
|
||||
r'//[ \t]*\[diag\.([a-zA-Z0-9_]+)\]',
|
||||
);
|
||||
|
||||
_IndexContributor(this.assembler);
|
||||
final _IndexAssembler assembler;
|
||||
final CompilationUnit unit;
|
||||
|
||||
/// Caches the diagnostic library if the unit being indexed is an analyzer
|
||||
/// test file. This enables synthetic indexing of expectation comments
|
||||
/// embedded in string literals (e.g. `// [diag.foo]`).
|
||||
late final LibraryElementImpl? _analyzerDiagnosticLibrary =
|
||||
_findAnalyzerDiagnosticLibrary();
|
||||
|
||||
_IndexContributor(this.assembler, this.unit);
|
||||
|
||||
/// Record that the name [node] has a relation of the given [kind].
|
||||
void recordNameRelation(
|
||||
@@ -1172,6 +1183,32 @@ class _IndexContributor extends GeneralizingAstVisitor {
|
||||
recordRelation(element, kind, node, isQualified);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSimpleStringLiteral(SimpleStringLiteral node) {
|
||||
// Index analyzer diagnostic expectations inside string literals.
|
||||
if (_analyzerDiagnosticLibrary case var diagnosticLibrary?) {
|
||||
var lexeme = node.literal.lexeme;
|
||||
var matches = _expectationPattern.allMatches(lexeme);
|
||||
var tokenOffset = node.literal.offset;
|
||||
for (var match in matches) {
|
||||
var name = match.group(1)!;
|
||||
var start = (match.end - 1) - name.length;
|
||||
var element = diagnosticLibrary.exportNamespace.get2(name);
|
||||
if (element is GetterElement) {
|
||||
recordRelationOffset(
|
||||
element.variable,
|
||||
IndexRelationKind.IS_REFERENCED_BY,
|
||||
tokenOffset + start,
|
||||
name.length,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visitSimpleStringLiteral(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
|
||||
var element = node.element;
|
||||
@@ -1317,6 +1354,35 @@ class _IndexContributor extends GeneralizingAstVisitor {
|
||||
);
|
||||
}
|
||||
|
||||
LibraryElementImpl? _findAnalyzerDiagnosticLibrary() {
|
||||
var unitLibrary = unit.declaredFragment!.element;
|
||||
|
||||
var uriStr = unitLibrary.uri.toString();
|
||||
var isAnalyzerTest =
|
||||
uriStr.startsWith('package:test/') ||
|
||||
uriStr.contains('/pkg/analyzer/test/');
|
||||
if (!isAnalyzerTest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (unitLibrary is LibraryElementImpl) {
|
||||
var elementFactory = unitLibrary.session.elementFactory;
|
||||
var diagnosticLibrary = elementFactory.libraryOfUri(
|
||||
Uri.parse('package:analyzer/src/diagnostic/diagnostic.dart'),
|
||||
);
|
||||
if (diagnosticLibrary != null) {
|
||||
return diagnosticLibrary;
|
||||
}
|
||||
diagnosticLibrary = elementFactory.libraryOfUri(
|
||||
Uri.parse('package:test/diagnostic.dart'),
|
||||
);
|
||||
if (diagnosticLibrary != null) {
|
||||
return diagnosticLibrary;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// If the given [constructor] is a synthetic constructor created for a
|
||||
/// [ClassTypeAlias], return the actual constructor of a [ClassDeclaration]
|
||||
/// which is invoked. Return `null` if a redirection cycle is detected.
|
||||
|
||||
@@ -212,6 +212,10 @@ class _LocalNameScope {
|
||||
}
|
||||
|
||||
class _ReferencedNamesComputer extends GeneralizingAstVisitor<void> {
|
||||
static final RegExp _analyzerExpectedDiagnosticPattern = RegExp(
|
||||
r'//[ \t]*\[diag\.([a-zA-Z0-9_]+)\]',
|
||||
);
|
||||
|
||||
final Set<String> names = <String>{};
|
||||
final Set<String> importPrefixNames = <String>{};
|
||||
|
||||
@@ -375,6 +379,16 @@ class _ReferencedNamesComputer extends GeneralizingAstVisitor<void> {
|
||||
names.add(name);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSimpleStringLiteral(SimpleStringLiteral node) {
|
||||
var lexeme = node.literal.lexeme;
|
||||
var matches = _analyzerExpectedDiagnosticPattern.allMatches(lexeme);
|
||||
for (var match in matches) {
|
||||
names.add(match.group(1)!);
|
||||
}
|
||||
super.visitSimpleStringLiteral(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSuperFormalParameter(SuperFormalParameter node) {
|
||||
names.add(node.name.lexeme);
|
||||
|
||||
@@ -62,6 +62,33 @@ class IndexTest extends PubPackageResolutionTest with _IndexMixin {
|
||||
expect(actual, expected);
|
||||
}
|
||||
|
||||
test_analyzer_diagnosticCode() async {
|
||||
var diagnosticFile = newFile('$testPackageLibPath/diagnostic.dart', r'''
|
||||
const myDiagnosticCode = 0;
|
||||
''');
|
||||
|
||||
var diagnosticLibrary = await libraryElementForFile(diagnosticFile);
|
||||
var element = diagnosticLibrary.topLevelVariables.firstWhere(
|
||||
(v) => v.name == 'myDiagnosticCode',
|
||||
);
|
||||
|
||||
newFile('$testPackageLibPath/helper.dart', r'''
|
||||
import 'diagnostic.dart';
|
||||
''');
|
||||
|
||||
await _indexTestUnit(r'''
|
||||
import 'helper.dart';
|
||||
|
||||
void f() {
|
||||
'// [diag.myDiagnosticCode] message';
|
||||
}
|
||||
''');
|
||||
|
||||
assertElementIndexText(element, r'''
|
||||
46 4:13 |myDiagnosticCode| IS_REFERENCED_BY qualified
|
||||
''');
|
||||
}
|
||||
|
||||
test_ClassElement_emptyBody() async {
|
||||
await _indexTestUnit(r'''
|
||||
class C;
|
||||
|
||||
@@ -1498,6 +1498,34 @@ class C {
|
||||
''');
|
||||
}
|
||||
|
||||
test_searchReferences_analyzer_diagnosticCode() async {
|
||||
var diagnosticFile = newFile('$testPackageLibPath/diagnostic.dart', r'''
|
||||
const myDiagnosticCode = 0;
|
||||
''');
|
||||
|
||||
var diagnosticLibrary = await libraryElementForFile(diagnosticFile);
|
||||
var element = diagnosticLibrary.topLevelVariables.firstWhere(
|
||||
(v) => v.name == 'myDiagnosticCode',
|
||||
);
|
||||
|
||||
newFile('$testPackageLibPath/helper.dart', r'''
|
||||
import 'diagnostic.dart';
|
||||
''');
|
||||
|
||||
await resolveTestCode(r'''
|
||||
import 'helper.dart';
|
||||
|
||||
void f() {
|
||||
'// [diag.myDiagnosticCode]';
|
||||
}
|
||||
''');
|
||||
|
||||
await assertElementReferencesText(element, r'''
|
||||
<testLibraryFragment> f@28
|
||||
46 4:13 |myDiagnosticCode| REFERENCE qualified
|
||||
''');
|
||||
}
|
||||
|
||||
@SkippedTest() // TODO(scheglov): implement augmentation
|
||||
test_searchReferences_class_constructor_declaredInAugmentation() async {
|
||||
newFile('$testPackageLibPath/a.dart', r'''
|
||||
|
||||
Reference in New Issue
Block a user