| // Copyright (c) 2022, 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:collection'; |
| import 'dart:io'; |
| |
| import 'package:_js_interop_checks/src/js_interop.dart'; |
| import 'package:front_end/src/api_unstable/dart2js.dart'; |
| import 'package:kernel/kernel.dart'; |
| |
| // Computes string maps that encode information about the bindings between the |
| // Dart types/members and their native JS equivalents. |
| void main(List<String> args) { |
| if (args.length != 1) { |
| print('usage: ${Platform.script} \$OUTPUT_FILE'); |
| return; |
| } |
| var component = loadComponentFromBinary(computePlatformBinariesLocation() |
| .resolve('dart2js_platform.dill') |
| .toFilePath()); |
| |
| // We use `SplayTreeMap`s and `SplayTreeSet`s to retain the sorted order. This |
| // is to ensure a deterministic order for the output file. |
| final SplayTreeMap<String, SplayTreeMap<String, SplayTreeSet<String>>> |
| nativeTypeToDartMembers = |
| SplayTreeMap<String, SplayTreeMap<String, SplayTreeSet<String>>>(); |
| final SplayTreeMap<String, SplayTreeMap<String, String>> |
| dartTypeToNativeMembers = |
| SplayTreeMap<String, SplayTreeMap<String, String>>(); |
| final SplayTreeMap<String, SplayTreeSet<String>> dartTypeToNativeTypes = |
| SplayTreeMap<String, SplayTreeSet<String>>(); |
| |
| const Set<String> webLibraries = { |
| 'dart:html', |
| 'dart:indexed_db', |
| 'dart:svg', |
| 'dart:web_audio', |
| 'dart:web_gl' |
| }; |
| |
| const Set<String> duplicateClassNames = { |
| 'ImageElement', |
| 'ScriptElement', |
| 'StyleElement', |
| 'TitleElement' |
| }; |
| |
| for (var library in component.libraries) { |
| if (webLibraries.contains(library.importUri.toString())) { |
| for (var cls in library.classes) { |
| if (cls.isMixinApplication) continue; |
| // All strings in the maps are annotated with quotes, so that we print |
| // proper Dart code when we print the maps. |
| var clsName = "'${cls.name}'"; |
| var nativeTypes = getNativeNames(cls).map((name) => "'$name'").toList(); |
| if (nativeTypes.isEmpty) nativeTypes = [clsName]; |
| // There are a couple of cases where there are two classes with the same |
| // name. They are all element classes bound to an `HTML` and an `SVG` |
| // version. For now, ignore the `SVG` version, as they're unused in |
| // google3 and most of them are marked unstable, and their `HTML` |
| // variants are much more common. |
| // TODO(srujzs): Remove this if we decide to deprecate these classes. |
| if (duplicateClassNames.contains(cls.name)) { |
| if (nativeTypes.length == 1 && nativeTypes[0] == "'SVG${cls.name}'") { |
| continue; |
| } |
| } |
| assert(!dartTypeToNativeTypes.containsKey(clsName)); |
| dartTypeToNativeTypes[clsName] = SplayTreeSet.from(nativeTypes); |
| |
| var nativePropToDartProp = SplayTreeMap<String, SplayTreeSet<String>>(); |
| var dartPropToNativeProp = SplayTreeMap<String, String>(); |
| |
| for (var member in [ |
| ...cls.fields, |
| // Ignore synthetic members. |
| ...cls.procedures.where((procedure) => !procedure.isSynthetic), |
| ...cls.constructors.where((constructor) => !constructor.isSynthetic), |
| ]) { |
| // Only record external members as they map to the native names. |
| if (!member.isExternal) continue; |
| // Private members can't be accessed. |
| if (member.name.isPrivate) continue; |
| var dartMember = "'${member.name}'"; |
| var nativeMember = "'${_getJSNameValue(member) ?? member.name.text}'"; |
| |
| // Multiple `dart:html` members may be bound to the same native |
| // symbol. |
| nativePropToDartProp |
| .putIfAbsent(nativeMember, () => SplayTreeSet<String>()) |
| .add(dartMember); |
| for (var nativeType in nativeTypes) { |
| nativeTypeToDartMembers[nativeType] = nativePropToDartProp; |
| } |
| |
| dartPropToNativeProp[dartMember] = nativeMember; |
| dartTypeToNativeMembers[clsName] = dartPropToNativeProp; |
| } |
| } |
| } |
| } |
| var outputCode = """ |
| // A series of maps that record the various bindings we use in the web |
| // libraries. The term bindings here can either refer to the value in an |
| // `@Native` annotation or the value in a `@JSName` annotation. These maps |
| // compute the relationship between those values and the Dart members they |
| // annotate for fast lookup. |
| |
| /// Mapping of native types that are bound in the web libraries via |
| /// `@Native` to a map of their members to those members' Dart names. |
| final Map<String, Map<String, Set<String>>> nativeTypeToDartMembers = $nativeTypeToDartMembers; |
| /// Mapping of `@Native` types in the web libraries to a map of their |
| /// members to the native members that those members bind to. |
| final Map<String, Map<String, String>> dartTypeToNativeMembers = $dartTypeToNativeMembers; |
| /// Mapping of `@Native` types in the web libraries to the native types they |
| /// bind. |
| final Map<String, Set<String>> dartTypeToNativeTypes = $dartTypeToNativeTypes; |
| """; |
| var outputFile = args[0]; |
| File(outputFile).writeAsStringSync(outputCode); |
| } |
| |
| /// If [member] has a `@JSName('...')` annotation, returns the value inside the |
| /// parentheses. |
| /// |
| /// If there is no value or the class does not have a `@JSName()` annotation, |
| /// returns null. |
| String? _getJSNameValue(Member member) { |
| String? value; |
| for (var annotation in member.annotations) { |
| var c = annotationClass(annotation); |
| if (c != null && |
| c.name == 'JSName' && |
| c.enclosingLibrary.importUri == Uri.parse('dart:_js_helper')) { |
| var values = stringAnnotationValues(annotation); |
| if (values.isNotEmpty) { |
| value = values[0]; |
| } |
| } |
| } |
| return value; |
| } |