// 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 'dart:mirrors'; import 'package:analysis_server/src/protocol_server.dart' hide DiagnosticMessage, Enum; import 'package:analysis_server/src/services/search/search_engine.dart'; import 'package:analyzer/dart/element/element.dart' as engine; import 'package:analyzer/error/error.dart' as engine; import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'abstract_context.dart'; import 'constants.dart'; void main() { defineReflectiveSuite(() { defineReflectiveTests(AnalysisErrorTest); defineReflectiveTests(EnumTest); }); } @reflectiveTest class AnalysisErrorTest extends AbstractContextTest { void test_fromEngine_hasContextMessage() async { var content = r''' void f() { print(x); var x = 1; } '''; var normalizedContent = normalizeSource(content); var file = newFile('$testPackageLibPath/foo.dart', content); var result = await getResolvedUnit(file); var engineDiagnostic = result.diagnostics.firstWhere( (d) => d.diagnosticCode == diag.referencedBeforeDeclaration, ); var error = newAnalysisError_fromEngine(result, engineDiagnostic); expect(error.toJson(), { severityKey: 'ERROR', typeKey: 'COMPILE_TIME_ERROR', locationKey: { fileKey: file.path, offsetKey: normalizedContent.indexOf('x'), lengthKey: 1, startLineKey: 2, startColumnKey: 9, endLineKey: 2, endColumnKey: 10, }, messageKey: "Local variable 'x' can't be referenced before it is declared.", correctionKey: "Try moving the declaration to before the first use, or renaming the local variable so that it doesn't hide a name from an enclosing scope.", codeKey: 'referenced_before_declaration', urlKey: 'https://dart.dev/diagnostics/referenced_before_declaration', contextReferencesKey: [ { messageKey: "The declaration of 'x' is here.", locationKey: { fileKey: file.path, offsetKey: normalizedContent.indexOf('x = 1'), lengthKey: 1, startLineKey: 3, startColumnKey: 7, endLineKey: 3, endColumnKey: 8, }, }, ], hasFixKey: false, }); } void test_fromEngine_hasCorrection() async { newFile('$testPackageLibPath/lib1.dart', 'class N {}\n'); newFile('$testPackageLibPath/lib2.dart', 'class N {}\n'); var content = r''' export 'lib1.dart'; export 'lib2.dart'; '''; var normalizedContent = normalizeSource(content); var file = newFile('$testPackageLibPath/foo.dart', content); var result = await getResolvedUnit(file); var engineDiagnostic = result.diagnostics.firstWhere( (d) => d.diagnosticCode == diag.ambiguousExport, ); var error = newAnalysisError_fromEngine(result, engineDiagnostic); expect(error.toJson(), { severityKey: 'ERROR', typeKey: 'COMPILE_TIME_ERROR', locationKey: { fileKey: file.path, offsetKey: normalizedContent.indexOf("'lib2.dart'"), lengthKey: 11, startLineKey: 2, startColumnKey: 8, endLineKey: 2, endColumnKey: 19, }, messageKey: engineDiagnostic.message, correctionKey: 'Try removing the export of one of the libraries, or explicitly hiding the name in one of the export directives.', codeKey: 'ambiguous_export', urlKey: 'https://dart.dev/diagnostics/ambiguous_export', hasFixKey: false, }); } void test_fromEngine_hasUrl() async { var content = r''' void f() { print(x); var x = 1; } '''; var normalizedContent = normalizeSource(content); var file = newFile('$testPackageLibPath/foo.dart', content); var result = await getResolvedUnit(file); var engineDiagnostic = result.diagnostics.firstWhere( (d) => d.diagnosticCode == diag.referencedBeforeDeclaration, ); var error = newAnalysisError_fromEngine(result, engineDiagnostic); expect(error.toJson(), { severityKey: 'ERROR', typeKey: 'COMPILE_TIME_ERROR', locationKey: { fileKey: file.path, offsetKey: normalizedContent.indexOf('x'), lengthKey: 1, startLineKey: 2, startColumnKey: 9, endLineKey: 2, endColumnKey: 10, }, messageKey: "Local variable 'x' can't be referenced before it is declared.", correctionKey: "Try moving the declaration to before the first use, or renaming the local variable so that it doesn't hide a name from an enclosing scope.", codeKey: 'referenced_before_declaration', urlKey: 'https://dart.dev/diagnostics/referenced_before_declaration', contextReferencesKey: [ { messageKey: "The declaration of 'x' is here.", locationKey: { fileKey: file.path, offsetKey: normalizedContent.indexOf('x = 1'), lengthKey: 1, startLineKey: 3, startColumnKey: 7, endLineKey: 3, endColumnKey: 8, }, }, ], hasFixKey: false, }); } void test_fromEngine_lint() async { createAnalysisOptionsFile(lints: ['avoid_print']); var content = r''' void f() { print('hello'); } '''; var normalizedContent = normalizeSource(content); var file = newFile('$testPackageLibPath/foo.dart', content); var result = await getResolvedUnit(file); var engineDiagnostic = result.diagnostics.firstWhere( (d) => d.diagnosticCode.lowerCaseName == 'avoid_print', ); var error = newAnalysisError_fromEngine(result, engineDiagnostic); expect(error.toJson(), { severityKey: 'INFO', typeKey: 'LINT', locationKey: { fileKey: file.path, offsetKey: normalizedContent.indexOf('print'), lengthKey: 5, startLineKey: 2, startColumnKey: 3, endLineKey: 2, endColumnKey: 8, }, messageKey: engineDiagnostic.message, correctionKey: 'Try using a logging framework.', codeKey: 'avoid_print', urlKey: 'https://dart.dev/diagnostics/avoid_print', hasFixKey: false, }); } void test_fromEngine_noCorrection() async { var content = r''' void f() { // TODO: something } '''; var normalizedContent = normalizeSource(content); var file = newFile('$testPackageLibPath/foo.dart', content); var result = await getResolvedUnit(file); var engineDiagnostic = result.diagnostics.firstWhere( (d) => d.diagnosticCode.type == engine.DiagnosticType.TODO, ); var error = newAnalysisError_fromEngine(result, engineDiagnostic); expect(error.toJson(), { severityKey: 'INFO', typeKey: 'TODO', locationKey: { fileKey: file.path, offsetKey: normalizedContent.indexOf('TODO'), lengthKey: 15, startLineKey: 2, startColumnKey: 6, endLineKey: 2, endColumnKey: 21, }, messageKey: engineDiagnostic.message, codeKey: 'todo', hasFixKey: false, }); } } @reflectiveTest class EnumTest { void test_AnalysisErrorSeverity() { EnumTester().run( (engineSeverity) => AnalysisErrorSeverity.values.byName(engineSeverity.name), exceptions: {engine.DiagnosticSeverity.NONE: null}, ); } void test_AnalysisErrorType() { EnumTester().run( (engineErrorType) => AnalysisErrorType.values.byName(engineErrorType.name), ); } void test_ElementKind() { EnumTester().run( convertElementKind, exceptions: { // TODO(paulberry): do any of the exceptions below constitute bugs? engine.ElementKind.AUGMENTATION_IMPORT: ElementKind.UNKNOWN, engine.ElementKind.CLASS_AUGMENTATION: ElementKind.UNKNOWN, 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.LIBRARY_AUGMENTATION: ElementKind.UNKNOWN, engine.ElementKind.NAME: ElementKind.UNKNOWN, engine.ElementKind.NEVER: ElementKind.UNKNOWN, engine.ElementKind.PART: ElementKind.COMPILATION_UNIT, engine.ElementKind.RECORD: 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, exceptions: { MatchKind.REFERENCE_BY_NAMED_ARGUMENT: SearchResultKind.REFERENCE, MatchKind.REFERENCE_IN_PATTERN_FIELD: SearchResultKind.REFERENCE, MatchKind.DOT_SHORTHANDS_CONSTRUCTOR_INVOCATION: SearchResultKind.INVOCATION, MatchKind.INVOCATION_BY_ENUM_CONSTANT_WITHOUT_ARGUMENTS: SearchResultKind.INVOCATION, MatchKind.DOT_SHORTHANDS_CONSTRUCTOR_TEAR_OFF: SearchResultKind.REFERENCE, MatchKind.REFERENCE_BY_CONSTRUCTOR_TEAR_OFF: SearchResultKind.REFERENCE, MatchKind.REFERENCE_IN_EXTENDS_CLAUSE: SearchResultKind.REFERENCE, MatchKind.REFERENCE_IN_IMPLEMENTS_CLAUSE: SearchResultKind.REFERENCE, MatchKind.REFERENCE_IN_WITH_CLAUSE: SearchResultKind.REFERENCE, MatchKind.REFERENCE_IN_ON_CLAUSE: SearchResultKind.REFERENCE, }, ); } } /// 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); }, throwsArgumentError); } else { var apiValue = convert(engineValue); expect(apiValue, equals(expectedResult)); } } else { var apiValue = convert(engineValue); expect((apiValue as Enum).name, equals(enumName)); expect((apiValue as Enum).name, equals(engineValue.toString())); } }); } }