| // 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.', | |
| ); | |
| writeTestPackageConfig2( | |
| 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); | |
| } | |
| } |