Skip to content

Commit c667238

Browse files
srawlinsCommit Queue
authored andcommitted
DAS plugins: analyze and report diagnostics in parts
Fixes #61449 The previous code, sure enough, only created a DiagnosticReporter for the defining unit, and only visited the main unit. The fix is to create a DiagnosticListener for each unit, set the DiagnosticReporters correctly and the "current unit" as we go, and then pull the reported diagnostics from each listener. Change-Id: I6b645284c58e5e35ccc5b89f30f6ffe97f48296c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/448644 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 80eae1e commit c667238

File tree

3 files changed

+203
-52
lines changed

3 files changed

+203
-52
lines changed

pkg/analysis_server_plugin/lib/src/plugin_server.dart

Lines changed: 73 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
3030
import 'package:analyzer/src/dart/analysis/analysis_options.dart';
3131
import 'package:analyzer/src/dart/analysis/byte_store.dart';
3232
import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
33+
import 'package:analyzer/src/dart/element/element.dart';
3334
import 'package:analyzer/src/dart/element/type_system.dart';
3435
import 'package:analyzer/src/ignore_comments/ignore_info.dart';
3536
import 'package:analyzer/src/lint/config.dart';
@@ -333,41 +334,47 @@ class PluginServer {
333334
path,
334335
);
335336
if (libraryResult is! ResolvedLibraryResult) {
337+
// We only handle analyzing at the library-level. Below, we work through
338+
// each of the compilation units found in `libraryResult`.
336339
return const [];
337340
}
338-
var unitResult = await analysisContext.currentSession.getResolvedUnit(path);
339-
if (unitResult is! ResolvedUnitResult) {
340-
return const [];
341-
}
342-
var listener = RecordingDiagnosticListener();
343-
var diagnosticReporter = DiagnosticReporter(
344-
listener,
345-
unitResult.libraryElement.firstFragment.source,
346-
);
347341

