2019-08-15 10:21:00 +00:00
|
|
|
// Copyright (c) 2019, 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.
|
|
|
|
|
2021-01-18 15:40:21 +00:00
|
|
|
// @dart = 2.9
|
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
import 'dart:io' show Directory, File, FileSystemEntity;
|
2019-08-15 10:21:00 +00:00
|
|
|
|
|
|
|
import 'dart:typed_data' show Uint8List;
|
|
|
|
|
2019-11-01 14:13:42 +00:00
|
|
|
import 'package:_fe_analyzer_shared/src/parser/parser.dart'
|
|
|
|
show FormalParameterKind, MemberKind, Parser;
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-11-01 09:37:57 +00:00
|
|
|
import 'package:_fe_analyzer_shared/src/parser/listener.dart' show Listener;
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-11-01 09:37:57 +00:00
|
|
|
import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-11-01 09:37:57 +00:00
|
|
|
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-11-01 09:37:57 +00:00
|
|
|
import 'package:_fe_analyzer_shared/src/scanner/utf8_bytes_scanner.dart'
|
|
|
|
show Utf8BytesScanner;
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-11-01 09:37:57 +00:00
|
|
|
import 'package:front_end/src/fasta/command_line_reporting.dart'
|
|
|
|
as command_line_reporting;
|
2021-08-25 11:44:53 +00:00
|
|
|
import 'package:front_end/src/fasta/source/diet_parser.dart'
|
|
|
|
show useImplicitCreationExpressionInCfe;
|
2019-08-15 10:21:00 +00:00
|
|
|
|
|
|
|
import 'package:kernel/kernel.dart';
|
|
|
|
|
2020-02-18 10:56:59 +00:00
|
|
|
import 'package:package_config/package_config.dart';
|
2019-08-21 12:20:22 +00:00
|
|
|
|
2019-08-15 10:21:00 +00:00
|
|
|
import 'package:testing/testing.dart'
|
2019-08-21 12:20:22 +00:00
|
|
|
show Chain, ChainContext, Result, Step, TestDescription, runMe;
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2020-09-16 11:40:38 +00:00
|
|
|
import 'testing_utils.dart' show checkEnvironment, getGitFiles;
|
|
|
|
|
2019-08-15 10:21:00 +00:00
|
|
|
main([List<String> arguments = const []]) =>
|
2019-08-27 09:56:53 +00:00
|
|
|
runMe(arguments, createContext, configurationPath: "../testing.json");
|
2019-08-15 10:21:00 +00:00
|
|
|
|
|
|
|
Future<Context> createContext(
|
|
|
|
Chain suite, Map<String, String> environment) async {
|
2020-09-16 11:40:38 +00:00
|
|
|
const Set<String> knownEnvironmentKeys = {"onlyInGit"};
|
|
|
|
checkEnvironment(environment, knownEnvironmentKeys);
|
|
|
|
|
|
|
|
bool onlyInGit = environment["onlyInGit"] != "false";
|
|
|
|
return new Context(onlyInGit: onlyInGit);
|
2019-08-15 10:21:00 +00:00
|
|
|
}
|
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
class LintTestDescription extends TestDescription {
|
|
|
|
final String shortName;
|
|
|
|
final Uri uri;
|
|
|
|
final LintTestCache cache;
|
|
|
|
final LintListener listener;
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
LintTestDescription(this.shortName, this.uri, this.cache, this.listener) {
|
|
|
|
this.listener.description = this;
|
|
|
|
this.listener.uri = uri;
|
2019-08-15 10:21:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String getErrorMessage(int offset, int squigglyLength, String message) {
|
2019-08-21 12:20:22 +00:00
|
|
|
cache.source ??= new Source(cache.lineStarts, cache.rawBytes, uri, uri);
|
|
|
|
Location location = cache.source.getLocation(uri, offset);
|
2019-08-15 10:21:00 +00:00
|
|
|
return command_line_reporting.formatErrorMessage(
|
2019-08-21 12:20:22 +00:00
|
|
|
cache.source.getTextLine(location.line),
|
2019-08-15 10:21:00 +00:00
|
|
|
location,
|
|
|
|
squigglyLength,
|
|
|
|
uri.toString(),
|
|
|
|
message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
class LintTestCache {
|
|
|
|
List<int> rawBytes;
|
|
|
|
List<int> lineStarts;
|
|
|
|
Source source;
|
|
|
|
Token firstToken;
|
2020-02-12 10:07:25 +00:00
|
|
|
PackageConfig packages;
|
2019-08-21 12:20:22 +00:00
|
|
|
}
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
class Context extends ChainContext {
|
2020-09-16 11:40:38 +00:00
|
|
|
final bool onlyInGit;
|
|
|
|
Context({this.onlyInGit});
|
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
final List<Step> steps = const <Step>[
|
|
|
|
const LintStep(),
|
|
|
|
];
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
// Override special handling of negative tests.
|
|
|
|
@override
|
|
|
|
Result processTestResult(
|
|
|
|
TestDescription description, Result result, bool last) {
|
|
|
|
return result;
|
|
|
|
}
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
Stream<LintTestDescription> list(Chain suite) async* {
|
2020-09-16 11:40:38 +00:00
|
|
|
Set<Uri> gitFiles;
|
|
|
|
if (onlyInGit) {
|
2020-09-17 13:09:30 +00:00
|
|
|
gitFiles = await getGitFiles(suite.uri);
|
2020-09-16 11:40:38 +00:00
|
|
|
}
|
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
Directory testRoot = new Directory.fromUri(suite.uri);
|
|
|
|
if (await testRoot.exists()) {
|
|
|
|
Stream<FileSystemEntity> files =
|
|
|
|
testRoot.list(recursive: true, followLinks: false);
|
|
|
|
await for (FileSystemEntity entity in files) {
|
|
|
|
if (entity is! File) continue;
|
|
|
|
String path = entity.uri.path;
|
|
|
|
if (suite.exclude.any((RegExp r) => path.contains(r))) continue;
|
|
|
|
if (suite.pattern.any((RegExp r) => path.contains(r))) {
|
2020-09-16 11:40:38 +00:00
|
|
|
if (onlyInGit && !gitFiles.contains(entity.uri)) continue;
|
2019-08-21 12:20:22 +00:00
|
|
|
Uri root = suite.uri;
|
|
|
|
String baseName = "${entity.uri}".substring("$root".length);
|
|
|
|
baseName = baseName.substring(0, baseName.length - ".dart".length);
|
|
|
|
LintTestCache cache = new LintTestCache();
|
|
|
|
|
|
|
|
yield new LintTestDescription(
|
|
|
|
"$baseName/ExplicitType",
|
|
|
|
entity.uri,
|
|
|
|
cache,
|
|
|
|
new ExplicitTypeLintListener(),
|
|
|
|
);
|
|
|
|
|
|
|
|
yield new LintTestDescription(
|
|
|
|
"$baseName/ImportsTwice",
|
|
|
|
entity.uri,
|
|
|
|
cache,
|
|
|
|
new ImportsTwiceLintListener(),
|
|
|
|
);
|
2019-08-22 08:09:21 +00:00
|
|
|
|
|
|
|
Uri apiUnstableUri =
|
|
|
|
Uri.base.resolve("pkg/front_end/lib/src/api_unstable/");
|
|
|
|
if (!entity.uri.toString().startsWith(apiUnstableUri.toString())) {
|
|
|
|
yield new LintTestDescription(
|
|
|
|
"$baseName/Exports",
|
|
|
|
entity.uri,
|
|
|
|
cache,
|
|
|
|
new ExportsLintListener(),
|
|
|
|
);
|
|
|
|
}
|
2019-08-21 12:20:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw "${suite.uri} isn't a directory";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
class LintStep extends Step<LintTestDescription, LintTestDescription, Context> {
|
|
|
|
const LintStep();
|
|
|
|
|
|
|
|
String get name => "lint";
|
|
|
|
|
|
|
|
Future<Result<LintTestDescription>> run(
|
|
|
|
LintTestDescription description, Context context) async {
|
|
|
|
if (description.cache.rawBytes == null) {
|
|
|
|
File f = new File.fromUri(description.uri);
|
|
|
|
description.cache.rawBytes = f.readAsBytesSync();
|
|
|
|
|
|
|
|
Uint8List bytes = new Uint8List(description.cache.rawBytes.length + 1);
|
|
|
|
bytes.setRange(
|
|
|
|
0, description.cache.rawBytes.length, description.cache.rawBytes);
|
|
|
|
|
|
|
|
Utf8BytesScanner scanner =
|
|
|
|
new Utf8BytesScanner(bytes, includeComments: true);
|
|
|
|
description.cache.firstToken = scanner.tokenize();
|
|
|
|
description.cache.lineStarts = scanner.lineStarts;
|
|
|
|
|
|
|
|
Uri dotPackages = description.uri.resolve(".packages");
|
|
|
|
while (true) {
|
|
|
|
if (new File.fromUri(dotPackages).existsSync()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Stupid bailout.
|
|
|
|
if (dotPackages.pathSegments.length < Uri.base.pathSegments.length) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
dotPackages = dotPackages.resolve("../.packages");
|
|
|
|
}
|
|
|
|
|
|
|
|
File dotPackagesFile = new File.fromUri(dotPackages);
|
|
|
|
if (dotPackagesFile.existsSync()) {
|
2020-02-12 10:07:25 +00:00
|
|
|
description.cache.packages = await loadPackageConfigUri(dotPackages);
|
2019-08-21 12:20:22 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
if (description.cache.firstToken == null) {
|
|
|
|
return crash(description, StackTrace.current);
|
|
|
|
}
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2021-08-25 11:44:53 +00:00
|
|
|
Parser parser = new Parser(description.listener,
|
|
|
|
useImplicitCreationExpression: useImplicitCreationExpressionInCfe);
|
2019-08-21 12:20:22 +00:00
|
|
|
parser.parseUnit(description.cache.firstToken);
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
if (description.listener.problems.isEmpty) {
|
2019-08-15 10:21:00 +00:00
|
|
|
return pass(description);
|
|
|
|
}
|
2019-08-21 12:20:22 +00:00
|
|
|
return fail(description, description.listener.problems.join("\n\n"));
|
2019-08-15 10:21:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class LintListener extends Listener {
|
2020-12-07 16:20:28 +00:00
|
|
|
List<String> problems = <String>[];
|
2019-08-21 12:20:22 +00:00
|
|
|
LintTestDescription description;
|
|
|
|
Uri uri;
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
onProblem(int offset, int squigglyLength, String message) {
|
|
|
|
problems.add(description.getErrorMessage(offset, squigglyLength, message));
|
|
|
|
}
|
|
|
|
}
|
2019-08-15 10:21:00 +00:00
|
|
|
|
2019-08-21 12:20:22 +00:00
|
|
|
class ExplicitTypeLintListener extends LintListener {
|
2020-12-07 16:20:28 +00:00
|
|
|
List<LatestType> _latestTypes = <LatestType>[];
|
2019-08-15 10:21:00 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
void beginVariablesDeclaration(
|
|
|
|
Token token, Token lateToken, Token varFinalOrConst) {
|
2019-11-01 14:13:42 +00:00
|
|
|
if (!_latestTypes.last.type) {
|
2019-08-15 10:21:00 +00:00
|
|
|
onProblem(
|
|
|
|
varFinalOrConst.offset, varFinalOrConst.length, "No explicit type.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void handleType(Token beginToken, Token questionMark) {
|
2019-11-01 14:13:42 +00:00
|
|
|
_latestTypes.add(new LatestType(beginToken, true));
|
2019-08-15 10:21:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void handleNoType(Token lastConsumed) {
|
2019-11-01 14:13:42 +00:00
|
|
|
_latestTypes.add(new LatestType(lastConsumed, false));
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void endFunctionType(Token functionToken, Token questionMark) {
|
|
|
|
_latestTypes.add(new LatestType(functionToken, true));
|
2019-08-15 10:21:00 +00:00
|
|
|
}
|
2019-08-21 12:33:45 +00:00
|
|
|
|
|
|
|
void endTopLevelFields(
|
2020-05-14 18:34:06 +00:00
|
|
|
Token externalToken,
|
2019-08-21 12:33:45 +00:00
|
|
|
Token staticToken,
|
|
|
|
Token covariantToken,
|
|
|
|
Token lateToken,
|
|
|
|
Token varFinalOrConst,
|
|
|
|
int count,
|
|
|
|
Token beginToken,
|
|
|
|
Token endToken) {
|
2019-11-01 14:13:42 +00:00
|
|
|
if (!_latestTypes.last.type) {
|
2019-11-01 11:24:20 +00:00
|
|
|
onProblem(beginToken.offset, beginToken.length, "No explicit type.");
|
2019-08-21 12:33:45 +00:00
|
|
|
}
|
2019-11-01 14:13:42 +00:00
|
|
|
_latestTypes.removeLast();
|
2019-08-21 12:33:45 +00:00
|
|
|
}
|
|
|
|
|
2020-05-14 18:34:06 +00:00
|
|
|
void endClassFields(
|
2020-07-15 22:48:01 +00:00
|
|
|
Token abstractToken,
|
2020-05-14 18:34:06 +00:00
|
|
|
Token externalToken,
|
|
|
|
Token staticToken,
|
|
|
|
Token covariantToken,
|
|
|
|
Token lateToken,
|
|
|
|
Token varFinalOrConst,
|
|
|
|
int count,
|
|
|
|
Token beginToken,
|
|
|
|
Token endToken) {
|
2019-11-01 14:13:42 +00:00
|
|
|
if (!_latestTypes.last.type) {
|
2019-08-21 12:33:45 +00:00
|
|
|
onProblem(
|
|
|
|
varFinalOrConst.offset, varFinalOrConst.length, "No explicit type.");
|
|
|
|
}
|
2019-11-01 14:13:42 +00:00
|
|
|
_latestTypes.removeLast();
|
|
|
|
}
|
|
|
|
|
|
|
|
void endFormalParameter(
|
|
|
|
Token thisKeyword,
|
|
|
|
Token periodAfterThis,
|
|
|
|
Token nameToken,
|
|
|
|
Token initializerStart,
|
|
|
|
Token initializerEnd,
|
|
|
|
FormalParameterKind kind,
|
|
|
|
MemberKind memberKind) {
|
|
|
|
_latestTypes.removeLast();
|
2019-08-21 12:33:45 +00:00
|
|
|
}
|
2019-08-15 10:21:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class LatestType {
|
|
|
|
final Token token;
|
|
|
|
bool type;
|
|
|
|
|
|
|
|
LatestType(this.token, this.type);
|
|
|
|
}
|
2019-08-21 12:20:22 +00:00
|
|
|
|
|
|
|
class ImportsTwiceLintListener extends LintListener {
|
|
|
|
Set<Uri> seenImports = new Set<Uri>();
|
|
|
|
|
|
|
|
void endImport(Token importKeyword, Token semicolon) {
|
|
|
|
Token importUriToken = importKeyword.next;
|
|
|
|
String importUri = importUriToken.lexeme;
|
|
|
|
if (importUri.startsWith("r")) {
|
|
|
|
importUri = importUri.substring(2, importUri.length - 1);
|
|
|
|
} else {
|
|
|
|
importUri = importUri.substring(1, importUri.length - 1);
|
|
|
|
}
|
|
|
|
Uri resolved = uri.resolve(importUri);
|
|
|
|
if (resolved.scheme == "package") {
|
|
|
|
if (description.cache.packages != null) {
|
|
|
|
resolved = description.cache.packages.resolve(resolved);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!seenImports.add(resolved)) {
|
|
|
|
onProblem(importUriToken.offset, importUriToken.lexeme.length,
|
|
|
|
"Uri '$resolved' already imported once.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-22 08:09:21 +00:00
|
|
|
|
|
|
|
class ExportsLintListener extends LintListener {
|
|
|
|
void endExport(Token exportKeyword, Token semicolon) {
|
|
|
|
Token exportUriToken = exportKeyword.next;
|
|
|
|
String exportUri = exportUriToken.lexeme;
|
|
|
|
if (exportUri.startsWith("r")) {
|
|
|
|
exportUri = exportUri.substring(2, exportUri.length - 1);
|
|
|
|
} else {
|
|
|
|
exportUri = exportUri.substring(1, exportUri.length - 1);
|
|
|
|
}
|
|
|
|
Uri resolved = uri.resolve(exportUri);
|
|
|
|
if (resolved.scheme == "package") {
|
|
|
|
if (description.cache.packages != null) {
|
|
|
|
resolved = description.cache.packages.resolve(resolved);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onProblem(exportUriToken.offset, exportUriToken.lexeme.length,
|
|
|
|
"Exports disallowed internally.");
|
|
|
|
}
|
|
|
|
}
|