blob: 08f036e8cf5cf759255d61714440564bafe3b058 [file] [log] [blame]
// Copyright (c) 2020, 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.md file.
import 'dart:convert';
import 'dart:io';
import 'package:front_end/src/fasta/util/direct_parser_ast.dart';
import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
late Uri base;
void main(List<String> args) {
File script = new File.fromUri(Platform.script);
base = script.parent.uri;
testTopLevelStuff();
testClassStuff();
testMixinStuff();
if (!args.contains("--fast")) {
canParseTopLevelIshOfAllFrontendFiles();
}
}
void canParseTopLevelIshOfAllFrontendFiles() {
Directory directory = new Directory.fromUri(base.resolve("../../../"));
int processed = 0;
int errors = 0;
for (FileSystemEntity entry in directory.listSync(recursive: true)) {
if (entry is File) {
if (!entry.path.endsWith(".dart")) continue;
try {
processed++;
List<int> data = entry.readAsBytesSync();
DirectParserASTContentCompilationUnitEnd ast = getAST(data,
includeBody: true,
includeComments: true,
enableExtensionMethods: true,
enableNonNullable: false);
splitIntoChunks(ast, data);
for (DirectParserASTContent child in ast.children!) {
if (child.isClass()) {
splitIntoChunks(
child.asClass().getClassOrMixinOrExtensionBody(), data);
} else if (child.isMixinDeclaration()) {
splitIntoChunks(
child.asMixinDeclaration().getClassOrMixinOrExtensionBody(),
data);
}
}
} catch (e, st) {
print("Failure on $entry:\n$e\n\n$st\n\n--------------\n\n");
errors++;
}
}
}
print("Processed $processed files in $directory. "
"Encountered $errors errors.");
}
void testTopLevelStuff() {
File file = new File.fromUri(
base.resolve("direct_parser_ast_test_data/top_level_stuff.txt"));
List<int> data = file.readAsBytesSync();
DirectParserASTContentCompilationUnitEnd ast = getAST(data,
includeBody: true,
includeComments: true,
enableExtensionMethods: true,
enableNonNullable: false);
expect(2, ast.getImports().length);
expect(2, ast.getExports().length);
List<String> foundChunks = splitIntoChunks(ast, data);
expect(22, foundChunks.length);
expect("library top_level_stuff;", foundChunks[0]);
expect('import "top_level_stuff_helper.dart";', foundChunks[1]);
expect('export "top_level_stuff_helper.dart";', foundChunks[2]);
expect(
'import "top_level_stuff_helper.dart" show a, b, '
'c hide d, e, f show foo;',
foundChunks[3]);
expect(
'export "top_level_stuff_helper.dart" show a, b, '
'c hide d, e, f show foo;',
foundChunks[4]);
expect("part 'top_level_stuff_helper.dart';", foundChunks[5]);
expect('@metadataOneOnThisOne("bla")\n', foundChunks[6]);
expect("@metadataTwoOnThisOne\n", foundChunks[7]);
expect("""void toplevelMethod() {
// no content
}""", foundChunks[8]);
expect("""List<E> anotherTopLevelMethod<E>() {
return null;
}""", foundChunks[9]);
expect("enum FooEnum { A, B, Bla }", foundChunks[10]);
expect("""class FooClass {
// no content.
}""", foundChunks[11]);
expect("""mixin FooMixin {
// no content.
}""", foundChunks[12]);
expect("""class A<T> {
// no content.
}""", foundChunks[13]);
expect("typedef B = Function();", foundChunks[14]);
expect("""mixin C<T> on A<T> {
// no content.
}""", foundChunks[15]);
expect("""extension D<T> on A<T> {
// no content.
}""", foundChunks[16]);
expect("class E = A with FooClass;", foundChunks[17]);
expect("int field1;", foundChunks[18]);
expect("int field2, field3;", foundChunks[19]);
expect("int field4 = 42;", foundChunks[20]);
expect("@AnnotationAtEOF", foundChunks[21]);
file = new File.fromUri(
base.resolve("direct_parser_ast_test_data/top_level_stuff_helper.txt"));
data = file.readAsBytesSync();
ast = getAST(data,
includeBody: true,
includeComments: true,
enableExtensionMethods: true,
enableNonNullable: false);
foundChunks = splitIntoChunks(ast, data);
expect(1, foundChunks.length);
expect("part of 'top_level_stuff.txt';", foundChunks[0]);
file = new File.fromUri(
base.resolve("direct_parser_ast_test_data/script_handle.txt"));
data = file.readAsBytesSync();
ast = getAST(data,
includeBody: true,
includeComments: true,
enableExtensionMethods: true,
enableNonNullable: false);
foundChunks = splitIntoChunks(ast, data);
expect(1, foundChunks.length);
expect("#!/usr/bin/env dart -c", foundChunks[0]);
}
void testClassStuff() {
File file =
new File.fromUri(base.resolve("direct_parser_ast_test_data/class.txt"));
List<int> data = file.readAsBytesSync();
DirectParserASTContentCompilationUnitEnd ast = getAST(data,
includeBody: true,
includeComments: true,
enableExtensionMethods: true,
enableNonNullable: false);
List<DirectParserASTContentTopLevelDeclarationEnd> classes = ast.getClasses();
expect(2, classes.length);
DirectParserASTContentTopLevelDeclarationEnd decl = classes[0];
DirectParserASTContentClassDeclarationEnd cls = decl.asClass();
expect("Foo", decl.getIdentifier().token.lexeme);
DirectParserASTContentClassExtendsHandle extendsDecl = cls.getClassExtends();
expect("extends", extendsDecl.extendsKeyword?.lexeme);
DirectParserASTContentClassOrMixinImplementsHandle implementsDecl =
cls.getClassImplements();
expect("implements", implementsDecl.implementsKeyword?.lexeme);
DirectParserASTContentClassWithClauseHandle? withClauseDecl =
cls.getClassWithClause();
expect(null, withClauseDecl);
List<DirectParserASTContentMemberEnd> members =
cls.getClassOrMixinOrExtensionBody().getMembers();
expect(5, members.length);
expect(members[0].isClassConstructor(), true);
expect(members[1].isClassFactoryMethod(), true);
expect(members[2].isClassMethod(), true);
expect(members[3].isClassMethod(), true);
expect(members[4].isClassFields(), true);
List<String> chunks =
splitIntoChunks(cls.getClassOrMixinOrExtensionBody(), data);
expect(5, chunks.length);
expect("""Foo() {
// Constructor
}""", chunks[0]);
expect("factory Foo.factory() => Foo();", chunks[1]);
expect("""void method() {
// instance method.
}""", chunks[2]);
expect("""static void staticMethod() {
// static method.
}""", chunks[3]);
expect("int field1, field2 = 42;", chunks[4]);
chunks = processItem(
members[0].getClassConstructor().getBlockFunctionBody()!, data);
expect(1, chunks.length);
expect("""{
// Constructor
}""", chunks[0]);
chunks =
processItem(members[2].getClassMethod().getBlockFunctionBody()!, data);
expect(1, chunks.length);
expect("""{
// instance method.
}""", chunks[0]);
chunks =
processItem(members[3].getClassMethod().getBlockFunctionBody()!, data);
expect(1, chunks.length);
expect("""{
// static method.
}""", chunks[0]);
// TODO: Move (something like) this into the check-all-files-thing.
for (DirectParserASTContentMemberEnd member
in cls.getClassOrMixinOrExtensionBody().getMembers()) {
if (member.isClassConstructor()) continue;
if (member.isClassFactoryMethod()) continue;
if (member.isClassFields()) continue;
if (member.isClassMethod()) continue;
throw "$member --- ${member.children}";
}
decl = classes[1];
cls = decl.asClass();
expect("Foo2", decl.getIdentifier().token.lexeme);
extendsDecl = cls.getClassExtends();
expect(null, extendsDecl.extendsKeyword?.lexeme);
implementsDecl = cls.getClassImplements();
expect(null, implementsDecl.implementsKeyword?.lexeme);
withClauseDecl = cls.getClassWithClause();
expect("with", withClauseDecl!.withKeyword.lexeme);
members = cls.getClassOrMixinOrExtensionBody().getMembers();
expect(0, members.length);
}
void testMixinStuff() {
File file =
new File.fromUri(base.resolve("direct_parser_ast_test_data/mixin.txt"));
List<int> data = file.readAsBytesSync();
DirectParserASTContentCompilationUnitEnd ast = getAST(data,
includeBody: true,
includeComments: true,
enableExtensionMethods: true,
enableNonNullable: false);
List<DirectParserASTContentTopLevelDeclarationEnd> mixins =
ast.getMixinDeclarations();
expect(mixins.length, 1);
DirectParserASTContentTopLevelDeclarationEnd decl = mixins[0];
DirectParserASTContentMixinDeclarationEnd mxn = decl.asMixinDeclaration();
expect("B", decl.getIdentifier().token.lexeme);
List<DirectParserASTContentMemberEnd> members =
mxn.getClassOrMixinOrExtensionBody().getMembers();
expect(4, members.length);
expect(members[0].isMixinFields(), true);
expect(members[1].isMixinMethod(), true);
expect(members[2].isMixinFactoryMethod(), true);
expect(members[3].isMixinConstructor(), true);
List<String> chunks =
splitIntoChunks(mxn.getClassOrMixinOrExtensionBody(), data);
expect(4, chunks.length);
expect("static int staticField = 0;", chunks[0]);
expect("""void foo() {
// empty
}""", chunks[1]);
expect("""factory B() {
// empty
}""", chunks[2]);
expect("""B.foo() {
// empty
}""", chunks[3]);
}
void expect<E>(E expect, E actual) {
if (expect != actual) throw "Expected '$expect' but got '$actual'";
}
List<String> splitIntoChunks(DirectParserASTContent ast, List<int> data) {
List<String> foundChunks = [];
for (DirectParserASTContent child in ast.children!) {
foundChunks.addAll(processItem(child, data));
}
return foundChunks;
}
List<String> processItem(DirectParserASTContent item, List<int> data) {
if (item.isClass()) {
DirectParserASTContentClassDeclarationEnd cls = item.asClass();
return [
getCutContent(data, cls.beginToken.offset,
cls.endToken.offset + cls.endToken.length)
];
} else if (item.isMetadata()) {
DirectParserASTContentMetadataStarEnd metadataStar = item.asMetadata();
List<DirectParserASTContentMetadataEnd> entries =
metadataStar.getMetadataEntries();
if (entries.isNotEmpty) {
List<String> chunks = [];
for (DirectParserASTContentMetadataEnd metadata in entries) {
chunks.add(getCutContent(
data, metadata.beginToken.offset, metadata.endToken.offset));
}
return chunks;
}
return const [];
} else if (item.isImport()) {
DirectParserASTContentImportEnd import = item.asImport();
return [
getCutContent(data, import.importKeyword.offset,
import.semicolon!.offset + import.semicolon!.length)
];
} else if (item.isExport()) {
DirectParserASTContentExportEnd export = item.asExport();
return [
getCutContent(data, export.exportKeyword.offset,
export.semicolon.offset + export.semicolon.length)
];
} else if (item.isLibraryName()) {
DirectParserASTContentLibraryNameEnd name = item.asLibraryName();
return [
getCutContent(data, name.libraryKeyword.offset,
name.semicolon.offset + name.semicolon.length)
];
} else if (item.isPart()) {
DirectParserASTContentPartEnd part = item.asPart();
return [
getCutContent(data, part.partKeyword.offset,
part.semicolon.offset + part.semicolon.length)
];
} else if (item.isPartOf()) {
DirectParserASTContentPartOfEnd partOf = item.asPartOf();
return [
getCutContent(data, partOf.partKeyword.offset,
partOf.semicolon.offset + partOf.semicolon.length)
];
} else if (item.isTopLevelMethod()) {
DirectParserASTContentTopLevelMethodEnd method = item.asTopLevelMethod();
return [
getCutContent(data, method.beginToken.offset,
method.endToken.offset + method.endToken.length)
];
} else if (item.isTopLevelFields()) {
DirectParserASTContentTopLevelFieldsEnd fields = item.asTopLevelFields();
return [
getCutContent(data, fields.beginToken.offset,
fields.endToken.offset + fields.endToken.length)
];
} else if (item.isEnum()) {
DirectParserASTContentEnumEnd declaration = item.asEnum();
return [
getCutContent(
data,
declaration.enumKeyword.offset,
declaration.leftBrace.endGroup!.offset +
declaration.leftBrace.endGroup!.length)
];
} else if (item.isMixinDeclaration()) {
DirectParserASTContentMixinDeclarationEnd mixinDecl =
item.asMixinDeclaration();
return [
getCutContent(data, mixinDecl.mixinKeyword.offset,
mixinDecl.endToken.offset + mixinDecl.endToken.length)
];
} else if (item.isNamedMixinDeclaration()) {
DirectParserASTContentNamedMixinApplicationEnd namedMixinDecl =
item.asNamedMixinDeclaration();
return [
getCutContent(data, namedMixinDecl.begin.offset,
namedMixinDecl.endToken.offset + namedMixinDecl.endToken.length)
];
} else if (item.isTypedef()) {
DirectParserASTContentTypedefEnd typedefDecl = item.asTypedef();
return [
getCutContent(data, typedefDecl.typedefKeyword.offset,
typedefDecl.endToken.offset + typedefDecl.endToken.length)
];
} else if (item.isExtension()) {
DirectParserASTContentExtensionDeclarationEnd extensionDecl =
item.asExtension();
return [
getCutContent(data, extensionDecl.extensionKeyword.offset,
extensionDecl.endToken.offset + extensionDecl.endToken.length)
];
} else if (item.isScript()) {
DirectParserASTContentScriptHandle script = item.asScript();
return [
getCutContent(
data, script.token.offset, script.token.offset + script.token.length)
];
} else if (item is DirectParserASTContentMemberEnd) {
if (item.isClassConstructor()) {
DirectParserASTContentClassConstructorEnd decl =
item.getClassConstructor();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else if (item.isClassFactoryMethod()) {
DirectParserASTContentClassFactoryMethodEnd decl =
item.getClassFactoryMethod();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else if (item.isClassMethod()) {
DirectParserASTContentClassMethodEnd decl = item.getClassMethod();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else if (item.isClassFields()) {
DirectParserASTContentClassFieldsEnd decl = item.getClassFields();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else if (item.isClassFields()) {
DirectParserASTContentClassFieldsEnd decl = item.getClassFields();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else if (item.isMixinFields()) {
DirectParserASTContentMixinFieldsEnd decl = item.getMixinFields();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else if (item.isMixinMethod()) {
DirectParserASTContentMixinMethodEnd decl = item.getMixinMethod();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else if (item.isMixinFactoryMethod()) {
DirectParserASTContentMixinFactoryMethodEnd decl =
item.getMixinFactoryMethod();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else if (item.isMixinConstructor()) {
DirectParserASTContentMixinConstructorEnd decl =
item.getMixinConstructor();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else {
if (item.type == DirectParserASTType.BEGIN) return const [];
if (item.type == DirectParserASTType.HANDLE) return const [];
if (item.isClassRecoverableError()) return const [];
if (item.isRecoverableError()) return const [];
if (item.isRecoverImport()) return const [];
throw "Unknown: $item --- ${item.children}";
}
} else if (item.isFunctionBody()) {
DirectParserASTContentBlockFunctionBodyEnd decl = item.asFunctionBody();
return [
getCutContent(data, decl.beginToken.offset,
decl.endToken.offset + decl.endToken.length)
];
} else {
if (item.type == DirectParserASTType.BEGIN) return const [];
if (item.type == DirectParserASTType.HANDLE) return const [];
if (item.isInvalidTopLevelDeclaration()) return const [];
if (item.isRecoverableError()) return const [];
if (item.isRecoverImport()) return const [];
throw "Unknown: $item --- ${item.children}";
}
}
List<int>? _contentCache;
String? _contentCacheString;
String getCutContent(List<int> content, int from, int to) {
if (identical(content, _contentCache)) {
// cache up to date.
} else {
_contentCache = content;
_contentCacheString = utf8.decode(content);
}
return _contentCacheString!.substring(from, to);
}