348-
var currentUnit = RuleContextUnit(
349-
file: unitResult.file,
350-
content: unitResult.content,
351-
diagnosticReporter: diagnosticReporter,
352-
unit: unitResult.unit,
353-
);
354-
var allUnits = [
342+
var diagnosticListeners = {
355343
for (var unitResult in libraryResult.units)
356-
RuleContextUnit(
357-
file: unitResult.file,
358-
content: unitResult.content,
359-
diagnosticReporter: diagnosticReporter,
360-
unit: unitResult.unit,
344+
unitResult: RecordingDiagnosticListener(),
345+
};
346+
347+
RuleContextUnit? definingContextUnit;
348+
var definingUnit =
349+
(libraryResult.element as LibraryElementImpl).definingCompilationUnit;
350+
var allUnits = <RuleContextUnit>[];
351+
352+
for (var unitResult in libraryResult.units) {
353+
var contextUnit = RuleContextUnit(
354+
file: unitResult.file,
355+
content: unitResult.content,
356+
diagnosticReporter: DiagnosticReporter(
357+
diagnosticListeners[unitResult]!,
358+
unitResult.libraryElement.firstFragment.source,
361359
),
362-
];
360+
unit: unitResult.unit,
361+
);
362+
allUnits.add(contextUnit);
363+
if (unitResult.unit.declaredFragment == definingUnit) {
364+
definingContextUnit = contextUnit;
365+
}
366+
}
367+
// Just a fallback value. We shouldn't get into this situation, but this is
368+
// a safe default.
369+
definingContextUnit ??= allUnits.first;
363370

364371
// TODO(srawlins): Enable timing similar to what the linter package's
365-
// `benchhmark.dart` script does.
372+
// `benchmark.dart` script does.
366373
var nodeRegistry = RuleVisitorRegistryImpl(enableTiming: false);
367374

368375
var context = RuleContextWithResolvedResults(
369376
allUnits,
370-
currentUnit,
377+
definingContextUnit,
371378
libraryResult.element.typeProvider,
372379
libraryResult.element.typeSystem as TypeSystemImpl,
373380
// TODO(srawlins): Support 'package' parameter.
@@ -386,56 +393,72 @@ class PluginServer {
386393
var rules = Registry.ruleRegistry.enabled(
387394
configuration.diagnosticConfigs,
388395
);
389-
for (var rule in rules) {
390-
rule.reporter = diagnosticReporter;
391-
// TODO(srawlins): Enable timing similar to what the linter package's
392-
// `benchmark.dart` script does.
393-
rule.registerNodeProcessors(nodeRegistry, context);
394-
}
396+
395397
for (var code in rules.expand((r) => r.diagnosticCodes)) {
396398
pluginCodeMapping.putIfAbsent(code, () => configuration.name);
397399
severityMapping.putIfAbsent(
398400
code,
399401
() => _configuredSeverity(configuration, code),
400402
);
401403
}
402-
}
403404

404-
context.currentUnit = currentUnit;
405-
currentUnit.unit.accept(
406-
AnalysisRuleVisitor(nodeRegistry, shouldPropagateExceptions: true),
407-
);
405+
for (var rule in rules) {
406+
// TODO(srawlins): Enable timing similar to what the linter package's
407+
// `benchmark.dart` script does.
408+
rule.registerNodeProcessors(nodeRegistry, context);
409+
}
408410

409-
var ignoreInfo = IgnoreInfo.forDart(unitResult.unit, unitResult.content);
410-
var diagnostics = listener.diagnostics.where((e) {
411-
var pluginName = pluginCodeMapping[e.diagnosticCode];
412-
if (pluginName == null) {
413-
// If [e] is somehow not mapped, something is wrong; but don't mark it
414-
// as ignored.
415-
return true;
411+
// Now to perform the actual analysis.
412+
for (var currentUnit in allUnits) {
413+
for (var rule in rules) {
414+
rule.reporter = currentUnit.diagnosticReporter;
415+
}
416+
417+
context.currentUnit = currentUnit;
418+
currentUnit.unit.accept(
419+
AnalysisRuleVisitor(nodeRegistry, shouldPropagateExceptions: true),
420+
);
416421
}
417-
return !ignoreInfo.ignored(e, pluginName: pluginName);
418-
});
422+
}
423+
424+
// TODO(srawlins): Support `AnalysisRuleVisitor.afterLibrary`. See how it is
425+
// used in `library_analyzer.dart`.
419426

420427
// The list of the `AnalysisError`s and their associated
421428
// `protocol.AnalysisError`s.
422-
var diagnosticsAndProtocolErrors = [
423-
for (var diagnostic in diagnostics)
424-
(
429+
var diagnosticsAndProtocolErrors =
430+
<({Diagnostic diagnostic, protocol.AnalysisError protocolError})>[];
431+
432+
diagnosticListeners.forEach((unitResult, listener) {
433+
var ignoreInfo = IgnoreInfo.forDart(unitResult.unit, unitResult.content);
434+
var diagnostics = listener.diagnostics.where((e) {
435+
var pluginName = pluginCodeMapping[e.diagnosticCode];
436+
if (pluginName == null) {
437+
// If [e] is somehow not mapped, something is wrong; but don't mark it
438+
// as ignored.
439+
return true;
440+
}
441+
return !ignoreInfo.ignored(e, pluginName: pluginName);
442+
});
443+
444+
for (var diagnostic in diagnostics) {
445+
diagnosticsAndProtocolErrors.add((
425446
diagnostic: diagnostic,
426447
protocolError: protocol.AnalysisError(
427448
severityMapping[diagnostic.diagnosticCode] ??
428449
protocol.AnalysisErrorSeverity.INFO,
429450
protocol.AnalysisErrorType.STATIC_WARNING,
430-
_locationFor(currentUnit.unit, path, diagnostic),
451+
_locationFor(unitResult.unit, unitResult.path, diagnostic),
431452
diagnostic.message,
432453
diagnostic.diagnosticCode.name,
433454
correction: diagnostic.correctionMessage,
434455
// TODO(srawlins): Use a valid value here.
435456
hasFix: true,
436457
),
437-
),
438-
];
458+
));
459+
}
460+
});
461+
439462
_recentState[path] = (
440463
analysisContext: analysisContext,
441464
errors: [...diagnosticsAndProtocolErrors],

pkg/analysis_server_plugin/test/src/lint_rules.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,31 @@ class NoDoublesCustomSeverityRule extends AnalysisRule {
7272
}
7373
}
7474

75+
class NoReferencesToStringsRule extends AnalysisRule {
76+
static const LintCode code = LintCode(
77+
'no_references_to_strings',
78+
'No references to Strings',
79+
);
80+
81+
NoReferencesToStringsRule()
82+
: super(
83+
name: 'no_references_to_strings',
84+
description: 'No references to Strings',
85+
);
86+
87+
@override
88+
DiagnosticCode get diagnosticCode => code;
89+
90+
@override
91+
void registerNodeProcessors(
92+
RuleVisitorRegistry registry,
93+
RuleContext context,
94+
) {
95+
var visitor = _NoReferencesToStringsVisitor(this, context);
96+
registry.addSimpleIdentifier(this, visitor);
97+
}
98+
}
99+
75100
class _NoBoolsVisitor extends SimpleAstVisitor<void> {
76101
final AnalysisRule rule;
77102

@@ -93,3 +118,18 @@ class _NoDoublesVisitor extends SimpleAstVisitor<void> {
93118
rule.reportAtNode(node);
94119
}
95120
}
121+
122+
class _NoReferencesToStringsVisitor extends SimpleAstVisitor<void> {
123+
final AnalysisRule rule;
124+
125+
final RuleContext context;
126+
127+
_NoReferencesToStringsVisitor(this.rule, this.context);
128+
129+
@override
130+
void visitSimpleIdentifier(SimpleIdentifier node) {
131+
if (node.staticType?.isDartCoreString ?? false) {
132+
rule.reportAtNode(node);
133+
}
134+
}
135+
}

pkg/analysis_server_plugin/test/src/plugin_server_test.dart

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,16 @@ class PluginServerTest extends PluginServerTestBase {
3434

3535
String get filePath => join(packagePath, 'lib', 'test.dart');
3636

37+
String get file2Path => join(packagePath, 'lib', 'test2.dart');
38+
3739
String get packagePath => convertPath('/package1');
3840

3941
StreamQueue<protocol.AnalysisErrorsParams> get _analysisErrorsParams {
4042
return StreamQueue(
4143
channel.notifications
4244
.where((n) => n.event == protocol.ANALYSIS_NOTIFICATION_ERRORS)
4345
.map((n) => protocol.AnalysisErrorsParams.fromNotification(n))
44-
.where((p) => p.file == filePath),
46+
.where((p) => p.file == filePath || p.file == file2Path),
4547
);
4648
}
4749

@@ -229,7 +231,11 @@ bool b = false;
229231
expect(details.name, 'No Literals Plugin');
230232
expect(
231233
details.lintRules,
232-
unorderedEquals(['no_doubles', 'no_doubles_custom_severity']),
234+
unorderedEquals([
235+
'no_doubles',
236+
'no_doubles_custom_severity',
237+
'no_references_to_strings',
238+
]),
233239
);
234240
expect(details.warningRules, unorderedEquals(['no_bools']));
235241
expect(details.fixes, hasLength(1));
@@ -314,6 +320,87 @@ bool b = false;
314320
_expectAnalysisError(params.errors.single, message: 'No bools message');
315321
}
316322

323+
Future<void> test_updateContent_addOverlay_affectedPart() async {
324+
writeAnalysisOptionsWithPlugin({'no_references_to_strings': 'enable'});
325+
newFile(filePath, '''
326+
part 'test2.dart';
327+
int s = 7;
328+
''');
329+
newFile(file2Path, '''
330+
part of 'test.dart';
331+
void f() {
332+
print(s);
333+
}
334+
''');
335+
await channel.sendRequest(
336+
protocol.AnalysisSetContextRootsParams([contextRoot]),
337+
);
338+
339+
var paramsQueue = _analysisErrorsParams;
340+
var params = await paramsQueue.next; // test.dart
341+
expect(params.errors, isEmpty);
342+
params = await paramsQueue.next; // test2.dart
343+
expect(params.errors, isEmpty);
344+
345+
await channel.sendRequest(
346+
protocol.AnalysisUpdateContentParams({
347+
filePath: protocol.AddContentOverlay('''
348+
part 'test2.dart';
349+
String s = "hello";
350+
'''),
351+
}),
352+
);
353+
354+
params = await paramsQueue.next;
355+
expect(params.errors, hasLength(1));
356+
_expectAnalysisError(
357+
params.errors.single,
358+
message: 'No references to Strings',
359+
);
360+
}
361+
362+
Future<void> test_updateContent_addOverlay_affectedLibrary() async {
363+
writeAnalysisOptionsWithPlugin({'no_references_to_strings': 'enable'});
364+
newFile(filePath, '''
365+
int s = 7;
366+
void f() {
367+
print(s);
368+
}
369+
''');
370+
newFile(file2Path, '''
371+
import 'test.dart';
372+
void f() {
373+
print(s);
374+
}
375+
''');
376+
await channel.sendRequest(
377+
protocol.AnalysisSetContextRootsParams([contextRoot]),
378+
);
379+
380+
var paramsQueue = _analysisErrorsParams;
381+
var params = await paramsQueue.next; // test.dart
382+
expect(params.errors, isEmpty);
383+
params = await paramsQueue.next; // test2.dart
384+
expect(params.errors, isEmpty);
385+
386+
await channel.sendRequest(
387+
protocol.AnalysisUpdateContentParams({
388+
filePath: protocol.AddContentOverlay('''
389+
String s = "hello";
390+
'''),
391+
}),
392+
);
393+
394+
params = await paramsQueue.next; // test.dart
395+
params = await paramsQueue.next; // test2.dart
396+
expect(params.errors, hasLength(1));
397+
expect(params.file, file2Path);
398+
_expectAnalysisError(
399+
params.errors.single,
400+
message: 'No references to Strings',
401+
);
402+
}
403+
317404
Future<void> test_updateContent_removeOverlay() async {
318405
writeAnalysisOptionsWithPlugin();
319406
newFile(filePath, 'bool b = false;');
@@ -509,6 +596,7 @@ class _NoLiteralsPlugin extends Plugin {
509596
registry.registerWarningRule(NoBoolsRule());
510597
registry.registerLintRule(NoDoublesRule());
511598
registry.registerLintRule(NoDoublesCustomSeverityRule());
599+
registry.registerLintRule(NoReferencesToStringsRule());
512600
registry.registerFixForRule(NoBoolsRule.code, _WrapInQuotes.new);
513601
registry.registerAssist(_InvertBoolean.new);
514602
}

0 commit comments

Comments
 (0)