Files
sdk/pkg/analysis_server/test/analysis/notification_errors_test.dart
Konstantin Shcheglov e20704c3b0 CQ. Move PackageConfigFileBuilder to analyzer_testing.
Move PackageConfigFileBuilder into the analyzer_testing public API and
deprecate the copy exposed from package:analyzer. The builder is only
used by test infrastructure, so keeping it in analyzer_testing makes the
ownership clearer and avoids exposing test-only utilities from analyzer.

Update the builder API to accept a rootFolder instead of a rootPath.
This lets callers pass the resource-provider folder directly, so the
generated rootUri is derived from the same file-system abstraction that
created the test files. This avoids accidentally passing POSIX paths
where resource provider paths are required, such as on Windows.

Update existing test utilities and callers to import the new library and
pass Folder objects. Remove the production analysis server dependency on
the builder by emitting the temporary plugin package config JSON
directly.

Change-Id: I46b14710626e0d6d5884afcdc5a05b23077acfc9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499081
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
2026-04-29 13:18:35 -07:00

601 lines
19 KiB
Dart

// Copyright (c) 2014, 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/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_constants.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_testing/package_config_file_builder.dart';
import 'package:linter/src/rules.dart';
import 'package:path/path.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../analysis_server_base.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(NotificationErrorsTest);
});
}
@reflectiveTest
class NotificationErrorsTest extends PubPackageAnalysisServerTest {
Map<File, List<AnalysisError>?> filesErrors = {};
@override
void processNotification(Notification notification) {
if (notification.event == analysisNotificationErrors) {
var decoded = AnalysisErrorsParams.fromNotification(
notification,
clientUriConverter: server.uriConverter,
);
filesErrors[getFile(decoded.file)] = decoded.errors;
} else if (notification.event == analysisNotificationFlushResults) {
var decoded = AnalysisFlushResultsParams.fromNotification(
notification,
clientUriConverter: server.uriConverter,
);
for (var file in decoded.files) {
filesErrors[getFile(file)] = null;
}
}
}
@override
void setUp() {
registerLintRules();
super.setUp();
}
Future<void> test_analysisOptionsFile() async {
var analysisOptions = newAnalysisOptionsYamlFile(testPackageRootPath, '''
linter:
rules:
- invalid_lint_rule_name
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue();
//
// Verify the error result.
//
var errors = filesErrors[analysisOptions]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, analysisOptions.path);
expect(error.severity, AnalysisErrorSeverity.WARNING);
expect(error.type, AnalysisErrorType.STATIC_WARNING);
}
Future<void> test_analysisOptionsFile_packageInclude() async {
var analysisOptions = newAnalysisOptionsYamlFile(testPackageRootPath, '''
include: package:lints/lints.yaml
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue();
// Verify there's an error for the import.
var errors = filesErrors[analysisOptions]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, analysisOptions.path);
expect(error.severity, AnalysisErrorSeverity.WARNING);
expect(error.type, AnalysisErrorType.STATIC_WARNING);
// Write a package file that allows resolving the include.
var lintsRootPath = '/packages/lints';
newFile(
'$lintsRootPath/lib/lints.yaml',
'# Used for testing diagnostics for imports in analysis options files.',
);
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'lints', rootFolder: getFolder(lintsRootPath)),
);
// Ensure the errors disappear.
await waitForTasksFinished();
await pumpEventQueue();
errors = filesErrors[analysisOptions]!;
expect(errors, hasLength(0));
}
Future<void> test_androidManifestFile() async {
var manifestPath = join(
testPackageRootPath,
'android',
'AndroidManifest.xml',
);
var manifestFile = newFile(manifestPath, '''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.software.home_screen" />
</manifest>
''');
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
optional-checks:
chrome-os-manifest-checks: true
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue();
//
// Verify the error result.
//
var errors = filesErrors[manifestFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, manifestFile.path);
expect(error.severity, AnalysisErrorSeverity.WARNING);
expect(error.type, AnalysisErrorType.STATIC_WARNING);
}
Future<void> test_androidManifestFile_dotDirectoryIgnored() async {
var manifestPath = join(
testPackageRootPath,
'ios',
'.symlinks',
'AndroidManifest.xml',
);
var manifestFile = newFile(manifestPath, '''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.software.home_screen" />
</manifest>
''');
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
optional-checks:
chrome-os-manifest-checks: true
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue();
//
// Verify that the file wasn't analyzed.
//
var errors = filesErrors[manifestFile];
expect(errors, isNull);
}
Future<void> test_dartToolGeneratedProject_referencedByUserProject() async {
// Although errors are not generated for dotfolders, their contents should
// still be analyzed so that code that references them (for example
// flutter_gen) should still be updated.
var configPath = join(
testPackageRootPath,
'.dart_tool/package_config.json',
);
var generatedProject = join(testPackageRootPath, '.dart_tool/foo');
var generatedFilePath = join(generatedProject, 'lib', 'foo.dart');
// Add the generated project into package_config.json.
var config = PackageConfigFileBuilder();
config.add(name: 'foo', rootFolder: getFolder(generatedProject));
newFile(configPath, config.toContent());
// Set up project that references the class prior to initial analysis.
var generatedFile = newFile(generatedFilePath, 'class A {}');
addTestFile('''
import 'package:foo/foo.dart';
A? a;
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[testFile], isEmpty);
// Remove the class, which should cause the main project to have an analysis
// error.
modifyFile2(generatedFile, '');
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[testFile], isNotEmpty);
}
Future<void> test_dataFile() async {
var dataFile = newFile('$testPackageLibPath/fix_data.yaml', '''
version: 1
transforms:
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue();
//
// Verify the error result.
//
var errors = filesErrors[dataFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, dataFile.path);
expect(error.severity, AnalysisErrorSeverity.ERROR);
expect(error.type, AnalysisErrorType.COMPILE_TIME_ERROR);
}
Future<void> test_dotFolder_priority() async {
// Files inside dotFolders should not generate error notifications even
// if they are added to priority (priority affects only priority, not what
// is analyzed).
await setRoots(included: [workspaceRootPath], excluded: []);
addTestFile('');
var brokenFile = newFile(
join(testPackageRootPath, '.dart_tool/broken.dart'),
'err',
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[brokenFile], isNull);
// Add to priority files and give chance for the file to be analyzed (if
// it would).
setPriorityFiles([brokenFile]);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// There should still be no errors.
expect(filesErrors[brokenFile], isNull);
}
Future<void> test_dotFolder_unopenedFile() async {
// Files inside dotFolders are not analyzed. Sending requests that cause
// them to be opened (such as hovers) should not result in error notifications
// because there is no event that would flush them and they'd remain in the
// editor forever.
await setRoots(included: [workspaceRootPath], excluded: []);
addTestFile('');
var brokenFile = newFile(
'$testPackageRootPath/.dart_tool/broken.dart',
'err',
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[brokenFile], isNull);
// Send a getHover request for the file that will cause it to be read from disk.
await handleSuccessfulRequest(
AnalysisGetHoverParams(
brokenFile.path,
0,
).toRequest('0', clientUriConverter: server.uriConverter),
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// There should be no errors because the file is not being analyzed.
expect(filesErrors[brokenFile], isNull);
}
Future<void> test_excludedFolder() async {
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
exclude:
- excluded/**
''');
await setRoots(included: [workspaceRootPath], excluded: []);
var excludedFile = newFile(
'$testPackageRootPath/excluded/broken.dart',
'err',
);
// There should be no errors initially.
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[excludedFile], isNull);
// Triggering the file to be processed should still generate no errors.
await handleSuccessfulRequest(
AnalysisGetHoverParams(
excludedFile.path,
0,
).toRequest('0', clientUriConverter: server.uriConverter),
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[excludedFile], isNull);
// Opening the file should still generate no errors.
await handleSuccessfulRequest(
AnalysisSetPriorityFilesParams([
excludedFile.path,
]).toRequest('0', clientUriConverter: server.uriConverter),
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[excludedFile], isNull);
}
Future<void> test_importError() async {
await setRoots(included: [workspaceRootPath], excluded: []);
addTestFile('''
import 'does_not_exist.dart';
''');
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
var errors = filesErrors[testFile]!;
// Verify that we are generating only 1 error for the bad URI.
// https://github.com/dart-lang/sdk/issues/23754
expect(errors, hasLength(1));
var error = errors[0];
expect(error.severity, AnalysisErrorSeverity.ERROR);
expect(error.type, AnalysisErrorType.COMPILE_TIME_ERROR);
expect(error.message, startsWith("Target of URI doesn't exist"));
}
Future<void> test_lintError() async {
var camelCaseTypesLintName = 'camel_case_types';
newAnalysisOptionsYamlFile(testPackageRootPath, '''
linter:
rules:
- $camelCaseTypesLintName
''');
addTestFile('class a { }');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
var lints = testFileAnalysisOptions.lintRules;
// Registry should only contain single lint rule.
expect(lints, hasLength(1));
var lint = lints.first;
expect(lint.name, camelCaseTypesLintName);
// Verify lint error result.
var errors = filesErrors[testFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, testFile.path);
expect(error.severity, AnalysisErrorSeverity.INFO);
expect(error.type, AnalysisErrorType.LINT);
}
Future<void> test_notInAnalysisRoot() async {
await setRoots(included: [workspaceRootPath], excluded: []);
var otherFile = newFile('/other.dart', 'UnknownType V;');
addTestFile('''
import '/other.dart';
void f() {
print(V);
}
''');
await waitForTasksFinished();
expect(filesErrors[otherFile], isNull);
}
Future<void> test_overlay_dotFolder() async {
// Files inside dotFolders should not generate error notifications even
// if they have overlays added.
await setRoots(included: [workspaceRootPath], excluded: []);
addTestFile('');
var brokenFile = newFile(
'$testPackageRootPath/.dart_tool/broken.dart',
'err',
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[brokenFile], isNull);
// Add and overlay and give chance for the file to be analyzed (if
// it would).
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
brokenFile.path: AddContentOverlay('err'),
}).toRequest('1', clientUriConverter: server.uriConverter),
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// There should still be no errors.
expect(filesErrors[brokenFile], isNull);
}
Future<void> test_overlay_newFile() async {
// Overlays added for files that don't exist on disk should still generate
// error notifications. Removing the overlay if the file is not on disk
// should clear the errors.
await setRoots(included: [workspaceRootPath], excluded: []);
addTestFile('');
var brokenFile = getFile('$testPackageRootPath/broken.dart');
// Add and overlay and give chance for the file to be analyzed.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
brokenFile.path: AddContentOverlay('err'),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// There should now be errors.
expect(filesErrors[brokenFile], hasLength(greaterThan(0)));
// Remove the overlay (this file no longer exists anywhere).
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
brokenFile.path: RemoveContentOverlay(),
}).toRequest('1', clientUriConverter: server.uriConverter),
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// Unlike other tests here, removing an overlay for a file that doesn't exist
// on disk doesn't flush errors, but re-analyzes the missing file, which results
// in an error notification of 0 errors rather than a flush.
expect(filesErrors[brokenFile], isEmpty);
}
Future<void> test_overlay_newFileSavedBeforeRemoving() async {
// Overlays added for files that don't exist on disk should still generate
// error notifications. If the file is subsequently saved to disk before the
// overlay is removed, the errors should not be flushed when the overlay is
// removed.
await setRoots(included: [workspaceRootPath], excluded: []);
addTestFile('');
var brokenFile = getFile('$testPackageRootPath/broken.dart');
// Add and overlay and give chance for the file to be analyzed.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
brokenFile.path: AddContentOverlay('err'),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// There should now be errors.
expect(filesErrors[brokenFile], hasLength(greaterThan(0)));
// Write the file to disk.
brokenFile.writeAsStringSync('err');
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// Remove the overlay.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
brokenFile.path: RemoveContentOverlay(),
}).toRequest('1', clientUriConverter: server.uriConverter),
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// Errors should not have been flushed since the file still exists without
// the overlay.
expect(filesErrors[brokenFile], hasLength(greaterThan(0)));
}
Future<void> test_ParserError() async {
await setRoots(included: [workspaceRootPath], excluded: []);
addTestFile('library lib');
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
var errors = filesErrors[testFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, testFile.path);
expect(error.location.offset, isPositive);
expect(error.location.length, isNonNegative);
expect(error.severity, AnalysisErrorSeverity.ERROR);
expect(error.type, AnalysisErrorType.SYNTACTIC_ERROR);
expect(error.message, isNotNull);
}
Future<void> test_pubspecFile() async {
var pubspecFile = newPubspecYamlFile(testPackageRootPath, '''
version: 1.3.2
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue();
//
// Verify the error result.
//
var errors = filesErrors[pubspecFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, pubspecFile.path);
expect(error.severity, AnalysisErrorSeverity.WARNING);
expect(error.type, AnalysisErrorType.STATIC_WARNING);
//
// Fix the error and verify the new results.
//
pubspecFile.writeAsStringSync('''
name: sample
version: 1.3.2
''');
await waitForTasksFinished();
await pumpEventQueue();
errors = filesErrors[pubspecFile]!;
expect(errors, hasLength(0));
}
Future<void> test_pubspecFile_lint() async {
newAnalysisOptionsYamlFile(testPackageRootPath, '''
linter:
rules:
- sort_pub_dependencies
''');
var pubspecFile = newPubspecYamlFile(testPackageRootPath, '''
name: sample
dependencies:
b: any
a: any
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue();
//
// Verify the error result.
//
var errors = filesErrors[pubspecFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, pubspecFile.path);
expect(error.severity, AnalysisErrorSeverity.INFO);
expect(error.type, AnalysisErrorType.LINT);
//
// Fix the error and verify the new results.
//
pubspecFile.writeAsStringSync('''
name: sample
dependencies:
a: any
b: any
''');
await waitForTasksFinished();
await pumpEventQueue();
errors = filesErrors[pubspecFile]!;
expect(errors, hasLength(0));
}
Future<void> test_StaticWarning() async {
await setRoots(included: [workspaceRootPath], excluded: []);
addTestFile('''
enum E {e1, e2}
void f(E e) {
switch (e) {
case E.e1:
print(0);
break;
}
}
''');
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
var errors = filesErrors[testFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.severity, AnalysisErrorSeverity.ERROR);
expect(error.type, AnalysisErrorType.COMPILE_TIME_ERROR);
}
}