// 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. // @dart = 2.9 import 'dart:mirrors'; import 'package:analysis_server/src/protocol_server.dart' hide DiagnosticMessage; import 'package:analysis_server/src/services/search/search_engine.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/session.dart'; import 'package:analyzer/dart/element/element.dart' as engine; import 'package:analyzer/diagnostic/diagnostic.dart'; import 'package:analyzer/error/error.dart' as engine; import 'package:analyzer/src/dart/analysis/results.dart' as engine; import 'package:analyzer/src/dart/error/lint_codes.dart'; import 'package:analyzer/src/diagnostic/diagnostic.dart' as engine; import 'package:analyzer/src/error/codes.dart' as engine; import 'package:analyzer/src/generated/source.dart' as engine; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'constants.dart'; import 'mocks.dart'; void main() { defineReflectiveSuite(() { defineReflectiveTests(AnalysisErrorTest); defineReflectiveTests(EnumTest); }); } @reflectiveTest class AnalysisErrorTest { MockSource source = MockSource(); MockAnalysisError engineError; ResolvedUnitResult result; void setUp() { // prepare Source source.fullName = 'foo.dart'; // prepare AnalysisError engineError = MockAnalysisError(source, engine.CompileTimeErrorCode.AMBIGUOUS_EXPORT, 10, 20, 'my message'); // prepare ResolvedUnitResult var lineInfo = engine.LineInfo([0, 5, 9, 20]); result = engine.ResolvedUnitResultImpl(null, 'foo.dart', null, true, null, lineInfo, false, null, [engineError]); } void tearDown() { source = null; engineError = null; } void test_fromEngine_hasContextMessage() { engineError.contextMessages.add(engine.DiagnosticMessageImpl( filePath: 'bar.dart', offset: 30, length: 5, message: 'context')); var session = MockAnalysisSession(); session.addFileResult(engine.FileResultImpl( session, 'bar.dart', null, engine.LineInfo([0, 5, 9, 20]), false)); var error = newAnalysisError_fromEngine( engine.ResolvedUnitResultImpl(session, 'foo.dart', null, true, null, engine.LineInfo([0, 5, 9, 20]), false, null, [engineError]), engineError); expect(error.toJson(), { SEVERITY: 'ERROR', TYPE: 'COMPILE_TIME_ERROR', LOCATION: { FILE: 'foo.dart', OFFSET: 10, LENGTH: 20, START_LINE: 3, START_COLUMN: 2, END_LINE: 4, END_COLUMN: 11, }, MESSAGE: 'my message', CODE: 'ambiguous_export', URL: 'https://dart.dev/tools/diagnostic-messages#ambiguous_export', CONTEXT_MESSAGES: [ { MESSAGE: 'context', LOCATION: { FILE: 'bar.dart', OFFSET: 30, LENGTH: 5, START_LINE: 4, START_COLUMN: 11, END_LINE: 4, END_COLUMN: 16, } } ], HAS_FIX: false }); } void test_fromEngine_hasCorrection() { engineError.correction = 'my correction'; var error = newAnalysisError_fromEngine(result, engineError); expect(error.toJson(), { SEVERITY: 'ERROR', TYPE: 'COMPILE_TIME_ERROR', LOCATION: { FILE: 'foo.dart', OFFSET: 10, LENGTH: 20, START_LINE: 3, START_COLUMN: 2, END_LINE: 4, END_COLUMN: 11, }, MESSAGE: 'my message', CORRECTION: 'my correction', CODE: 'ambiguous_export', URL: 'https://dart.dev/tools/diagnostic-messages#ambiguous_export', HAS_FIX: false }); } void test_fromEngine_hasUrl() { engineError = MockAnalysisError( source, MockErrorCode(url: 'http://codes.dartlang.org/TEST_ERROR'), 10, 20, 'my message'); var error = newAnalysisError_fromEngine(result, engineError); expect(error.toJson(), { SEVERITY: 'ERROR', TYPE: 'COMPILE_TIME_ERROR', LOCATION: { FILE: 'foo.dart', OFFSET: 10, LENGTH: 20, START_LINE: 3, START_COLUMN: 2, END_LINE: 4, END_COLUMN: 11, }, MESSAGE: 'my message', CODE: 'test_error', URL: 'http://codes.dartlang.org/TEST_ERROR', HAS_FIX: false }); } void test_fromEngine_lint() { engineError = MockAnalysisError( source, LintCode('my_lint', 'my message', correction: 'correction'), 10, 20, 'my message'); var error = newAnalysisError_fromEngine(result, engineError); expect(error.toJson(), { SEVERITY: 'INFO', TYPE: 'LINT', LOCATION: { FILE: 'foo.dart', OFFSET: 10, LENGTH: 20, START_LINE: 3, START_COLUMN: 2, END_LINE: 4, END_COLUMN: 11, }, MESSAGE: 'my message', CODE: 'my_lint', URL: 'https://dart-lang.github.io/linter/lints/my_lint.html', HAS_FIX: false }); } void test_fromEngine_noCorrection() { engineError.correction = null; var error = newAnalysisError_fromEngine(result, engineError); expect(error.toJson(), { SEVERITY: 'ERROR', TYPE: 'COMPILE_TIME_ERROR', LOCATION: { FILE: 'foo.dart', OFFSET: 10, LENGTH: 20, START_LINE: 3, START_COLUMN: 2, END_LINE: 4, END_COLUMN: 11, }, MESSAGE: 'my message', CODE: 'ambiguous_export', URL: 'https://dart.dev/tools/diagnostic-messages#ambiguous_export', HAS_FIX: false }); } void test_fromEngine_noLineInfo() { engineError.correction = null; var error = newAnalysisError_fromEngine( engine.ResolvedUnitResultImpl(null, 'foo.dart', null, true, null, null, false, null, [engineError]), engineError); expect(error.toJson(), { SEVERITY: 'ERROR', TYPE: 'COMPILE_TIME_ERROR', LOCATION: { FILE: 'foo.dart', OFFSET: 10, LENGTH: 20, START_LINE: -1, START_COLUMN: -1, END_LINE: -1, END_COLUMN: -1, }, MESSAGE: 'my message', CODE: 'ambiguous_export', URL: 'https://dart.dev/tools/diagnostic-messages#ambiguous_export', HAS_FIX: false }); } } @reflectiveTest class EnumTest { void test_AnalysisErrorSeverity() { EnumTester().run( (engine.ErrorSeverity engineErrorSeverity) => AnalysisErrorSeverity(engineErrorSeverity.name), exceptions: {engine.ErrorSeverity.NONE: null}); } void test_AnalysisErrorType() { EnumTester().run( (engine.ErrorType engineErrorType) => AnalysisErrorType(engineErrorType.name)); } void test_ElementKind() { EnumTester() .run(convertElementKind, exceptions: { // TODO(paulberry): do any of the exceptions below constitute bugs? engine.ElementKind.DYNAMIC: ElementKind.UNKNOWN, engine.ElementKind.ERROR: ElementKind.UNKNOWN, engine.ElementKind.EXPORT: ElementKind.UNKNOWN, engine.ElementKind.GENERIC_FUNCTION_TYPE: ElementKind.FUNCTION_TYPE_ALIAS, engine.ElementKind.IMPORT: ElementKind.UNKNOWN, engine.ElementKind.NAME: ElementKind.UNKNOWN, engine.ElementKind.NEVER: ElementKind.UNKNOWN, engine.ElementKind.UNIVERSE: ElementKind.UNKNOWN }); } void test_SearchResultKind() { // TODO(paulberry): why does the MatchKind class exist at all? Can't we // use SearchResultKind inside the analysis server? EnumTester() .run(newSearchResultKind_fromEngine); } } /// Helper class for testing the correspondence between an analysis engine enum /// and an analysis server API enum. class EnumTester { /// Test that the function [convert] properly converts all possible values of /// [EngineEnum] to an [ApiEnum] with the same name, with the exceptions noted /// in [exceptions]. For each key in [exceptions], if the corresponding value /// is null, then we check that converting the given key results in an error. /// If the corresponding value is an [ApiEnum], then we check that converting /// the given key results in the given value. void run(ApiEnum Function(EngineEnum) convert, {Map exceptions = const {}}) { var engineClass = reflectClass(EngineEnum); engineClass.staticMembers.forEach((Symbol symbol, MethodMirror method) { if (symbol == #values) { return; } if (!method.isGetter) { return; } var enumName = MirrorSystem.getName(symbol); var engineValue = engineClass.getField(symbol).reflectee as EngineEnum; expect(engineValue, TypeMatcher()); if (exceptions.containsKey(engineValue)) { var expectedResult = exceptions[engineValue]; if (expectedResult == null) { expect(() { convert(engineValue); }, throwsException); } else { var apiValue = convert(engineValue); expect(apiValue, equals(expectedResult)); } } else { var apiValue = convert(engineValue); expect((apiValue as dynamic).name, equals(enumName)); } }); } } class MockAnalysisError implements engine.AnalysisError { @override MockSource source; @override engine.ErrorCode errorCode; @override int offset; @override String message; @override String correction; @override int length; @override List contextMessages = []; MockAnalysisError( this.source, this.errorCode, this.offset, this.length, this.message); @override String get correctionMessage => null; @override DiagnosticMessage get problemMessage => null; @override Severity get severity => null; } class MockAnalysisSession implements AnalysisSession { Map fileResults = {}; void addFileResult(FileResult result) { fileResults[result.path] = result; } @override FileResult getFile(String path) => fileResults[path]; @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class MockErrorCode implements engine.ErrorCode { @override engine.ErrorType type; @override engine.ErrorSeverity errorSeverity; @override String name; @override String url; MockErrorCode( {this.type = engine.ErrorType.COMPILE_TIME_ERROR, this.errorSeverity = engine.ErrorSeverity.ERROR, this.name = 'TEST_ERROR', this.url}); @override String get correction { throw StateError('Unexpected invocation of correction'); } @override bool get hasPublishedDocs => false; @override bool get isIgnorable => true; @override bool get isUnresolvedIdentifier => false; @override String get message { throw StateError('Unexpected invocation of message'); } @override String get uniqueName { throw StateError('Unexpected invocation of uniqueName'); } }