// Copyright (c) 2015, 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/util/filenames.dart'; import 'package:async_helper/async_helper.dart'; import 'package:expect/expect.dart'; import 'package:compiler/src/commandline_options.dart'; import 'package:compiler/src/io/source_information.dart'; import 'package:compiler/src/js/js_debug.dart'; import 'package:js_ast/js_ast.dart'; import '../helpers/sourcemap_helper.dart'; typedef CodePointWhiteListFunction WhiteListFunction( String configuration, String file); typedef bool CodePointWhiteListFunction(CodePoint codePoint); CodePointWhiteListFunction emptyWhiteListFunction(String config, String file) { return emptyWhiteList; } bool emptyWhiteList(CodePoint codePoint) => false; main(List arguments) { test(arguments); } void test(List arguments, {WhiteListFunction whiteListFunction: emptyWhiteListFunction}) { Set configurations = new Set(); Map tests = {}; if (!parseArguments(arguments, configurations, tests)) { return; } asyncTest(() async { bool errorsFound = false; for (String file in tests.keys) { print('==$file========================================================='); for (String config in configurations) { List options = TEST_CONFIGURATIONS[config]; print('---$config----------------------------------------------------'); Uri uri = tests[file]; TestResult result = await runTests(config, file, uri, options); if (result.missingCodePointsMap.isNotEmpty) { errorsFound = result.printMissingCodePoints(whiteListFunction(config, file)); } if (result.multipleNodesMap.isNotEmpty) { result.printMultipleNodes(); errorsFound = true; } if (result.multipleOffsetsMap.isNotEmpty) { result.printMultipleOffsets(); errorsFound = true; } } } Expect.isFalse( errorsFound, "Errors found. " "Run the test with a URI option, " "`source_mapping_test_viewer [--out=] [configs] [tests]`, to " "create a html visualization of the missing code points."); }); } bool parseArguments( List arguments, Set configurations, Map tests, {bool measure: false}) { Set extra = arguments.contains('--file') ? new Set() : null; for (String argument in arguments) { if (!parseArgument(argument, configurations, tests, extra)) { return false; } } if (configurations.isEmpty) { configurations.addAll(TEST_CONFIGURATIONS.keys); if (!measure) { configurations.remove('old'); } } if (extra != null) { for (String file in extra) { Uri uri = Uri.base.resolve(nativeToUriPath(file)); tests[uri.pathSegments.last] = uri; } } if (tests.isEmpty) { tests.addAll(TEST_FILES); } if (arguments.contains('--exclude')) { List filesToRemove = new List.from(tests.keys); tests.clear(); tests.addAll(TEST_FILES); filesToRemove.forEach(tests.remove); } return true; } /// Parse [argument] for a valid configuration or test-file option. /// /// On success, the configuration name is added to [configurations] or the /// test-file name is added to [testFiles], and `true` is returned. /// On failure, a message is printed and `false` is returned. /// /// Unmatching arguments are added to [files] is provided. bool parseArgument(String argument, Set configurations, Map tests, Set extra) { if (argument.startsWith('-')) { // Skip options. return true; } else if (TEST_CONFIGURATIONS.containsKey(argument)) { configurations.add(argument); } else if (TEST_FILES.containsKey(argument)) { tests[argument] = TEST_FILES[argument]; } else if (extra != null) { extra.add(argument); } else { print("Unknown configuration or test file '$argument'. " "Must be one of '${TEST_CONFIGURATIONS.keys.join("', '")}' or " "'${TEST_FILES.keys.join("', '")}'."); return false; } return true; } const Map> TEST_CONFIGURATIONS = const { 'kernel': const [], }; final Map TEST_FILES = _computeTestFiles(); Map _computeTestFiles() { Map map = {}; Directory dataDir = new Directory.fromUri( Uri.base.resolve('pkg/compiler/test/sourcemaps/data/')); for (File file in dataDir.listSync()) { Uri uri = file.uri; map[uri.pathSegments.last] = uri; } return map; } Future runTests( String config, String filename, Uri uri, List options, {bool verbose: true}) async { SourceMapProcessor processor = new SourceMapProcessor(uri); SourceMaps sourceMaps = await processor.process( [Flags.useContentSecurityPolicy, Flags.disableInlining]..addAll(options), verbose: verbose); TestResult result = new TestResult(config, filename, processor); for (SourceMapInfo info in sourceMaps.elementSourceMapInfos.values) { if (info.element.library.canonicalUri.scheme == 'dart') continue; result.userInfoList.add(info); Iterable missingCodePoints = info.codePoints.where((c) => c.isMissing); if (missingCodePoints.isNotEmpty) { result.missingCodePointsMap[info] = missingCodePoints; } Map> offsetToLocationsMap = >{}; for (Node node in info.nodeMap.nodes) { info.nodeMap[node] .forEach((int targetOffset, List sourceLocations) { if (sourceLocations.length > 1) { Map> multipleMap = result.multipleNodesMap .putIfAbsent(info, () => >{}); multipleMap[node] = sourceLocations; } else { offsetToLocationsMap .putIfAbsent(targetOffset, () => new Set()) .addAll(sourceLocations); } }); } offsetToLocationsMap .forEach((int targetOffset, Set sourceLocations) { if (sourceLocations.length > 1) { Map> multipleMap = result.multipleOffsetsMap .putIfAbsent(info, () => >{}); multipleMap[targetOffset] = sourceLocations; } }); } return result; } class TestResult { final String config; final String file; final SourceMapProcessor processor; List userInfoList = []; Map> missingCodePointsMap = >{}; /// For each [SourceMapInfo] a map from JS node to multiple source locations /// associated with the node. Map>> multipleNodesMap = >>{}; /// For each [SourceMapInfo] a map from JS offset to multiple source locations /// associated with the offset. Map>> multipleOffsetsMap = >>{}; TestResult(this.config, this.file, this.processor); bool printMissingCodePoints( [CodePointWhiteListFunction codePointWhiteList = emptyWhiteList]) { bool allWhiteListed = true; missingCodePointsMap.forEach((info, missingCodePoints) { print("Missing code points for ${info.element} in '$file' " "in config '$config':"); for (CodePoint codePoint in missingCodePoints) { if (codePointWhiteList(codePoint)) { print(" $codePoint (white-listed)"); } else { print(" $codePoint"); allWhiteListed = false; } } }); return !allWhiteListed; } void printMultipleNodes() { multipleNodesMap.forEach((info, multipleMap) { multipleMap.forEach((node, sourceLocations) { print('Multiple source locations:\n ${sourceLocations.join('\n ')}\n' 'for `${nodeToString(node)}` in ${info.element} in ' '$file.'); }); }); } void printMultipleOffsets() { multipleOffsetsMap.forEach((info, multipleMap) { multipleMap.forEach((targetOffset, sourceLocations) { print('Multiple source locations:\n ${sourceLocations.join('\n ')}\n' 'for offset $targetOffset in ${info.element} in $file.'); }); }); } }