blob: e658608b4a4f06a8005b170bade75e61a41bed6b [file]
// Copyright (c) 2023, 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:convert';
import 'dart:js_interop';
import 'package:path/path.dart' as p;
import 'js/filesystem_api.dart';
import 'js/webidl2.dart' as webidl2;
import 'js/webidl_api.dart' as webidl;
import 'js/webref_css_api.dart';
import 'js/webref_elements_api.dart';
import 'js/webref_idl_api.dart';
import 'translator.dart';
import 'util.dart';
/// Generate CSS property names for setting / getting CSS properties in JS.
Future<List<String>> _generateCSSStyleDeclarations([JSObject? data]) async {
final cssStyleDeclarations = <String>{};
final array = objectEntries(data ?? await css.listAll().toDart);
for (var i = 0; i < array.length; i++) {
final entry = array[i] as JSArray<CSSEntries>;
final data = entry[1];
final properties = data.properties;
if (properties != null) {
for (var j = 0; j < properties.length; j++) {
final property = properties[j];
// There are three cases for [styleDeclaration]:
// 1) Length == 1, a single word CSS property.
// 2) Length == 2, a kebab case property + a camel case property.
// 3) Length == 3, webkit CSS properties.
final styleDeclaration = property.styleDeclaration;
if (styleDeclaration != null) {
final length = styleDeclaration.length;
if (length < 0 || length > 3) {
throw Exception('Unexpected style declaration $styleDeclaration');
}
// For now we ignore browser specific properties.
if (length == 3) continue;
final style = styleDeclaration[length - 1].toDart;
if (style.contains('-')) {
throw Exception('Unexpected style declaration $styleDeclaration');
}
cssStyleDeclarations.add(style);
}
}
}
}
return cssStyleDeclarations.toList()..sort();
}
/// Parse the elements spec and construct a map of element interfaces to the
/// tag names that correspond to the interface.
Future<Map<String, Set<String>>> _generateElementTagMap([
JSObject? data,
]) async {
final elementMap = <String, Set<String>>{};
final array = objectEntries(data ?? await elements.listAll().toDart);
for (var i = 0; i < array.length; i++) {
final entry = array[i] as JSArray;
final data = entry[1] as ElementsEntries;
final elements = data.elements;
if (elements != null) {
for (var j = 0; j < elements.length; j++) {
final element = elements[j];
final tag = element.name;
final interface = element.interface;
if (tag == null || interface == null) continue;
elementMap.putIfAbsent(interface, () => {}).add(tag);
}
}
}
return elementMap;
}
Future<(TranslationResult, Map<String, String>)> generateBindings(
String packageRoot,
String librarySubDir, {
required bool generateAll,
String? renameMapPath,
String? idlJsonPath,
required String bcdJsonPath,
}) async {
var renameMap = <String, String>{};
if (renameMapPath != null) {
if (fs.existsSync(renameMapPath.toJS).toDart) {
final jsonStr =
(fs.readFileSync(
renameMapPath.toJS,
JSReadFileOptions(encoding: 'utf8'.toJS),
)
as JSString)
.toDart;
final json = jsonDecode(jsonStr) as Map<String, dynamic>;
renameMap = json.map((k, v) => MapEntry(k, v as String));
}
}
JSObject? idlData;
JSObject? cssData;
JSObject? elementsData;
if (idlJsonPath != null) {
final jsonStr =
(fs.readFileSync(
idlJsonPath.toJS,
JSReadFileOptions(encoding: 'utf8'.toJS),
)
as JSString)
.toDart;
final json = jsonDecode(jsonStr) as Map<String, dynamic>;
idlData = (json['idl'] as Map?)?.jsify() as JSObject?;
cssData = (json['css'] as Map?)?.jsify() as JSObject?;
elementsData = (json['elements'] as Map?)?.jsify() as JSObject?;
}
final cssStyleDeclarations = await _generateCSSStyleDeclarations(cssData);
final elementHTMLMap = await _generateElementTagMap(elementsData);
final translator = Translator(
librarySubDir,
cssStyleDeclarations,
elementHTMLMap,
generateAll: generateAll,
packageRoot: packageRoot,
loadedRenameMap: renameMap,
bcdJsonPath: bcdJsonPath,
);
if (idlData != null) {
final array = objectEntries(idlData);
for (var i = 0; i < array.length; i++) {
final entry = array[i] as JSArray<JSAny?>;
final shortname = (entry[0] as JSString).toDart;
final ast = entry[1] as JSArray<webidl.Node>;
translator.collect(shortname, ast);
}
} else {
final array = objectEntries(await idl.parseAll().toDart);
for (var i = 0; i < array.length; i++) {
final entry = array[i] as JSArray<JSAny?>;
final shortname = (entry[0] as JSString).toDart;
final ast = entry[1] as JSArray<webidl.Node>;
translator.collect(shortname, ast);
}
}
translator.addInterfacesAndNamespaces();
final result = translator.translate();
final renamedTypes = translator.renamedClasses;
return (result, renamedTypes);
}
Future<TranslationResult> generateBindingsForFiles(
Map<String, String> fileContents,
String output, {
required String bcdJsonPath,
}) async {
// generate CSS style declarations and element tag map incase they are
// needed for the input files.
final emptyJsObject = <String, dynamic>{}.jsify() as JSObject;
final cssStyleDeclarations = await _generateCSSStyleDeclarations(
emptyJsObject,
);
final elementHTMLMap = await _generateElementTagMap(emptyJsObject);
final translator = Translator(
output,
cssStyleDeclarations,
elementHTMLMap,
generateAll: true,
generateForWeb: false,
bcdJsonPath: bcdJsonPath,
);
for (final file in fileContents.entries) {
final ast = webidl2.parse(file.value);
translator.collect(p.basenameWithoutExtension(file.key), ast);
}
translator.addInterfacesAndNamespaces();
return translator.translate();
}