blob: 544345dc48631e67d18e404ddd6b08d6496a9030 [file] [log] [blame]
// Copyright (c) 2021, 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:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:_fe_analyzer_shared/src/parser/parser.dart' show Parser;
import 'package:_fe_analyzer_shared/src/messages/codes.dart' as codes;
import 'package:_fe_analyzer_shared/src/parser/async_modifier.dart'
show AsyncModifier;
import 'package:_fe_analyzer_shared/src/parser/forwarding_listener.dart'
show NullListener;
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
show ScannerConfiguration;
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
import 'package:_fe_analyzer_shared/src/scanner/utf8_bytes_scanner.dart'
show Utf8BytesScanner;
import 'package:front_end/src/api_prototype/compiler_options.dart';
import 'package:front_end/src/api_prototype/file_system.dart';
import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart';
import 'package:front_end/src/api_prototype/memory_file_system.dart';
import 'package:front_end/src/api_prototype/standard_file_system.dart';
import 'package:front_end/src/base/processed_options.dart';
import 'package:front_end/src/fasta/builder/library_builder.dart';
import 'package:front_end/src/fasta/combinator.dart';
import 'package:front_end/src/fasta/command_line_reporting.dart'
as command_line_reporting;
import 'package:front_end/src/fasta/compiler_context.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:front_end/src/fasta/dill/dill_library_builder.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:front_end/src/fasta/dill/dill_target.dart';
import 'package:front_end/src/fasta/fasta_codes.dart';
import 'package:front_end/src/fasta/hybrid_file_system.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:front_end/src/fasta/incremental_compiler.dart';
import 'package:front_end/src/fasta/kernel/utils.dart';
import 'package:front_end/src/fasta/source/diet_parser.dart'
show useImplicitCreationExpressionInCfe;
// ignore: import_of_legacy_library_into_null_safe
import 'package:front_end/src/fasta/source/source_library_builder.dart';
import 'package:front_end/src/fasta/source/source_loader.dart';
import 'package:front_end/src/fasta/uri_translator.dart';
import 'package:kernel/kernel.dart' as kernel
show Combinator, Component, LibraryDependency, Library, Location, Source;
import 'package:kernel/target/targets.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:vm/target/vm.dart';
// ignore: import_of_legacy_library_into_null_safe
import '../test/incremental_suite.dart' show getOptions;
const _portMessageTest = "test";
const _portMessageGood = "good";
const _portMessageBad = "bad";
const _portMessageCrash = "crash";
const _portMessageParseError = "parseError";
const _portMessageDone = "done";
// TODO: This doesn't work on parts... (Well, it might, depending on how
// the part declares what file it's part of and if we've compiled other stuff
// first so we know more stuff).
class DartDocTest {
DocTestIncrementalCompiler? incrementalCompiler;
late CompilerOptions options;
late ProcessedOptions processedOpts;
bool errors = false;
List<String> errorStrings = [];
final FileSystem? underlyingFileSystem;
final bool silent;
DartDocTest({this.underlyingFileSystem, this.silent: false});
FileSystem _getFileSystem() =>
underlyingFileSystem ?? StandardFileSystem.instance;
void _print(Object? object) {
if (!silent) print(object);
}
/// All-in-one. Process a file and return if it was good.
Future<List<TestResult>> process(Uri uri) async {
_print("\n\nProcessing $uri");
Stopwatch stopwatch = new Stopwatch()..start();
// Extract test cases in file.
List<Test> tests = await extractTestsFromUri(uri);
if (tests.isEmpty) {
_print("No tests found in file in ${stopwatch.elapsedMilliseconds} ms.");
return [];
}
_print("Found ${tests.length} test(s) in file "
"in ${stopwatch.elapsedMilliseconds} ms.");
return await compileAndRun(uri, tests);
}
Future<List<Test>> extractTestsFromUri(Uri uri) async {
// Extract test cases in file.
FileSystemEntity file = _getFileSystem().entityForUri(uri);
List<int> rawBytes = await file.readAsBytes();
return extractTests(
rawBytes is Uint8List ? rawBytes : new Uint8List.fromList(rawBytes),
uri);
}
Future<List<TestResult>> compileAndRun(Uri uri, List<Test> tests,
{bool silent: false}) async {
errors = false;
errorStrings.clear();
// Create code to amend the file with.
StringBuffer sb = new StringBuffer();
if (tests.isNotEmpty) {
sb.writeln(
r"Future<void> $dart$doc$test$tester(dynamic dartDocTest) async {");
for (Test test in tests) {
if (test is TestParseError) {
sb.writeln(
"dartDocTest.parseError(\"Parse error @ ${test.position}\");");
} else if (test is ExpectTest) {
sb.writeln("try {");
sb.writeln(" dartDocTest.test(${test.call}, ${test.result});");
sb.writeln("} catch (e) {");
sb.writeln(" dartDocTest.crash(e);");
sb.writeln("}");
} else {
throw "Unknown test type: ${test.runtimeType}";
}
}
sb.writeln("}");
}
if (incrementalCompiler == null) {
setupIncrementalCompiler(uri);
}
processedOpts.inputs.clear();
processedOpts.inputs.add(uri);
HybridFileSystem fileSystem = new HybridFileSystem(
new MemoryFileSystem(new Uri(scheme: "dartdoctest", path: "/")),
_getFileSystem());
options.fileSystem = fileSystem;
processedOpts.clearFileSystemCache();
// Invalidate package uri to force re-finding of packages
// (e.g. if we're now compiling somewhere else).
incrementalCompiler!.invalidate(processedOpts.packagesUri);
Stopwatch stopwatch = new Stopwatch()..start();
IncrementalCompilerResult compilerResult =
await incrementalCompiler!.computeDelta(entryPoints: [uri]);
kernel.Component component = compilerResult.component;
if (errors) {
_print("Got errors in ${stopwatch.elapsedMilliseconds} ms.");
return [
new TestResult(null, TestOutcome.CompilationError)
..message = errorStrings.join("\n")
];
}
_print("Compiled (1) in ${stopwatch.elapsedMilliseconds} ms.");
stopwatch.reset();
await incrementalCompiler!.compileDartDocTestLibrary(
sb.toString(), component.uriToSource[uri]?.importUri ?? uri);
final Uri dartDocMainUri = new Uri(scheme: "dartdoctest", path: "main");
fileSystem.memory
.entityForUri(dartDocMainUri)
.writeAsStringSync(mainFileContent);
incrementalCompiler!.invalidate(dartDocMainUri);
IncrementalCompilerResult compilerMainResult = await incrementalCompiler!
.computeDelta(entryPoints: [dartDocMainUri], fullComponent: true);
kernel.Component componentMain = compilerMainResult.component;
if (errors) {
_print("Got errors in ${stopwatch.elapsedMilliseconds} ms.");
return [
new TestResult(null, TestOutcome.CompilationError)
..message = errorStrings.join("\n")
];
}
_print("Compiled (2) in ${stopwatch.elapsedMilliseconds} ms.");
stopwatch.reset();
Directory tmpDir = Directory.systemTemp.createTempSync();
Uri dillOutUri = tmpDir.uri.resolve("dartdoctestrun.dill");
await writeComponentToFile(componentMain, dillOutUri);
_print("Wrote dill in ${stopwatch.elapsedMilliseconds} ms.");
stopwatch.reset();
// Spawn URI (dill uri) to run tests.
ReceivePort exitPort = new ReceivePort();
ReceivePort errorPort = new ReceivePort();
ReceivePort communicationPort = new ReceivePort();
Completer<dynamic> completer = new Completer();
bool error = false;
exitPort.listen((message) {
exitPort.close();
errorPort.close();
communicationPort.close();
completer.complete();
});
errorPort.listen((message) {
_print("Isolate had an error: $message.");
error = true;
exitPort.close();
errorPort.close();
communicationPort.close();
completer.complete();
});
int testCount = 0;
int goodCount = 0;
int badCount = 0;
int crashCount = 0;
int parseErrorCount = 0;
bool done = false;
Test? currentTest;
List<TestResult> result = [];
communicationPort.listen((message) {
if (done) {
throw "Didn't expect any more messages. Got '$message'";
}
if (message == _portMessageTest) {
currentTest = tests[testCount];
testCount++;
} else if (message == _portMessageGood) {
goodCount++;
result.add(new TestResult(currentTest!, TestOutcome.Pass));
} else if (message.toString().startsWith("$_portMessageBad: ")) {
badCount++;
String strippedMessage =
message.toString().substring("$_portMessageBad: ".length);
result.add(new TestResult(currentTest!, TestOutcome.Failed)
..message = strippedMessage);
_print(strippedMessage);
} else if (message.toString().startsWith("$_portMessageCrash: ")) {
String strippedMessage =
message.toString().substring("$_portMessageCrash: ".length);
result.add(new TestResult(currentTest!, TestOutcome.Crash)
..message = strippedMessage);
crashCount++;
_print(strippedMessage);
} else if (message.toString().startsWith("$_portMessageParseError: ")) {
String strippedMessage =
message.toString().substring("$_portMessageParseError: ".length);
result.add(
new TestResult(currentTest!, TestOutcome.TestCompilationError)
..message = strippedMessage);
parseErrorCount++;
_print(strippedMessage);
} else if (message == _portMessageDone) {
done = true;
// don't complete completer here. Expect the exit port to close.
} else {
throw "Didn't expect '$message'";
}
});
// TODO: Possibly it should be launched in a process instead so we can
// (sort of) catch and report exit calls in test code.
await Isolate.spawnUri(
dillOutUri,
[],
communicationPort.sendPort,
onExit: exitPort.sendPort,
onError: errorPort.sendPort,
);
await completer.future;
tmpDir.deleteSync(recursive: true);
if (error) {
_print("Completed with an error in ${stopwatch.elapsedMilliseconds} ms.");
return [new TestResult(null, TestOutcome.RuntimeError)];
} else if (!done) {
_print(
"Didn't complete correctly in ${stopwatch.elapsedMilliseconds} ms.");
return [new TestResult(null, TestOutcome.FrameworkError)];
} else if (testCount != tests.length) {
_print("Didn't complete with error but ran "
"${testCount} tests while expecting ${tests.length} "
"in ${stopwatch.elapsedMilliseconds} ms.");
return [new TestResult(null, TestOutcome.FrameworkError)];
} else {
_print("Processed $testCount test(s) "
"in ${stopwatch.elapsedMilliseconds} ms.");
if (goodCount == testCount &&
badCount == 0 &&
crashCount == 0 &&
parseErrorCount == 0) {
_print("All tests passed.");
} else {
_print("$goodCount OK; "
"$badCount bad; "
"$crashCount crashed; "
"$parseErrorCount parse errors.");
}
return result;
}
}
void setupIncrementalCompiler(Uri uri) {
options = getOptions();
TargetFlags targetFlags = new TargetFlags(enableNullSafety: true);
// TODO: Target could possible be something else...
Target target = new VmTarget(targetFlags);
options.target = target;
options.omitPlatform = true;
options.onDiagnostic = (DiagnosticMessage message) {
_print(message.plainTextFormatted.first);
if (message.severity == Severity.error) {
errors = true;
for (String errorString in message.plainTextFormatted) {
errorStrings.add(errorString);
}
}
};
processedOpts = new ProcessedOptions(options: options, inputs: [uri]);
CompilerContext compilerContext = new CompilerContext(processedOpts);
this.incrementalCompiler = new DocTestIncrementalCompiler(compilerContext);
}
}
final String mainFileContent = """
// Copyright (c) 2021, 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 "${DocTestIncrementalCompiler.dartDocTestUri}" as tester;
import "dart:isolate";
Future<void> main(List<String> args, SendPort port) async {
DartDocTest test = new DartDocTest(port);
await tester.\$dart\$doc\$test\$tester(test);
port.send("$_portMessageDone");
}
class DartDocTest {
final SendPort port;
DartDocTest(this.port);
void test(dynamic actual, dynamic expected) {
port.send("$_portMessageTest");
if (_testImpl(actual, expected)) {
port.send("$_portMessageGood");
} else {
port.send("$_portMessageBad: Expected '\$expected'; got '\$actual'.");
}
}
void crash(dynamic error) {
port.send("$_portMessageTest");
port.send("$_portMessageCrash: \$error");
}
void parseError(String message) {
port.send("$_portMessageTest");
port.send("$_portMessageParseError: \$message");
}
bool _testImpl(dynamic actual, dynamic expected) {
if (identical(actual, expected)) return true;
if (actual == expected) return true;
if (actual == null || expected == null) return false;
if (actual is List && expected is List) {
if (actual.runtimeType != expected.runtimeType) return false;
if (actual.length != expected.length) return false;
for (int i = 0; i < actual.length; i++) {
if (actual[i] != expected[i]) return false;
}
return true;
}
if (actual is List || expected is List) return false;
if (actual is Map && expected is Map) {
if (actual.runtimeType != expected.runtimeType) return false;
if (actual.length != expected.length) return false;
for (dynamic key in actual.keys) {
if (!expected.containsKey(key)) return false;
if (actual[key] != expected[key]) return false;
}
return true;
}
if (actual is Map || expected is Map) return false;
if (actual is Set && expected is Set) {
if (actual.runtimeType != expected.runtimeType) return false;
if (actual.length != expected.length) return false;
for (dynamic value in actual) {
if (!expected.contains(value)) return false;
}
return true;
}
if (actual is Set || expected is Set) return false;
// More stuff?
return false;
}
}
""";
List<Test> extractTests(Uint8List rawBytes, Uri uriForReporting) {
String rawString = utf8.decode(rawBytes);
List<int> lineStarts = [];
Token firstToken = scanRawBytes(rawBytes, lineStarts: lineStarts);
kernel.Source source =
new kernel.Source(lineStarts, rawBytes, uriForReporting, uriForReporting);
Token token = firstToken;
List<Test> tests = [];
while (true) {
CommentToken? comment = token.precedingComments;
if (comment != null) {
tests.addAll(extractTestsFromComment(comment, rawString, source));
}
if (token.isEof) break;
Token? next = token.next;
if (next == null) break;
token = next;
}
return tests;
}
Token scanRawBytes(Uint8List rawBytes, {List<int>? lineStarts}) {
Uint8List bytes = new Uint8List(rawBytes.length + 1);
bytes.setRange(0, rawBytes.length, rawBytes);
ScannerConfiguration scannerConfiguration = new ScannerConfiguration(
enableExtensionMethods: true,
enableNonNullable: true,
enableTripleShift: true);
Utf8BytesScanner scanner = new Utf8BytesScanner(
bytes,
includeComments: true,
configuration: scannerConfiguration,
languageVersionChanged: (scanner, languageVersion) {
// For now don't do anything, but having it (making it non-null) means the
// configuration won't be reset.
},
);
Token result = scanner.tokenize();
if (lineStarts != null) {
lineStarts.clear();
lineStarts.addAll(scanner.lineStarts);
}
return result;
}
const int $LF = 10;
const int $SPACE = 32;
const int $STAR = 42;
class Test {}
class ExpectTest implements Test {
final String call;
final String result;
ExpectTest(this.call, this.result);
@override
bool operator ==(Object other) {
if (other is! ExpectTest) return false;
if (other.call != call) return false;
if (other.result != result) return false;
return true;
}
@override
String toString() {
return "ExpectTest[$call, $result]";
}
}
class TestParseError implements Test {
final String message;
final int position;
TestParseError(this.message, this.position);
@override
bool operator ==(Object other) {
if (other is! TestParseError) return false;
if (other.message != message) return false;
if (other.position != position) return false;
return true;
}
@override
String toString() {
return "TestParseError[$position, $message]";
}
}
enum TestOutcome {
Pass,
Failed,
Crash,
TestCompilationError,
CompilationError,
RuntimeError,
FrameworkError
}
class TestResult {
final Test? test;
final TestOutcome outcome;
String? message;
TestResult(this.test, this.outcome);
@override
bool operator ==(Object other) {
if (other is! TestResult) return false;
if (other.test != test) return false;
if (other.outcome != outcome) return false;
if (other.message != message) return false;
return true;
}
@override
String toString() {
if (message != null) {
return "TestResult[$outcome, $test, $message]";
}
return "TestResult[$outcome, $test]";
}
}
List<Test> extractTestsFromComment(
CommentToken comment, String rawString, kernel.Source source) {
CommentString commentsData = extractComments(comment, rawString);
final String comments = commentsData.string;
List<Test> result = [];
int index = comments.indexOf("DartDocTest(");
if (index < 0) {
return result;
}
Test scanDartDoc(int scanOffset) {
final Token firstToken =
scanRawBytes(utf8.encode(comments.substring(scanOffset)) as Uint8List);
final ErrorListener listener = new ErrorListener();
final Parser parser = new Parser(listener,
useImplicitCreationExpression: useImplicitCreationExpressionInCfe);
parser.asyncState = AsyncModifier.Async;
final Token pastErrors = parser.skipErrorTokens(firstToken);
assert(pastErrors.isIdentifier);
assert(pastErrors.lexeme == "DartDocTest");
final Token startParen = pastErrors.next!;
assert(identical("(", startParen.stringValue));
// Advance index so we don't parse the same thing again (for error cases).
index = scanOffset + startParen.charEnd;
final Token beforeComma = parser.parseExpression(startParen);
final Token comma = beforeComma.next!;
if (listener.hasErrors) {
StringBuffer sb = new StringBuffer();
int firstPosition = _createParseErrorMessages(
listener, sb, commentsData, scanOffset, source);
return new TestParseError(sb.toString(), firstPosition);
} else if (!identical(",", comma.stringValue)) {
int position = commentsData.charOffset + scanOffset + comma.charOffset;
Message message = codes.templateExpectedButGot.withArguments(',');
return new TestParseError(
_createParseErrorMessage(source, position, comma, comma, message),
position,
);
}
Token beforeEndParen = parser.parseExpression(comma);
Token endParen = beforeEndParen.next!;
if (listener.hasErrors) {
StringBuffer sb = new StringBuffer();
int firstPosition = _createParseErrorMessages(
listener, sb, commentsData, scanOffset, source);
return new TestParseError(sb.toString(), firstPosition);
} else if (!identical(")", endParen.stringValue)) {
int position = commentsData.charOffset + scanOffset + endParen.charOffset;
Message message = codes.templateExpectedButGot.withArguments(')');
return new TestParseError(
_createParseErrorMessage(source, position, comma, comma, message),
position,
);
}
// Advance index so we don't parse the same thing again (success case).
index = scanOffset + endParen.charEnd;
int startPos = scanOffset + startParen.next!.charOffset;
int midEndPos = scanOffset + beforeComma.charEnd;
int midStartPos = scanOffset + comma.next!.charOffset;
int endPos = scanOffset + beforeEndParen.charEnd;
return new ExpectTest(
comments.substring(startPos, midEndPos),
comments.substring(midStartPos, endPos),
);
}
while (index >= 0) {
result.add(scanDartDoc(index));
index = comments.indexOf("DartDocTest(", index);
}
return result;
}
int _createParseErrorMessages(ErrorListener listener, StringBuffer sb,
CommentString commentsData, int scanOffset, kernel.Source source) {
assert(listener.recoverableErrors.isNotEmpty);
sb.writeln("Parse error(s):");
int? firstPosition;
for (RecoverableError recoverableError in listener.recoverableErrors) {
final int position = commentsData.charOffset +
scanOffset +
recoverableError.startToken.charOffset;
firstPosition ??= position;
sb.writeln("");
sb.write(_createParseErrorMessage(
source,
position,
recoverableError.startToken,
recoverableError.endToken,
recoverableError.message,
));
}
return firstPosition!;
}
String _createParseErrorMessage(kernel.Source source, int position,
Token startToken, Token endToken, Message message) {
kernel.Location location = source.getLocation(source.importUri!, position);
return command_line_reporting.formatErrorMessage(
source.getTextLine(location.line),
location,
endToken.charEnd - startToken.charOffset,
source.importUri!.toString(),
message.problemMessage);
}
CommentString extractComments(CommentToken comment, String rawString) {
List<int> fileCodeUnits = rawString.codeUnits;
final int charOffset = comment.charOffset;
int expectedCharOffset = charOffset;
StringBuffer sb = new StringBuffer();
CommentToken? commentToken = comment;
bool commentBlock = false;
bool commentBlockStar = false;
while (commentToken != null) {
if (expectedCharOffset != commentToken.charOffset) {
// Missing spaces/linebreaks.
assert(expectedCharOffset < commentToken.offset);
for (int i = expectedCharOffset; i < commentToken.offset; i++) {
if (fileCodeUnits[i] == $LF) {
sb.writeCharCode($LF);
} else {
sb.write(" ");
}
}
}
expectedCharOffset = commentToken.charEnd;
String data = commentToken.lexeme;
if (!commentBlock) {
if (data.startsWith("///")) {
data = data.substring(3);
sb.write(" ");
} else if (data.startsWith("//")) {
data = data.substring(2);
sb.write(" ");
} else if (data.startsWith("/**")) {
data = data.substring(3);
commentBlock = true;
commentBlockStar = true;
sb.write(" ");
} else if (data.startsWith("/*")) {
data = data.substring(2);
commentBlock = true;
sb.write(" ");
}
}
if (commentBlock && data.endsWith("*/")) {
// Remove ending "*/"" as well as "starting" "*" if in a "/**" block.
List<int> codeUnits = data.codeUnits;
bool sawNewlineLast = false;
for (int i = 0; i < codeUnits.length - 2; i++) {
int codeUnit = codeUnits[i];
if (codeUnit == $LF) {
sb.writeCharCode($LF);
sawNewlineLast = true;
} else if (codeUnit <= $SPACE) {
sb.writeCharCode($SPACE);
} else if (commentBlockStar && sawNewlineLast && codeUnit == $STAR) {
sb.writeCharCode($SPACE);
sawNewlineLast = false;
} else {
sawNewlineLast = false;
sb.writeCharCode(codeUnit);
}
}
sb.write(" ");
commentBlock = false;
commentBlockStar = false;
} else {
sb.write(data);
}
Token? next = commentToken.next;
commentToken = null;
if (next is CommentToken) commentToken = next;
}
return new CommentString(sb.toString(), charOffset);
}
class CommentString {
final String string;
final int charOffset;
CommentString(this.string, this.charOffset);
@override
bool operator ==(Object other) {
if (other is! CommentString) return false;
if (other.string != string) return false;
if (other.charOffset != charOffset) return false;
return true;
}
@override
String toString() {
return "CommentString[$charOffset, $string]";
}
}
class ErrorListener extends NullListener {
List<RecoverableError> recoverableErrors = [];
@override
void handleRecoverableError(
Message message, Token startToken, Token endToken) {
super.handleRecoverableError(message, startToken, endToken);
recoverableErrors.add(new RecoverableError(message, startToken, endToken));
}
}
class RecoverableError {
final Message message;
final Token startToken;
final Token endToken;
RecoverableError(this.message, this.startToken, this.endToken);
}
class DocTestIncrementalCompiler extends IncrementalCompiler {
static final Uri dartDocTestUri =
new Uri(scheme: "dartdoctest", path: "tester");
DocTestIncrementalCompiler(CompilerContext context) : super(context);
@override
bool dontReissueLibraryProblemsFor(Uri? uri) {
return super.dontReissueLibraryProblemsFor(uri) || uri == dartDocTestUri;
}
@override
IncrementalKernelTarget createIncrementalKernelTarget(
FileSystem fileSystem,
bool includeComments,
DillTarget dillTarget,
UriTranslator uriTranslator) {
return new DocTestIncrementalKernelTarget(
this, fileSystem, includeComments, dillTarget, uriTranslator);
}
LibraryBuilder? _dartDocTestLibraryBuilder;
String? _dartDocTestCode;
Future<kernel.Component> compileDartDocTestLibrary(
String dartDocTestCode, Uri libraryUri) async {
assert(dillTargetForTesting != null && kernelTargetForTesting != null);
return await context.runInContext((_) async {
LibraryBuilder libraryBuilder =
kernelTargetForTesting!.loader.readAsEntryPoint(libraryUri);
kernelTargetForTesting!.loader.resetSeenMessages();
_dartDocTestLibraryBuilder = libraryBuilder;
_dartDocTestCode = dartDocTestCode;
invalidate(dartDocTestUri);
IncrementalCompilerResult compilerResult = await computeDelta(
entryPoints: [dartDocTestUri], fullComponent: true);
kernel.Component result = compilerResult.component;
_dartDocTestLibraryBuilder = null;
_dartDocTestCode = null;
kernelTargetForTesting!.uriToSource.remove(dartDocTestUri);
kernelTargetForTesting!.loader.sourceBytes.remove(dartDocTestUri);
return result;
});
}
SourceLibraryBuilder createDartDocTestLibrary(
SourceLoader loader, LibraryBuilder libraryBuilder) {
SourceLibraryBuilder dartDocTestLibrary = new SourceLibraryBuilder(
importUri: dartDocTestUri,
fileUri: dartDocTestUri,
packageLanguageVersion:
new ImplicitLanguageVersion(libraryBuilder.library.languageVersion),
loader: loader,
// TODO(jensj): Should probably set up scopes the same was as it's done
// (now) for expression compilation.
scope: libraryBuilder.scope.createNestedScope("dartdoctest"),
nameOrigin: libraryBuilder,
isUnsupported: false,
isAugmentation: false,
);
if (libraryBuilder is DillLibraryBuilder) {
for (kernel.LibraryDependency dependency
in libraryBuilder.library.dependencies) {
if (!dependency.isImport) continue;
List<CombinatorBuilder>? combinators;
for (kernel.Combinator combinator in dependency.combinators) {
combinators ??= <CombinatorBuilder>[];
combinators.add(combinator.isShow
? new CombinatorBuilder.show(combinator.names,
combinator.fileOffset, libraryBuilder.fileUri)
: new CombinatorBuilder.hide(combinator.names,
combinator.fileOffset, libraryBuilder.fileUri));
}
dartDocTestLibrary.addImport(
metadata: null,
isAugmentationImport: false,
uri: dependency.importedLibraryReference.asLibrary.importUri
.toString(),
configurations: null,
prefix: dependency.name,
combinators: combinators,
deferred: dependency.isDeferred,
charOffset: -1,
prefixCharOffset: -1,
uriOffset: -1,
importIndex: -1);
}
dartDocTestLibrary.addImport(
metadata: null,
isAugmentationImport: false,
uri: libraryBuilder.importUri.toString(),
configurations: null,
prefix: null,
combinators: null,
deferred: false,
charOffset: -1,
prefixCharOffset: -1,
uriOffset: -1,
importIndex: -1);
dartDocTestLibrary.addImportsToScope();
} else {
throw "Got ${libraryBuilder.runtimeType}";
}
return dartDocTestLibrary;
}
}
class DocTestIncrementalKernelTarget extends IncrementalKernelTarget {
final DocTestIncrementalCompiler compiler;
DocTestIncrementalKernelTarget(this.compiler, FileSystem fileSystem,
bool includeComments, DillTarget dillTarget, UriTranslator uriTranslator)
: super(fileSystem, includeComments, dillTarget, uriTranslator);
@override
SourceLoader createLoader() {
return new DocTestSourceLoader(compiler, fileSystem, includeComments, this);
}
}
class DocTestSourceLoader extends SourceLoader {
final DocTestIncrementalCompiler compiler;
DocTestSourceLoader(this.compiler, FileSystem fileSystem,
bool includeComments, DocTestIncrementalKernelTarget target)
: super(fileSystem, includeComments, target);
@override
SourceLibraryBuilder createLibraryBuilder(
{required Uri importUri,
required Uri fileUri,
Uri? packageUri,
required LanguageVersion packageLanguageVersion,
SourceLibraryBuilder? origin,
kernel.Library? referencesFrom,
bool? referenceIsPartOwner,
bool isAugmentation: false}) {
if (importUri == DocTestIncrementalCompiler.dartDocTestUri) {
HybridFileSystem hfs = target.fileSystem as HybridFileSystem;
MemoryFileSystem fs = hfs.memory;
fs
.entityForUri(DocTestIncrementalCompiler.dartDocTestUri)
.writeAsStringSync(compiler._dartDocTestCode!);
return compiler.createDartDocTestLibrary(
this, compiler._dartDocTestLibraryBuilder!);
}
return super.createLibraryBuilder(
importUri: importUri,
fileUri: fileUri,
packageUri: packageUri,
packageLanguageVersion: packageLanguageVersion,
origin: origin,
referencesFrom: referencesFrom,
referenceIsPartOwner: referenceIsPartOwner,
isAugmentation: isAugmentation);
}
}