blob: ab0e04da0d57ebc0a272c29079b29d2823c6d2d6 [file] [log] [blame]
// 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:analysis_server/src/domain_analysis.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:linter/src/rules.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../analysis_abstract.dart';
import '../src/utilities/mock_packages.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(NotificationErrorsTest);
});
}
@reflectiveTest
class NotificationErrorsTest extends AbstractAnalysisTest {
late Folder pedanticFolder;
Map<String, List<AnalysisError>?> filesErrors = {};
@override
void processNotification(Notification notification) {
if (notification.event == ANALYSIS_NOTIFICATION_ERRORS) {
var decoded = AnalysisErrorsParams.fromNotification(notification);
filesErrors[decoded.file] = decoded.errors;
} else if (notification.event == ANALYSIS_NOTIFICATION_FLUSH_RESULTS) {
var decoded = AnalysisFlushResultsParams.fromNotification(notification);
for (var file in decoded.files) {
filesErrors[file] = null;
}
}
}
@override
void setUp() {
registerLintRules();
super.setUp();
server.pendingFilesRemoveOverlayDelay = const Duration(milliseconds: 10);
server.handlers = [
AnalysisDomainHandler(server),
];
pedanticFolder = MockPackages.instance.addPedantic(resourceProvider);
}
Future<void> test_analysisOptionsFile() async {
var filePath = join(projectPath, 'analysis_options.yaml');
var analysisOptionsFile = newFile2(filePath, '''
linter:
rules:
- invalid_lint_rule_name
''').path;
await setRoots(included: [projectPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue();
//
// Verify the error result.
//
var errors = filesErrors[analysisOptionsFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, filePath);
expect(error.severity, AnalysisErrorSeverity.WARNING);
expect(error.type, AnalysisErrorType.STATIC_WARNING);
}
Future<void> test_analysisOptionsFile_packageInclude() async {
var filePath = join(projectPath, 'analysis_options.yaml');
var analysisOptionsFile = newFile2(filePath, '''
include: package:pedantic/analysis_options.yaml
''').path;
await setRoots(included: [projectPath], excluded: []);
await waitForTasksFinished();
await pumpEventQueue();
// Verify there's an error for the import.
var errors = filesErrors[analysisOptionsFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, filePath);
expect(error.severity, AnalysisErrorSeverity.WARNING);
expect(error.type, AnalysisErrorType.STATIC_WARNING);
// Write a package file that allows resolving the include.
newPackageConfigJsonFile(
projectPath,
(PackageConfigFileBuilder()
..add(name: 'pedantic', rootPath: pedanticFolder.parent.path))
.toContent(toUriStr: toUriStr),
);
// Ensure the errors disappear.
await waitForTasksFinished();
await pumpEventQueue();
errors = filesErrors[analysisOptionsFile]!;
expect(errors, hasLength(0));
}
Future<void> test_androidManifestFile() async {
var filePath = join(projectPath, 'android', 'AndroidManifest.xml');
var manifestFile = newFile2(filePath, '''
<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>
''').path;
newAnalysisOptionsYamlFile2(projectPath, '''
analyzer:
optional-checks:
chrome-os-manifest-checks: true
''');
await setRoots(included: [projectPath], 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, filePath);
expect(error.severity, AnalysisErrorSeverity.WARNING);
expect(error.type, AnalysisErrorType.STATIC_WARNING);
}
Future<void> test_androidManifestFile_dotDirectoryIgnored() async {
var filePath = join(projectPath, 'ios', '.symlinks', 'AndroidManifest.xml');
var manifestFile = newFile2(filePath, '''
<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>
''').path;
newAnalysisOptionsYamlFile2(projectPath, '''
analyzer:
optional-checks:
chrome-os-manifest-checks: true
''');
await setRoots(included: [projectPath], 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.
final configPath = join(projectPath, '.dart_tool/package_config.json');
final generatedProject = join(projectPath, '.dart_tool/foo');
final generatedFile = join(generatedProject, 'lib', 'foo.dart');
// Add the generated project into package_config.json.
final config = PackageConfigFileBuilder();
config.add(name: 'foo', rootPath: generatedProject);
newFile2(configPath, config.toContent(toUriStr: toUriStr));
// Set up project that references the class prior to initial analysis.
newFile2(generatedFile, 'class A {}');
addTestFile('''
import 'package:foo/foo.dart';
A? a;
''');
await createProject();
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[testFile], isEmpty);
// Remove the class, which should cause the main project to have an analysis
// error.
modifyFile(generatedFile, '');
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[testFile], isNotEmpty);
}
Future<void> test_dataFile() async {
var filePath = join(projectPath, 'lib', 'fix_data.yaml');
var dataFile = newFile2(filePath, '''
version: 1
transforms:
''').path;
await setRoots(included: [projectPath], 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, filePath);
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 createProject();
addTestFile('');
var brokenFile =
newFile2(join(projectPath, '.dart_tool/broken.dart'), 'err').path;
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 createProject();
addTestFile('');
var brokenFile =
newFile2(join(projectPath, '.dart_tool/broken.dart'), 'err').path;
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 waitResponse(AnalysisGetHoverParams(brokenFile, 0).toRequest('0'));
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 {
newAnalysisOptionsYamlFile2(projectPath, '''
analyzer:
exclude:
- excluded/**
''');
await createProject();
var excludedFile =
newFile2(join(projectPath, 'excluded/broken.dart'), 'err').path;
// 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 waitResponse(AnalysisGetHoverParams(excludedFile, 0).toRequest('0'));
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[excludedFile], isNull);
// Opening the file should still generate no errors.
await waitResponse(
AnalysisSetPriorityFilesParams([excludedFile]).toRequest('0'));
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
expect(filesErrors[excludedFile], isNull);
}
Future<void> test_importError() async {
await createProject();
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';
newAnalysisOptionsYamlFile2(projectPath, '''
linter:
rules:
- $camelCaseTypesLintName
''');
addTestFile('class a { }');
await setRoots(included: [projectPath], excluded: []);
await waitForTasksFinished();
var testDriver = server.getAnalysisDriver(testFile)!;
var lints = testDriver.analysisOptions.lintRules;
// Registry should only contain single lint rule.
expect(lints, hasLength(1));
var lint = lints.first as LintRule;
expect(lint.name, camelCaseTypesLintName);
// Verify lint error result.
var errors = filesErrors[testFile]!;
expect(errors, hasLength(1));
var error = errors[0];
expect(error.location.file, join(projectPath, 'bin', 'test.dart'));
expect(error.severity, AnalysisErrorSeverity.INFO);
expect(error.type, AnalysisErrorType.LINT);
expect(error.message, lint.description);
}
Future<void> test_notInAnalysisRoot() async {
await createProject();
var otherFile = newFile2('/other.dart', 'UnknownType V;').path;
addTestFile('''
import '/other.dart';
main() {
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 createProject();
addTestFile('');
var brokenFile =
newFile2(join(projectPath, '.dart_tool/broken.dart'), 'err').path;
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 waitResponse(
AnalysisUpdateContentParams({
brokenFile: AddContentOverlay('err'),
}).toRequest('1'),
);
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 createProject();
addTestFile('');
var brokenFile = convertPath(join(projectPath, 'broken.dart'));
// Add and overlay and give chance for the file to be analyzed.
await waitResponse(
AnalysisUpdateContentParams({
brokenFile: AddContentOverlay('err'),
}).toRequest('0'),
);
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 waitResponse(
AnalysisUpdateContentParams({
brokenFile: RemoveContentOverlay(),
}).toRequest('1'),
);
// Wait for the timer to remove the overlay to fire.
await Future.delayed(server.pendingFilesRemoveOverlayDelay);
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 createProject();
addTestFile('');
var brokenFile = convertPath(join(projectPath, 'broken.dart'));
// Add and overlay and give chance for the file to be analyzed.
await waitResponse(
AnalysisUpdateContentParams({
brokenFile: AddContentOverlay('err'),
}).toRequest('0'),
);
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// There should now be errors.
expect(filesErrors[brokenFile], hasLength(greaterThan(0)));
// Write the file to disk.
newFile2(brokenFile, 'err');
await waitForTasksFinished();
await pumpEventQueue(times: 5000);
// Remove the overlay.
await waitResponse(
AnalysisUpdateContentParams({
brokenFile: RemoveContentOverlay(),
}).toRequest('1'),
);
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 createProject();
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, join(projectPath, 'bin', 'test.dart'));
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(projectPath, '''
version: 1.3.2
''').path;
await setRoots(included: [projectPath], 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);
expect(error.severity, AnalysisErrorSeverity.WARNING);
expect(error.type, AnalysisErrorType.STATIC_WARNING);
//
// Fix the error and verify the new results.
//
modifyFile(pubspecFile, '''
name: sample
version: 1.3.2
''');
await waitForTasksFinished();
await pumpEventQueue();
errors = filesErrors[pubspecFile]!;
expect(errors, hasLength(0));
}
Future<void> test_pubspecFile_lint() async {
newAnalysisOptionsYamlFile2(projectPath, '''
linter:
rules:
- sort_pub_dependencies
''');
var pubspecFile = newPubspecYamlFile(projectPath, '''
name: sample
dependencies:
b: any
a: any
''').path;
await setRoots(included: [projectPath], 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);
expect(error.severity, AnalysisErrorSeverity.INFO);
expect(error.type, AnalysisErrorType.LINT);
//
// Fix the error and verify the new results.
//
modifyFile(pubspecFile, '''
name: sample
dependencies:
a: any
b: any
''');
await waitForTasksFinished();
await pumpEventQueue();
errors = filesErrors[pubspecFile]!;
expect(errors, hasLength(0));
}
Future<void> test_StaticWarning() async {
await createProject();
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.WARNING);
expect(error.type, AnalysisErrorType.STATIC_WARNING);
}
}