// 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 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>> nativeTypeToDartMembers = SplayTreeMap>>(); final SplayTreeMap> dartTypeToNativeMembers = SplayTreeMap>(); final SplayTreeMap> dartTypeToNativeTypes = SplayTreeMap>(); const Set webLibraries = { 'dart:html', 'dart:indexed_db', 'dart:svg', 'dart:web_audio', 'dart:web_gl' }; const Set 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>(); var dartPropToNativeProp = SplayTreeMap(); 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()) .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>> 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> dartTypeToNativeMembers = $dartTypeToNativeMembers; /// Mapping of `@Native` types in the web libraries to the native types they /// bind. final Map> 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; }