// Copyright (c) 2016, 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.7 import 'dart:async'; import 'dart:io'; import 'package:_fe_analyzer_shared/src/testing/annotated_code_helper.dart'; import 'package:async_helper/async_helper.dart'; import 'package:compiler/compiler_new.dart'; import 'package:expect/expect.dart'; import 'package:source_maps/source_maps.dart'; import '../helpers/memory_compiler.dart'; const List TESTS = const [ ''' @{main}main() { @{main}} ''', ''' @{main}main() { @{main}throw ''; @{main}} ''', ''' @{main}main() { @{main}return 0; @{main}} ''', ''' import 'package:expect/expect.dart'; @{main}main() { @{main}test(); @{main}} @pragma('dart2js:noInline') @{test}test() { @{test}} ''', ]; class Test { final String annotatedCode; final String code; final List expectedLocations; Test(this.annotatedCode, this.code, this.expectedLocations); } Test processTestCode(String code) { List expectedLocations = []; AnnotatedCode annotatedCode = new AnnotatedCode.fromText(code); for (Annotation annotation in annotatedCode.annotations) { String methodName = annotation.text; expectedLocations.add( new SourceLocation(methodName, annotation.lineNo, annotation.columnNo)); } return new Test(code, annotatedCode.sourceCode, expectedLocations); } void main(List arguments) { bool verbose = false; bool printJs = false; bool writeJs = false; List indices; for (String arg in arguments) { if (arg == '-v') { verbose = true; } else if (arg == '--print-js') { printJs = true; } else if (arg == '--write-js') { writeJs = true; } else { int index = int.tryParse(arg); if (index != null) { indices ??= []; if (index < 0 || index >= TESTS.length) { print('Index $index out of bounds: [0;${TESTS.length - 1}]'); } else { indices.add(index); } } } } if (indices == null) { indices = new List.generate(TESTS.length, (i) => i); } asyncTest(() async { for (int index in indices) { await runTest(index, processTestCode(TESTS[index]), printJs: printJs, writeJs: writeJs, verbose: verbose); } }); } Future runTest(int index, Test test, {bool printJs: false, bool writeJs, bool verbose: false}) async { print("--$index------------------------------------------------------------"); print("Compiling dart2js\n ${test.annotatedCode}"); OutputCollector collector = new OutputCollector(); List options = ['--out=out.js', '--source-map=out.js.map']; CompilationResult compilationResult = await runCompiler( entryPoint: Uri.parse('memory:main.dart'), memorySourceFiles: {'main.dart': test.code}, outputProvider: collector, options: options, unsafeToTouchSourceFiles: true); Expect.isTrue(compilationResult.isSuccess, "Unsuccessful compilation of test:\n${test.code}"); String sourceMapText = collector.getOutput('', OutputType.sourceMap); SingleMapping sourceMap = parse(sourceMapText); if (writeJs) { new File('out.js') .writeAsStringSync(collector.getOutput('', OutputType.js)); new File('out.js.map').writeAsStringSync(sourceMapText); } Set expectedLocations = test.expectedLocations.toSet(); List actualLocations = []; List extraLocations = []; for (TargetLineEntry targetLineEntry in sourceMap.lines) { for (TargetEntry targetEntry in targetLineEntry.entries) { if (targetEntry.sourceUrlId != null && sourceMap.urls[targetEntry.sourceUrlId] == 'memory:main.dart') { String methodName; if (targetEntry.sourceNameId != null) { methodName = sourceMap.names[targetEntry.sourceNameId]; } SourceLocation location = new SourceLocation(methodName, targetEntry.sourceLine + 1, targetEntry.sourceColumn + 1); actualLocations.add(location); if (!expectedLocations.remove(location)) { extraLocations.add(location); } } } } if (expectedLocations.isNotEmpty) { print('--Missing source locations:---------------------------------------'); AnnotatedCode annotatedCode = new AnnotatedCode(test.code, test.code, []); expectedLocations.forEach((l) => annotatedCode.addAnnotation( l.lineNo, l.columnNo, '/*', l.methodName, '*/')); print(annotatedCode.toText()); print('------------------------------------------------------------------'); Expect.isTrue( expectedLocations.isEmpty, "Missing source locations:\n${test.code}\n" "Actual:\n${actualLocations.join('\n')}\n" "Missing:\n${expectedLocations.join('\n')}\n"); } if (extraLocations.isNotEmpty) { print('--Extra source locations:-----------------------------------------'); AnnotatedCode annotatedCode = new AnnotatedCode(test.code, test.code, []); extraLocations.forEach((l) => annotatedCode.addAnnotation( l.lineNo, l.columnNo, '/*', l.methodName, '*/')); print(annotatedCode.toText()); print('------------------------------------------------------------------'); Expect.isTrue( extraLocations.isEmpty, "Extra source locations:\n${test.code}\n" "Actual:\n${actualLocations.join('\n')}\n" "Extra:\n${extraLocations.join('\n')}\n"); } } class SourceLocation { final String methodName; final int lineNo; final int columnNo; SourceLocation(this.methodName, this.lineNo, this.columnNo); @override int get hashCode => methodName.hashCode * 13 + lineNo.hashCode * 17 + columnNo.hashCode * 19; @override bool operator ==(other) { if (identical(this, other)) return true; if (other is! SourceLocation) return false; return methodName == other.methodName && lineNo == other.lineNo && columnNo == other.columnNo; } @override String toString() => '$methodName:$lineNo:$columnNo'; }