blob: 1efb8863de39167f5b911920d962991ef1d36472 [file] [edit]
// 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<engine.DiagnosticSeverity, AnalysisErrorSeverity>().run(
(engineSeverity) =>
AnalysisErrorSeverity.values.byName(engineSeverity.name),
exceptions: {engine.DiagnosticSeverity.NONE: null},
);
}
void test_AnalysisErrorType() {
EnumTester<engine.DiagnosticType, AnalysisErrorType>().run(
(engineErrorType) =>
AnalysisErrorType.values.byName(engineErrorType.name),
);
}
void test_ElementKind() {
EnumTester<engine.ElementKind, ElementKind>().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<MatchKind, SearchResultKind>().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<EngineEnum, ApiEnum> {
/// 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<EngineEnum, ApiEnum?> 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<EngineEnum>());
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()));
}
});
}
}