| // 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. |
| |
| /// This tool checks that the output from dart2js meets a given specification, |
| /// given in a YAML file. The format of the YAML file is: |
| /// |
| /// main: |
| /// include: |
| /// - some_package |
| /// - other_package |
| /// |
| /// foo: |
| /// include: |
| /// - foo |
| /// - bar |
| /// |
| /// baz: |
| /// include: |
| /// - baz |
| /// - quux |
| /// exclude: |
| /// - zardoz |
| /// |
| /// The YAML file consists of a list of declarations, one for each deferred |
| /// part expected in the output. At least one of these parts must be named |
| /// "main"; this is the main part that contains the program entrypoint. Each |
| /// top-level part contains a list of package names that are expected to be |
| /// contained in that part, a list of package names that are expected to be in |
| /// another part, or both. For instance, in the example YAML above the part |
| /// named "baz" is expected to contain the packages "baz" and "quux" and not to |
| /// contain the package "zardoz". |
| /// |
| /// The names for parts given in the specification YAML file (besides "main") |
| /// are the same as the name given to the deferred import in the dart file. For |
| /// instance, if you have `import 'package:foo/bar.dart' deferred as baz;` in |
| /// your dart file, then the corresponding name in the specification file is |
| /// 'baz'. |
| library dart2js_info.deferred_library_check; |
| |
| import 'info.dart'; |
| |
| List<ManifestComplianceFailure> checkDeferredLibraryManifest( |
| AllInfo info, Map manifest) { |
| var includedPackages = <String, Set<String>>{}; |
| var excludedPackages = <String, Set<String>>{}; |
| for (var part in manifest.keys) { |
| for (var package in manifest[part]['include'] ?? []) { |
| (includedPackages[part] ??= {}).add(package); |
| } |
| for (var package in manifest[part]['exclude'] ?? []) { |
| (excludedPackages[part] ??= {}).add(package); |
| } |
| } |
| |
| // There are 2 types of parts that are valid to mention in the specification |
| // file. These are the main part and directly imported deferred parts. The |
| // main part is always named 'main'; the directly imported deferred parts are |
| // the outputUnits whose list of 'imports' contains a single import. If the |
| // part is shared, it will have more than one import since it will include the |
| // imports of all the top-level deferred parts that will load the shared part. |
| List<String> validParts = [ |
| 'main', |
| ...info.outputUnits |
| .where((unit) => unit.imports.length == 1) |
| .map((unit) => unit.imports.single) |
| ]; |
| List<String> mentionedParts = [ |
| ...includedPackages.keys, |
| ...excludedPackages.keys |
| ]; |
| var partNameFailures = <_InvalidPartName>[]; |
| for (var part in mentionedParts) { |
| if (!validParts.contains(part)) { |
| partNameFailures.add(_InvalidPartName(part, validParts)); |
| } |
| } |
| if (partNameFailures.isNotEmpty) { |
| return partNameFailures; |
| } |
| |
| var mentionedPackages = { |
| for (var values in includedPackages.values) ...values, |
| for (var values in excludedPackages.values) ...values |
| }; |
| var actualIncludedPackages = <String, Set<String>>{}; |
| |
| var failures = <ManifestComplianceFailure>[]; |
| |
| checkInfo(BasicInfo info) { |
| if (info.size == 0) return; |
| var lib = _getLibraryOf(info); |
| if (lib != null && _isPackageUri(lib.uri)) { |
| var packageName = _getPackageName(lib.uri); |
| if (!mentionedPackages.contains(packageName)) return; |
| var containingParts = <String>[]; |
| if (info.outputUnit.name == 'main') { |
| containingParts.add('main'); |
| } else { |
| containingParts.addAll(info.outputUnit.imports); |
| } |
| for (var part in containingParts) { |
| (actualIncludedPackages[part] ??= {}).add(packageName); |
| if (excludedPackages[part].contains(packageName)) { |
| failures.add(_PartContainedExcludedPackage(part, packageName, info)); |
| } |
| } |
| } |
| } |
| |
| info.functions.forEach(checkInfo); |
| info.fields.forEach(checkInfo); |
| |
| includedPackages.forEach((part, packages) { |
| for (var package in packages) { |
| if (!actualIncludedPackages.containsKey(part) || |
| !actualIncludedPackages[part].contains(package)) { |
| failures.add(_PartDidNotContainPackage(part, package)); |
| } |
| } |
| }); |
| return failures; |
| } |
| |
| LibraryInfo _getLibraryOf(Info info) { |
| var current = info; |
| while (current is! LibraryInfo) { |
| if (current == null) { |
| return null; |
| } |
| current = current.parent; |
| } |
| return current; |
| } |
| |
| bool _isPackageUri(Uri uri) => uri.scheme == 'package'; |
| |
| String _getPackageName(Uri uri) { |
| assert(_isPackageUri(uri)); |
| return uri.pathSegments.first; |
| } |
| |
| class ManifestComplianceFailure { |
| const ManifestComplianceFailure(); |
| } |
| |
| class _InvalidPartName extends ManifestComplianceFailure { |
| final String part; |
| final List<String> validPartNames; |
| const _InvalidPartName(this.part, this.validPartNames); |
| |
| @override |
| String toString() { |
| return 'Manifest file declares invalid part "$part". ' |
| 'Valid part names are: $validPartNames'; |
| } |
| } |
| |
| class _PartContainedExcludedPackage extends ManifestComplianceFailure { |
| final String part; |
| final String package; |
| final BasicInfo info; |
| const _PartContainedExcludedPackage(this.part, this.package, this.info); |
| |
| @override |
| String toString() { |
| return 'Part "$part" was specified to exclude package "$package" but it ' |
| 'actually contains ${kindToString(info.kind)} "${info.name}" which ' |
| 'is from package "$package"'; |
| } |
| } |
| |
| class _PartDidNotContainPackage extends ManifestComplianceFailure { |
| final String part; |
| final String package; |
| const _PartDidNotContainPackage(this.part, this.package); |
| |
| @override |
| String toString() { |
| return 'Part "$part" was specified to include package "$package" but it ' |
| 'does not contain any elements from that package.'; |
| } |
| } |