diff --git a/web_generator/bin/update_idl_bindings.dart b/web_generator/bin/update_idl_bindings.dart index 019b422..1da5464 100644 --- a/web_generator/bin/update_idl_bindings.dart +++ b/web_generator/bin/update_idl_bindings.dart
@@ -67,12 +67,17 @@ // Run app with `node`. final generateAll = argResult['generate-all'] as bool; + final inputFiles = argResult['input'] as List<String>; await runProc( 'node', [ 'main.mjs', '--idl', - '--output=${p.join(_webPackagePath, 'lib', 'src')}', + for (String inputFile in inputFiles) '--input=$inputFile', + if (inputFiles.isEmpty) + '--output=${p.join(_webPackagePath, 'lib', 'src')}' + else + '--output=${argResult['output'] as String? ?? p.current}', if (generateAll) '--generate-all', ], workingDirectory: bindingsGeneratorPath, @@ -89,16 +94,17 @@ // delete context file await contextFile.delete(); - // Update readme. - final readmeFile = - File(p.normalize(p.fromUri(Platform.script.resolve('../README.md')))); + if (inputFiles.isEmpty) { + // Update readme. + final readmeFile = + File(p.normalize(p.fromUri(Platform.script.resolve('../README.md')))); - final sourceContent = readmeFile.readAsStringSync(); + final sourceContent = readmeFile.readAsStringSync(); - final cssVersion = _packageLockVersion(_webRefCss); - final elementsVersion = _packageLockVersion(_webRefElements); - final idlVersion = _packageLockVersion(_webRefIdl); - final versions = ''' + final cssVersion = _packageLockVersion(_webRefCss); + final elementsVersion = _packageLockVersion(_webRefElements); + final idlVersion = _packageLockVersion(_webRefIdl); + final versions = ''' $_startComment | Item | Version | | --- | --: | @@ -107,15 +113,16 @@ | `$_webRefIdl` | [$idlVersion](https://www.npmjs.com/package/$_webRefIdl/v/$idlVersion) | '''; - final newContent = - sourceContent.substring(0, sourceContent.indexOf(_startComment)) + - versions + - sourceContent.substring(sourceContent.indexOf(_endComment)); - if (newContent == sourceContent) { - print(ansi.styleBold.wrap('No update for readme.')); - } else { - print(ansi.styleBold.wrap('Updating readme for IDL version $idlVersion')); - readmeFile.writeAsStringSync(newContent, mode: FileMode.writeOnly); + final newContent = + sourceContent.substring(0, sourceContent.indexOf(_startComment)) + + versions + + sourceContent.substring(sourceContent.indexOf(_endComment)); + if (newContent == sourceContent) { + print(ansi.styleBold.wrap('No update for readme.')); + } else { + print(ansi.styleBold.wrap('Updating readme for IDL version $idlVersion')); + readmeFile.writeAsStringSync(newContent, mode: FileMode.writeOnly); + } } } @@ -161,23 +168,26 @@ '<!-- END updated by $_scriptPOSIXPath. Do not modify by hand -->'; final _usage = ''' -Global Options: -${_parser.usage} +${ansi.styleBold.wrap('WebIDL Gen')}: +$_thisScript [options] -${ansi.styleBold.wrap('IDL Command')}: $_thisScript idl [options] +If no IDL file is provided, defaults to the WebIDL definitions needed for package:web Usage: -${_parser.commands['idl']?.usage} - -${ansi.styleBold.wrap('Typescript Gen Command')}: $_thisScript dts <.d.ts file> [options] - -Usage: -${_parser.commands['dts']?.usage}'''; +${_parser.usage}'''; final _parser = ArgParser() ..addFlag('help', negatable: false, help: 'Show help information') ..addFlag('update', abbr: 'u', help: 'Update npm dependencies') ..addFlag('compile', defaultsTo: true) + ..addOption('output', + abbr: 'o', + help: 'Output directory where bindings will be generated to ' + '(defaults to `lib/src` in the web package when no IDL file is provided)') + ..addMultiOption('input', + abbr: 'i', + help: 'The input IDL file(s) to read and generate bindings for. ' + 'If not provided, the default WebIDL definitions will be used.') ..addFlag('generate-all', negatable: false, help: 'Generate bindings for all IDL definitions, including experimental '
diff --git a/web_generator/lib/src/ast.dart b/web_generator/lib/src/ast.dart deleted file mode 100644 index 14a43a6..0000000 --- a/web_generator/lib/src/ast.dart +++ /dev/null
@@ -1,169 +0,0 @@ -// Copyright (c) 2025, 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 'package:code_builder/code_builder.dart'; - -import 'interop_gen/generate.dart'; -import 'interop_gen/namer.dart'; - -sealed class Node { - abstract final String? name; - abstract final ID id; - final String? dartName; - - Node() : dartName = null; -} - -abstract class Declaration extends Node { - @override - abstract final String name; - - Spec emit(); -} - -abstract class NamedDeclaration extends Declaration { - ReferredType asReferredType([List<Type>? typeArgs]) => - ReferredType(name: name, declaration: this, typeParams: typeArgs ?? []); -} - -abstract interface class ExportableDeclaration extends Declaration { - /// Whether this declaration is exported. - bool get exported; -} - -abstract class Type extends Node { - Reference emit(); -} - -enum PrimitiveType implements Type { - string('string'), - any('any'), - object('object'), - number('number'), - boolean('boolean'), - undefined('undefined'), - unknown('unknown'); - - const PrimitiveType(this.name); - - @override - final String name; - - @override - ID get id => ID(type: 'type', name: name); - - // TODO(https://github.com/dart-lang/web/pull/386): Configuration options: double and num - @override - Reference emit() { - return switch (this) { - PrimitiveType.string => refer('String'), - PrimitiveType.any => refer('JSAny', 'dart:js_interop'), - PrimitiveType.object => refer('JSObject', 'dart:js_interop'), - PrimitiveType.number => refer('int'), - PrimitiveType.boolean => refer('bool'), - PrimitiveType.undefined => TypeReference((t) => t - ..symbol = 'JSAny' - ..url = 'dart:js_interop' - ..isNullable = true), - PrimitiveType.unknown => TypeReference((t) => t - ..symbol = 'JSAny' - ..url = 'dart:js_interop' - ..isNullable = true) - }; - } - - @override - String? get dartName => null; -} - -// TODO(): Refactor name - not all types can be referred to -// (only specific types) Instead change this -// to represent `typeof` declarations. -// TODO(): Create a shared type for such types that -// can be referred to (i.e namespace, interface, class) -// as a type `ReferrableDeclaration`. -class ReferredType<T extends Declaration> extends Type { - @override - String name; - - @override - ID get id => ID(type: 'type', name: name); - - T declaration; - - List<Type> typeParams; - - ReferredType( - {required this.name, - required this.declaration, - this.typeParams = const []}); - - @override - Reference emit() { - // TODO: implement emit - throw UnimplementedError(); - } -} - -// TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`) -class UnionType extends Type { - List<Type> types; - - UnionType({required this.types}); - - @override - ID get id => ID(type: 'type', name: types.map((t) => t.id).join('|')); - - @override - Reference emit() { - throw UnimplementedError(); - } - - @override - String? get name => null; -} - -class VariableDeclaration extends NamedDeclaration - implements ExportableDeclaration { - /// The variable modifier, as represented in TypeScript - VariableModifier modifier; - - @override - String name; - - Type type; - - @override - bool exported; - - VariableDeclaration( - {required this.name, - required this.type, - required this.modifier, - required this.exported}); - - @override - ID get id => ID(type: 'var', name: name); - - @override - Spec emit() { - if (modifier == VariableModifier.$const) { - return Method((m) => m - ..name = name - ..type = MethodType.getter - ..annotations.add(generateJSAnnotation()) - ..external = true - ..returns = type.emit()); - } else { - // getter and setter -> single variable - return Field((f) => f - ..external = true - ..name = name - ..type = type.emit() - ..annotations.add(generateJSAnnotation())); - } - } -} - -enum VariableModifier { let, $const, $var }
diff --git a/web_generator/lib/src/ast/base.dart b/web_generator/lib/src/ast/base.dart new file mode 100644 index 0000000..6ebc13c --- /dev/null +++ b/web_generator/lib/src/ast/base.dart
@@ -0,0 +1,76 @@ +// Copyright (c) 2025, 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 'package:code_builder/code_builder.dart'; + +import '../interop_gen/namer.dart'; +import 'types.dart'; + +class GlobalOptions { + static int variardicArgsCount = 4; + static bool shouldEmitJsTypes = false; +} + +class Options {} + +// TODO(nikeokoronkwo): Remove this once we address isNullable +class DeclarationOptions extends Options { + DeclarationOptions(); + + TypeOptions toTypeOptions({bool nullable = false}) => + TypeOptions(nullable: nullable); +} + +class TypeOptions extends Options { + bool nullable; + + TypeOptions({this.nullable = false}); +} + +class ASTOptions { + bool parameter; + bool emitJSTypes; + int variardicArgsCount; + + ASTOptions( + {this.parameter = false, + this.variardicArgsCount = 4, + this.emitJSTypes = false}); +} + +sealed class Node { + abstract final String? name; + abstract final ID id; + String? get dartName; + + Spec emit([Options? options]); + + Node(); +} + +abstract class Declaration extends Node { + @override + abstract final String name; + + @override + Spec emit([covariant DeclarationOptions? options]); +} + +abstract class NamedDeclaration extends Declaration { + ReferredType asReferredType([List<Type>? typeArgs]) => + ReferredType(name: name, declaration: this, typeParams: typeArgs ?? []); +} + +abstract interface class ExportableDeclaration extends Declaration { + /// Whether this declaration is exported. + bool get exported; +} + +abstract class Type extends Node { + @override + String? dartName; + + @override + Reference emit([covariant TypeOptions? options]); +}
diff --git a/web_generator/lib/src/ast/builtin.dart b/web_generator/lib/src/ast/builtin.dart new file mode 100644 index 0000000..563572c --- /dev/null +++ b/web_generator/lib/src/ast/builtin.dart
@@ -0,0 +1,116 @@ +// Copyright (c) 2025, 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. + +// ignore_for_file: non_constant_identifier_names + +import 'package:code_builder/code_builder.dart'; + +import '../interop_gen/namer.dart'; +import 'base.dart'; + +/// A built in type supported by `dart:js_interop` or by this library +/// (with generated declarations) +class BuiltinType extends Type { + @override + final String name; + + final List<Type> typeParams; + + /// Whether the given type is present in "dart:js_interop" + final bool fromDartJSInterop; + + // TODO(nikeokoronkwo): Types in general should have an `isNullable` + // property on them to indicate nullability for Dart generated code. + final bool? isNullable; + + BuiltinType( + {required this.name, + this.typeParams = const [], + this.fromDartJSInterop = false, + this.isNullable}); + + @override + ID get id => ID(type: 'type', name: name); + + @override + String? get dartName => null; + + @override + Reference emit([TypeOptions? options]) { + options ??= TypeOptions(); + + return TypeReference((t) => t + ..symbol = name + ..types.addAll(typeParams + // if there is only one type param, and it is void, ignore + .where((p) => typeParams.length != 1 || p != $voidType) + .map((p) => p.emit(TypeOptions()))) + ..url = fromDartJSInterop ? 'dart:js_interop' : null + ..isNullable = isNullable ?? options!.nullable); + } + + static final BuiltinType $voidType = BuiltinType(name: 'void'); + static final BuiltinType anyType = + BuiltinType(name: 'JSAny', fromDartJSInterop: true, isNullable: true); + + static BuiltinType primitiveType(PrimitiveType typeIdentifier, + {bool? shouldEmitJsType, + bool? isNullable, + List<Type> typeParams = const []}) { + shouldEmitJsType ??= GlobalOptions.shouldEmitJsTypes; + return switch (typeIdentifier) { + PrimitiveType.int || + PrimitiveType.num || + PrimitiveType.double when shouldEmitJsType => + BuiltinType( + name: 'JSNumber', fromDartJSInterop: true, isNullable: isNullable), + PrimitiveType.int => BuiltinType(name: 'int', isNullable: isNullable), + PrimitiveType.num => BuiltinType(name: 'num', isNullable: isNullable), + PrimitiveType.double => + BuiltinType(name: 'double', isNullable: isNullable), + PrimitiveType.boolean => shouldEmitJsType + ? BuiltinType( + name: 'JSBoolean', + fromDartJSInterop: true, + isNullable: isNullable) + : BuiltinType(name: 'bool', isNullable: isNullable), + PrimitiveType.string => shouldEmitJsType + ? BuiltinType( + name: 'JSString', fromDartJSInterop: true, isNullable: isNullable) + : BuiltinType(name: 'String', isNullable: isNullable), + PrimitiveType.$void || PrimitiveType.undefined => $voidType, + PrimitiveType.any || PrimitiveType.unknown => anyType, + PrimitiveType.object => BuiltinType( + name: 'JSObject', fromDartJSInterop: true, isNullable: isNullable), + PrimitiveType.array => BuiltinType( + name: 'JSArray', + typeParams: [typeParams.single], + fromDartJSInterop: true, + isNullable: isNullable), + PrimitiveType.promise => BuiltinType( + name: 'JSPromise', + typeParams: [typeParams.single], + fromDartJSInterop: true, + isNullable: isNullable), + PrimitiveType.function => BuiltinType( + name: 'JSFunction', fromDartJSInterop: true, isNullable: isNullable), + }; + } +} + +enum PrimitiveType { + int, + num, + double, + boolean, + string, + $void, + any, + object, + unknown, + undefined, + array, + promise, + function +}
diff --git a/web_generator/lib/src/ast/declarations.dart b/web_generator/lib/src/ast/declarations.dart new file mode 100644 index 0000000..1b02774 --- /dev/null +++ b/web_generator/lib/src/ast/declarations.dart
@@ -0,0 +1,140 @@ +// Copyright (c) 2025, 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 'package:code_builder/code_builder.dart'; + +import '../interop_gen/namer.dart'; +import 'base.dart'; +import 'helpers.dart'; +import 'types.dart'; + +class VariableDeclaration extends NamedDeclaration + implements ExportableDeclaration { + /// The variable modifier, as represented in TypeScript + VariableModifier modifier; + + @override + String name; + + Type type; + + @override + bool exported; + + VariableDeclaration( + {required this.name, + required this.type, + required this.modifier, + required this.exported}); + + @override + ID get id => ID(type: 'var', name: name); + + @override + Spec emit([DeclarationOptions? options]) { + if (modifier == VariableModifier.$const) { + return Method((m) => m + ..name = name + ..type = MethodType.getter + ..annotations.add(generateJSAnnotation()) + ..external = true + ..returns = type.emit()); + } else { + // getter and setter -> single variable + return Field((f) => f + ..external = true + ..name = name + ..type = type.emit() + ..annotations.add(generateJSAnnotation())); + } + } + + @override + String? get dartName => null; +} + +enum VariableModifier { let, $const, $var } + +class FunctionDeclaration extends NamedDeclaration + implements ExportableDeclaration { + @override + final String name; + + @override + final String? dartName; + + final List<ParameterDeclaration> parameters; + + final List<GenericType> typeParameters; + + final Type returnType; + + @override + bool exported; + + @override + ID id; + + FunctionDeclaration( + {required this.name, + required this.id, + this.dartName, + this.parameters = const [], + this.typeParameters = const [], + required this.exported, + required this.returnType}); + + @override + Spec emit([DeclarationOptions? options]) { + options ??= DeclarationOptions(); + + final requiredParams = <Parameter>[]; + final optionalParams = <Parameter>[]; + for (final p in parameters) { + if (p.variardic) { + optionalParams.addAll(spreadParam(p, GlobalOptions.variardicArgsCount)); + requiredParams.add(p.emit(options)); + } else { + if (p.optional) { + optionalParams.add(p.emit(options)); + } else { + requiredParams.add(p.emit(options)); + } + } + } + + return Method((m) => m + ..external = true + ..name = dartName ?? name + ..annotations.add(generateJSAnnotation( + dartName == null || dartName == name ? null : name)) + ..types + .addAll(typeParameters.map((t) => t.emit(options?.toTypeOptions()))) + ..returns = returnType.emit() + ..requiredParameters.addAll(requiredParams) + ..optionalParameters.addAll(optionalParams)); + } +} + +class ParameterDeclaration { + final String name; + + final bool optional; + + final Type type; + + final bool variardic; + + ParameterDeclaration( + {required this.name, + this.optional = false, + required this.type, + this.variardic = false}); + + Parameter emit([DeclarationOptions? options]) { + return Parameter((p) => p + ..name = name + ..type = type.emit(TypeOptions(nullable: optional))); + } +}
diff --git a/web_generator/lib/src/ast/helpers.dart b/web_generator/lib/src/ast/helpers.dart new file mode 100644 index 0000000..e987ccd --- /dev/null +++ b/web_generator/lib/src/ast/helpers.dart
@@ -0,0 +1,56 @@ +// Copyright (c) 2025, 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 'package:code_builder/code_builder.dart'; + +import 'base.dart'; +import 'builtin.dart'; +import 'declarations.dart'; + +BuiltinType? getSupportedType(String name, [List<Type> typeParams = const []]) { + final type = switch (name) { + 'Array' => PrimitiveType.array, + 'Promise' => PrimitiveType.promise, + _ => null + }; + + if (type == null) return null; + + return BuiltinType.primitiveType(type, typeParams: [ + getJSTypeAlternative(typeParams.singleOrNull ?? BuiltinType.anyType) + ]); +} + +Type getJSTypeAlternative(Type type) { + if (type is BuiltinType) { + if (type.fromDartJSInterop) return type; + + final primitiveType = switch (type.name) { + 'num' => PrimitiveType.num, + 'int' => PrimitiveType.int, + 'double' => PrimitiveType.double, + 'String' => PrimitiveType.string, + 'bool' => PrimitiveType.boolean, + _ => null + }; + + if (primitiveType == null) return BuiltinType.anyType; + + return BuiltinType.primitiveType(primitiveType, shouldEmitJsType: true); + } + return type; +} + +Expression generateJSAnnotation([String? name]) { + return refer('JS', 'dart:js_interop') + .call([if (name != null) literalString(name)]); +} + +List<Parameter> spreadParam(ParameterDeclaration p, int count) { + return List.generate(count - 1, (i) { + final paramNumber = i + 2; + final paramName = '${p.name}$paramNumber'; + return ParameterDeclaration(name: paramName, type: p.type).emit(); + }); +}
diff --git a/web_generator/lib/src/ast/types.dart b/web_generator/lib/src/ast/types.dart new file mode 100644 index 0000000..e3d789b --- /dev/null +++ b/web_generator/lib/src/ast/types.dart
@@ -0,0 +1,70 @@ +// Copyright (c) 2025, 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 'package:code_builder/code_builder.dart'; +import '../interop_gen/namer.dart'; +import 'base.dart'; + +class ReferredType<T extends Declaration> extends Type { + @override + String name; + + @override + ID get id => ID(type: 'type', name: name); + + T declaration; + + List<Type> typeParams; + + ReferredType( + {required this.name, + required this.declaration, + this.typeParams = const []}); + + @override + Reference emit([TypeOptions? options]) { + // TODO: implement emit + throw UnimplementedError(); + } +} + +// TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`) +class UnionType extends Type { + List<Type> types; + + UnionType({required this.types}); + + @override + ID get id => ID(type: 'type', name: types.map((t) => t.id).join('|')); + + @override + Reference emit([TypeOptions? options]) { + throw UnimplementedError('TODO: Implement UnionType.emit'); + } + + @override + String? get name => null; +} + +/// The base class for a type generic (like 'T') +class GenericType extends Type { + @override + final String name; + + final Type? constraint; + + final Declaration? parent; + + GenericType({required this.name, this.constraint, this.parent}); + + @override + Reference emit([TypeOptions? options]) => TypeReference((t) => t + ..symbol = name + ..bound = constraint?.emit() + ..isNullable = options?.nullable); + + @override + ID get id => + ID(type: 'generic-type', name: '$name@${parent?.id ?? "(anonymous)"}'); +}
diff --git a/web_generator/lib/src/dart_main.dart b/web_generator/lib/src/dart_main.dart index d5de405..f6943a4 100644 --- a/web_generator/lib/src/dart_main.dart +++ b/web_generator/lib/src/dart_main.dart
@@ -32,7 +32,10 @@ if (argResult.wasParsed('idl')) { await generateIDLBindings( - outputDirectory: argResult['output'] as String, + input: (argResult['input'] as List<String>).isEmpty + ? null + : argResult['input'] as Iterable<String>, + output: argResult['output'] as String, generateAll: argResult['generate-all'] as bool, languageVersion: Version.parse(languageVersionString), ); @@ -72,22 +75,46 @@ } Future<void> generateIDLBindings({ - required String outputDirectory, + Iterable<String>? input, + required String output, required bool generateAll, required Version languageVersion, }) async { - const librarySubDir = 'dom'; + if (input == null) { + // parse dom library as normal + const librarySubDir = 'dom'; - ensureDirectoryExists('$outputDirectory/$librarySubDir'); + ensureDirectoryExists('$output/$librarySubDir'); - final bindings = await generateBindings(packageRoot, librarySubDir, - generateAll: generateAll); - for (var entry in bindings.entries) { - final libraryPath = entry.key; - final library = entry.value; + final bindings = await generateBindings(packageRoot, librarySubDir, + generateAll: generateAll); - final contents = _emitLibrary(library, languageVersion).toJS; - fs.writeFileSync('$outputDirectory/$libraryPath'.toJS, contents); + for (var entry in bindings.entries) { + final libraryPath = entry.key; + final library = entry.value; + + final contents = _emitLibrary(library, languageVersion).toJS; + fs.writeFileSync('$output/$libraryPath'.toJS, contents); + } + } else { + // parse individual files + ensureDirectoryExists(output); + + final bindings = await generateBindingsForFiles({ + for (final file in input) + file: (fs.readFileSync( + file.toJS, JSReadFileOptions(encoding: 'utf-8'.toJS)) + as JSString) + .toDart + }, output); + + for (var entry in bindings.entries) { + final libraryPath = entry.key; + final library = entry.value; + + final contents = _emitLibrary(library, languageVersion).toJS; + fs.writeFileSync('$output/$libraryPath'.toJS, contents); + } } }
diff --git a/web_generator/lib/src/generate_bindings.dart b/web_generator/lib/src/generate_bindings.dart index 36324f7..33f9361 100644 --- a/web_generator/lib/src/generate_bindings.dart +++ b/web_generator/lib/src/generate_bindings.dart
@@ -4,6 +4,9 @@ import 'dart:js_interop'; +import 'package:path/path.dart' as p; + +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'; @@ -74,8 +77,8 @@ final cssStyleDeclarations = await _generateCSSStyleDeclarations(); final elementHTMLMap = await _generateElementTagMap(); final translator = Translator( - packageRoot, librarySubDir, cssStyleDeclarations, elementHTMLMap, - generateAll: generateAll); + librarySubDir, cssStyleDeclarations, elementHTMLMap, + generateAll: generateAll, packageRoot: packageRoot); final array = objectEntries(await idl.parseAll().toDart); for (var i = 0; i < array.length; i++) { final entry = array[i] as JSArray<JSAny?>; @@ -86,3 +89,21 @@ translator.addInterfacesAndNamespaces(); return translator.translate(); } + +Future<TranslationResult> generateBindingsForFiles( + Map<String, String> fileContents, String output) async { + // generate CSS style declarations and element tag map incase they are + // needed for the input files. + final cssStyleDeclarations = await _generateCSSStyleDeclarations(); + final elementHTMLMap = await _generateElementTagMap(); + final translator = Translator(output, cssStyleDeclarations, elementHTMLMap, + generateAll: true, generateForWeb: false); + + for (final file in fileContents.entries) { + final ast = webidl2.parse(file.value); + translator.collect(p.basenameWithoutExtension(file.key), ast); + } + + translator.addInterfacesAndNamespaces(); + return translator.translate(); +}
diff --git a/web_generator/lib/src/interop_gen/generate.dart b/web_generator/lib/src/interop_gen/generate.dart deleted file mode 100644 index 54ce2d3..0000000 --- a/web_generator/lib/src/interop_gen/generate.dart +++ /dev/null
@@ -1,10 +0,0 @@ -// Copyright (c) 2025, 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 'package:code_builder/code_builder.dart'; - -Expression generateJSAnnotation([String? name]) { - return refer('JS', 'dart:js_interop') - .call([if (name != null) literalString(name)]); -}
diff --git a/web_generator/lib/src/interop_gen/namer.dart b/web_generator/lib/src/interop_gen/namer.dart index 3725c11..dd07115 100644 --- a/web_generator/lib/src/interop_gen/namer.dart +++ b/web_generator/lib/src/interop_gen/namer.dart
@@ -2,6 +2,8 @@ // 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 '../banned_names.dart'; + class ID { final String type; final String name; @@ -21,10 +23,44 @@ UniqueNamer([Iterable<String> used = const <String>[]]) : _usedNames = used.toSet(); + /// Creates a unique name and ID for a given declaration to prevent + /// name collisions in Dart applications + /// + /// (Dart does not support operator overloading) + ({ID id, String name}) makeUnique(String name, String type) { + // nested structures (and anonymous structures) may not have a name + if (name.isEmpty) { + name = 'unnamed'; + } + + var newName = name; + if (keywords.contains(newName)) { + newName = '$newName\$'; + } + + var i = 0; + while (_usedNames.contains(newName)) { + ++i; + newName = '$name\$$i'; + } + + markUsed(newName); + return ( + id: ID(type: type, name: name, index: i == 0 ? null : i), + name: newName + ); + } + static ID parse(String id) { String? index; - final [type, name, ...ids] = id.split('#'); - if (ids.isEmpty) index = ids.single; + String name; + final [type, ...parts] = id.split('#'); + if (parts.isEmpty) { + throw Exception('Invalid ID: $id'); + } else { + name = parts[0]; + if (parts.length > 1) index = parts[1]; + } return ID( type: type, name: name, index: index == null ? null : int.parse(index));
diff --git a/web_generator/lib/src/interop_gen/transform.dart b/web_generator/lib/src/interop_gen/transform.dart index f6c0a78..663786b 100644 --- a/web_generator/lib/src/interop_gen/transform.dart +++ b/web_generator/lib/src/interop_gen/transform.dart
@@ -7,7 +7,7 @@ import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; -import '../ast.dart'; +import '../ast/base.dart'; import '../js/typescript.dart' as ts; import '../js/typescript.types.dart'; import 'namer.dart'; @@ -29,9 +29,9 @@ final specs = declMap.decls.values.map((d) { return switch (d) { final Declaration n => n.emit(), - final Type t => t.emit(), + final Type _ => null, }; - }); + }).whereType<Spec>(); final lib = Library((l) => l..body.addAll(specs)); return MapEntry(file, formatter.format('${lib.accept(emitter)}')); });
diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart index 0da0e79..404c299 100644 --- a/web_generator/lib/src/interop_gen/transform/transformer.dart +++ b/web_generator/lib/src/interop_gen/transform/transformer.dart
@@ -3,7 +3,11 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:js_interop'; -import '../../ast.dart'; +import '../../ast/base.dart'; +import '../../ast/builtin.dart'; +import '../../ast/declarations.dart'; +import '../../ast/helpers.dart'; +import '../../ast/types.dart'; import '../../js/typescript.dart' as ts; import '../../js/typescript.types.dart'; import '../namer.dart'; @@ -41,6 +45,8 @@ nodeMap.addAll({for (final d in decs) d.id.toString(): d}); default: final Declaration decl = switch (node.kind) { + TSSyntaxKind.FunctionDeclaration => + _transformFunction(node as TSFunctionDeclaration), _ => throw Exception('Unsupported Declaration Kind: ${node.kind}') }; // ignore: dead_code This line will not be dead in future decl additions @@ -50,6 +56,180 @@ nodes.add(node); } + List<Declaration> _transformVariable(TSVariableStatement variable) { + // get the modifier of the declaration + final modifiers = variable.modifiers.toDart; + final isExported = modifiers.any((m) { + return m.kind == TSSyntaxKind.ExportKeyword; + }); + + var modifier = VariableModifier.$var; + + if ((variable.declarationList.flags & TSNodeFlags.Const) != 0) { + modifier = VariableModifier.$const; + } else if ((variable.declarationList.flags & TSNodeFlags.Let) != 0) { + modifier = VariableModifier.let; + } + + return variable.declarationList.declarations.toDart.map((d) { + namer.markUsed(d.name.text); + return VariableDeclaration( + name: d.name.text, + type: d.type == null ? BuiltinType.anyType : _transformType(d.type!), + modifier: modifier, + exported: isExported); + }).toList(); + } + + TSNode? _getDeclarationByName(TSIdentifier name) { + final symbol = typeChecker.getSymbolAtLocation(name); + + final declarations = symbol?.getDeclarations(); + // TODO(https://github.com/dart-lang/web/issues/387): Some declarations may not be defined on file, + // and may be from an import statement + // We should be able to handle these + return declarations?.toDart.first; + } + + FunctionDeclaration _transformFunction(TSFunctionDeclaration function) { + final name = function.name.text; + + final modifiers = function.modifiers.toDart; + final isExported = modifiers.any((m) { + return m.kind == TSSyntaxKind.ExportKeyword; + }); + + final params = function.parameters.toDart; + + final typeParams = function.typeParameters?.toDart; + + final (id: id, name: uniqueName) = namer.makeUnique(name, 'fun'); + + return FunctionDeclaration( + name: name, + id: id, + dartName: uniqueName, + exported: isExported, + parameters: params.map(_transformParameter).toList(), + typeParameters: + typeParams?.map(_transformTypeParamDeclaration).toList() ?? [], + returnType: function.type != null + ? _transformType(function.type!) + : BuiltinType.anyType); + } + + ParameterDeclaration _transformParameter(TSParameterDeclaration parameter) { + final type = parameter.type != null + ? _transformType(parameter.type!, parameter: true) + : BuiltinType.anyType; + final isOptional = parameter.questionToken != null; + final isVariardic = parameter.dotDotDotToken != null; + + // what kind of parameter is this + switch (parameter.name.kind) { + case TSSyntaxKind.Identifier: + return ParameterDeclaration( + name: (parameter.name as TSIdentifier).text, + type: type, + variardic: isVariardic, + optional: isOptional); + default: + // TODO: Support Destructured Object Parameters + // and Destructured Array Parameters + throw Exception('Unsupported Parameter Name kind ${parameter.kind}'); + } + } + + GenericType _transformTypeParamDeclaration( + TSTypeParameterDeclaration typeParam) { + return GenericType( + name: typeParam.name.text, + constraint: typeParam.constraint == null + ? BuiltinType.anyType + : _transformType(typeParam.constraint!)); + } + + /// Parses the type + /// + /// TODO(https://github.com/dart-lang/web/issues/384): Add support for literals (i.e individual booleans and `null`) + /// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types + Type _transformType(TSTypeNode type, {bool parameter = false}) { + if (type.kind == TSSyntaxKind.UnionType) { + final unionType = type as TSUnionTypeNode; + return UnionType( + types: unionType.types.toDart.map<Type>(_transformType).toList()); + } + + if (type.kind == TSSyntaxKind.TypeReference) { + final refType = type as TSTypeReferenceNode; + + final name = refType.typeName.text; + final typeArguments = refType.typeArguments?.toDart; + + var declarationsMatching = nodeMap.findByName(name); + + if (declarationsMatching.isEmpty) { + // check if builtin + // TODO(https://github.com/dart-lang/web/issues/380): A better name + // for this, and adding support for "supported declarations" + // (also a better name for that) + final supportedType = getSupportedType( + name, (typeArguments ?? []).map(_transformType).toList()); + if (supportedType != null) { + return supportedType; + } + + // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? + final declaration = _getDeclarationByName(refType.typeName); + + if (declaration == null) { + throw Exception('Found no declaration matching $name'); + } + + if (declaration.kind == TSSyntaxKind.TypeParameter) { + return GenericType(name: name); + } + + transform(declaration); + + declarationsMatching = nodeMap.findByName(name); + } + + // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? + final firstNode = + declarationsMatching.whereType<NamedDeclaration>().first; + + return firstNode.asReferredType( + (typeArguments ?? []).map(_transformType).toList(), + ); + } + + if (type.kind == TSSyntaxKind.ArrayType) { + return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [ + getJSTypeAlternative( + _transformType((type as TSArrayTypeNode).elementType)) + ]); + } + + // check for primitive type via its kind + final primitiveType = switch (type.kind) { + TSSyntaxKind.ArrayType => PrimitiveType.array, + TSSyntaxKind.StringKeyword => PrimitiveType.string, + TSSyntaxKind.AnyKeyword => PrimitiveType.any, + TSSyntaxKind.ObjectKeyword => PrimitiveType.object, + TSSyntaxKind.NumberKeyword => + (parameter ? PrimitiveType.num : PrimitiveType.double), + TSSyntaxKind.UndefinedKeyword => PrimitiveType.undefined, + TSSyntaxKind.UnknownKeyword => PrimitiveType.unknown, + TSSyntaxKind.BooleanKeyword => PrimitiveType.boolean, + TSSyntaxKind.VoidKeyword => PrimitiveType.$void, + _ => throw UnsupportedError( + 'The given type with kind ${type.kind} is not supported yet') + }; + + return BuiltinType.primitiveType(primitiveType); + } + NodeMap filter() { final filteredDeclarations = NodeMap(); @@ -66,7 +246,7 @@ filteredDeclarations.add(e); } break; - case final PrimitiveType _: + case final BuiltinType _: // primitive types are generated by default break; case Type(): @@ -94,15 +274,29 @@ switch (decl) { case final VariableDeclaration v: - if (v.type is! PrimitiveType) filteredDeclarations.add(v.type); + if (v.type is! BuiltinType) filteredDeclarations.add(v.type); + break; + case final FunctionDeclaration f: + if (f.returnType is! BuiltinType) { + filteredDeclarations.add(f.returnType); + } + filteredDeclarations.addAll({ + for (final node in f.parameters.map((p) => p.type)) + node.id.toString(): node + }); + filteredDeclarations.addAll({ + for (final node + in f.typeParameters.map((p) => p.constraint).whereType<Type>()) + node.id.toString(): node + }); break; case final UnionType u: filteredDeclarations.addAll({ - for (final t in u.types.where((t) => t is! PrimitiveType)) + for (final t in u.types.where((t) => t is! BuiltinType)) t.id.toString(): t }); break; - case final PrimitiveType _: + case final BuiltinType _: // primitive types are generated by default break; default: @@ -121,95 +315,4 @@ return filteredDeclarations; } - - List<Declaration> _transformVariable(TSVariableStatement variable) { - // get the modifier of the declaration - final modifiers = variable.modifiers.toDart; - final isExported = modifiers.any((m) { - return m.kind == TSSyntaxKind.ExportKeyword; - }); - - var modifier = VariableModifier.$var; - - if ((variable.declarationList.flags & TSNodeFlags.Const) != 0) { - modifier = VariableModifier.$const; - } else if ((variable.declarationList.flags & TSNodeFlags.Let) != 0) { - modifier = VariableModifier.let; - } - - return variable.declarationList.declarations.toDart.map((d) { - namer.markUsed(d.name.text); - return VariableDeclaration( - name: d.name.text, - type: d.type == null ? PrimitiveType.any : _transformType(d.type!), - modifier: modifier, - exported: isExported); - }).toList(); - } - - TSNode? _getDeclarationByName(TSIdentifier name) { - final symbol = typeChecker.getSymbolAtLocation(name); - - final declarations = symbol?.getDeclarations(); - // TODO(https://github.com/dart-lang/web/issues/387): Some declarations may not be defined on file, - // and may be from an import statement - // We should be able to handle these - return declarations?.toDart.first; - } - - /// Parses the type - /// - /// TODO(https://github.com/dart-lang/web/issues/384): Add support for literals (i.e individual booleans and `null`) - /// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types - Type _transformType(TSTypeNode type) { - if (type.kind == TSSyntaxKind.UnionType) { - final unionType = type as TSUnionTypeNode; - // parse union type - return UnionType( - types: unionType.types.toDart.map<Type>(_transformType).toList()); - } - - if (type.kind == TSSyntaxKind.TypeReference) { - // reference type - final refType = type as TSTypeReferenceNode; - - final name = refType.typeName.text; - final typeArguments = refType.typeArguments?.toDart; - - var declarationsMatching = nodeMap.findByName(name); - if (declarationsMatching.isEmpty) { - // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? - final declaration = _getDeclarationByName(refType.typeName); - - if (declaration == null) { - throw Exception('Found no declaration matching $name'); - } - - transform(declaration); - - declarationsMatching = nodeMap.findByName(name); - } - - // TODO: In the case of overloading, should/shouldn't we handle more than one declaration? - final firstNode = - declarationsMatching.whereType<NamedDeclaration>().first; - - return firstNode.asReferredType( - (typeArguments ?? []).map(_transformType).toList(), - ); - } - - // check for its kind - return switch (type.kind) { - TSSyntaxKind.StringKeyword => PrimitiveType.string, - TSSyntaxKind.AnyKeyword => PrimitiveType.any, - TSSyntaxKind.ObjectKeyword => PrimitiveType.object, - TSSyntaxKind.NumberKeyword => PrimitiveType.number, - TSSyntaxKind.UndefinedKeyword => PrimitiveType.undefined, - TSSyntaxKind.UnknownKeyword => PrimitiveType.unknown, - TSSyntaxKind.BooleanKeyword => PrimitiveType.boolean, - _ => throw UnsupportedError( - 'The given type with kind ${type.kind} is not supported yet') - }; - } }
diff --git a/web_generator/lib/src/js/typescript.dart b/web_generator/lib/src/js/typescript.dart index bc9a73d..f098057 100644 --- a/web_generator/lib/src/js/typescript.dart +++ b/web_generator/lib/src/js/typescript.dart
@@ -1,3 +1,7 @@ +// Copyright (c) 2025, 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. + @JS('ts') library;
diff --git a/web_generator/lib/src/js/typescript.types.dart b/web_generator/lib/src/js/typescript.types.dart index e6d31d3..ed8855d 100644 --- a/web_generator/lib/src/js/typescript.types.dart +++ b/web_generator/lib/src/js/typescript.types.dart
@@ -1,3 +1,7 @@ +// Copyright (c) 2025, 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. + // ignore_for_file: constant_identifier_names @JS('ts') @@ -7,6 +11,8 @@ import 'package:meta/meta.dart'; +import 'typescript.dart'; + extension type const TSSyntaxKind._(num _) { /// To be ignored static const TSSyntaxKind EndOfFileToken = TSSyntaxKind._(1); @@ -18,6 +24,7 @@ static const TSSyntaxKind InterfaceDeclaration = TSSyntaxKind._(264); static const TSSyntaxKind FunctionDeclaration = TSSyntaxKind._(262); static const TSSyntaxKind ExportDeclaration = TSSyntaxKind._(278); + static const TSSyntaxKind Parameter = TSSyntaxKind._(169); /// keywords static const TSSyntaxKind ExportKeyword = TSSyntaxKind._(95); @@ -34,13 +41,17 @@ static const TSSyntaxKind UndefinedKeyword = TSSyntaxKind._(157); static const TSSyntaxKind SetKeyword = TSSyntaxKind._(153); static const TSSyntaxKind UnknownKeyword = TSSyntaxKind._(159); + static const TSSyntaxKind VoidKeyword = TSSyntaxKind._(116); // types static const TSSyntaxKind UnionType = TSSyntaxKind._(192); static const TSSyntaxKind TypeReference = TSSyntaxKind._(183); + static const TSSyntaxKind ArrayType = TSSyntaxKind._(188); /// Other static const TSSyntaxKind Identifier = TSSyntaxKind._(80); + static const TSSyntaxKind ObjectBindingPattern = TSSyntaxKind._(206); + static const TSSyntaxKind ArrayBindingPattern = TSSyntaxKind._(207); static const TSSyntaxKind TypeParameter = TSSyntaxKind._(168); static const TSSyntaxKind HeritageClause = TSSyntaxKind._(298); static const TSSyntaxKind ExpressionWithTypeArguments = TSSyntaxKind._(233); @@ -57,11 +68,20 @@ external TSSyntaxKind get kind; external TSNode get parent; external TSNodeFlags get flags; + external String getText([TSSourceFile? sourceFile]); + external String getFullText([TSSourceFile? sourceFile]); } @JS('TypeNode') extension type TSTypeNode._(JSObject _) implements TSNode {} +@JS('ArrayTypeNode') +extension type TSArrayTypeNode._(JSObject _) implements TSTypeNode { + @redeclare + TSSyntaxKind get kind => TSSyntaxKind.ArrayType; + external TSTypeNode get elementType; +} + @JS('UnionTypeNode') extension type TSUnionTypeNode._(JSObject _) implements TSTypeNode { @redeclare @@ -89,21 +109,47 @@ external String get text; } -@JS('VariableDeclaration') +@JS('VariableStatement') extension type TSVariableStatement._(JSObject _) implements TSStatement { external TSVariableDeclarationList get declarationList; external TSNodeArray<TSNode> get modifiers; } +@JS('VariableDeclarationList') +extension type TSVariableDeclarationList._(JSObject _) implements TSNode { + external TSNodeArray<TSVariableDeclaration> get declarations; +} + @JS('VariableDeclaration') extension type TSVariableDeclaration._(JSObject _) implements TSDeclaration { external TSIdentifier get name; external TSTypeNode? get type; } -@JS('VariableDeclarationList') -extension type TSVariableDeclarationList._(JSObject _) implements TSNode { - external TSNodeArray<TSVariableDeclaration> get declarations; +@JS('FunctionDeclaration') +extension type TSFunctionDeclaration._(JSObject _) implements TSDeclaration { + external TSIdentifier get name; + external TSTypeNode? get type; + external TSNode? get asteriskToken; + external TSNodeArray<TSParameterDeclaration> get parameters; + external TSNodeArray<TSTypeParameterDeclaration>? get typeParameters; + external TSNodeArray<TSNode> get modifiers; +} + +@JS('ParameterDeclaration') +extension type TSParameterDeclaration._(JSObject _) implements TSDeclaration { + external TSNode get name; + external TSTypeNode? get type; + external TSNodeArray<TSNode>? get modifiers; + external TSNode? get questionToken; + external TSNode? get dotDotDotToken; +} + +@JS('TypeParameterDeclaration') +extension type TSTypeParameterDeclaration._(JSObject _) + implements TSDeclaration { + external TSIdentifier get name; + external TSTypeNode? get constraint; } @JS('NodeArray')
diff --git a/web_generator/lib/src/js/typescript_extensions.dart b/web_generator/lib/src/js/typescript_extensions.dart deleted file mode 100644 index 603d62d..0000000 --- a/web_generator/lib/src/js/typescript_extensions.dart +++ /dev/null
@@ -1,14 +0,0 @@ -import 'typescript.types.dart'; - -extension Names on TSSyntaxKind { - String get name { - return switch (this) { - TSSyntaxKind.DeclareKeyword => 'declare', - TSSyntaxKind.ExportKeyword => 'export', - TSSyntaxKind.ExtendsKeyword => 'extends', - TSSyntaxKind.ImplementsKeyword => 'implements', - TSSyntaxKind.VariableDeclaration => 'variable', - _ => throw UnsupportedError('The keyword is not supported at the moment') - }; - } -}
diff --git a/web_generator/lib/src/js/webidl2.dart b/web_generator/lib/src/js/webidl2.dart new file mode 100644 index 0000000..78e97de --- /dev/null +++ b/web_generator/lib/src/js/webidl2.dart
@@ -0,0 +1,12 @@ +// Copyright (c) 2025, 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. +@JS('webidl2') +library; + +import 'dart:js_interop'; + +import 'webidl_api.dart' as idl; + +@JS() +external JSArray<idl.Node> parse(String contents);
diff --git a/web_generator/lib/src/main.mjs b/web_generator/lib/src/main.mjs index 2355045..c1b7383 100644 --- a/web_generator/lib/src/main.mjs +++ b/web_generator/lib/src/main.mjs
@@ -8,6 +8,7 @@ import * as css from '@webref/css'; import * as elements from '@webref/elements'; import * as idl from '@webref/idl'; +import * as webidl2 from "webidl2"; import * as ts from 'typescript'; const require = createRequire(import.meta.url); @@ -19,6 +20,7 @@ globalThis.elements = elements; globalThis.fs = fs; globalThis.idl = idl; +globalThis.webidl2 = webidl2; globalThis.ts = ts; globalThis.location = { href: `file://${process.cwd()}/` }
diff --git a/web_generator/lib/src/package-lock.json b/web_generator/lib/src/package-lock.json index e6d9b3d..a83802b 100644 --- a/web_generator/lib/src/package-lock.json +++ b/web_generator/lib/src/package-lock.json
@@ -13,10 +13,8 @@ "@webref/css": "^6.11.0", "@webref/elements": "^2.2.2", "@webref/idl": "^3.43.1", - "typescript": "^5.8.3" - }, - "devDependencies": { - "webidl2": "^24.2.2" + "typescript": "^5.8.3", + "webidl2": "^24.4.1" } }, "node_modules/@mdn/browser-compat-data": {
diff --git a/web_generator/lib/src/package.json b/web_generator/lib/src/package.json index 58ac69e..2b77220 100644 --- a/web_generator/lib/src/package.json +++ b/web_generator/lib/src/package.json
@@ -13,9 +13,7 @@ "@webref/css": "^6.11.0", "@webref/elements": "^2.2.2", "@webref/idl": "^3.43.1", - "typescript": "^5.8.3" - }, - "devDependencies": { - "webidl2": "^24.2.2" + "typescript": "^5.8.3", + "webidl2": "^24.4.1" } }
diff --git a/web_generator/lib/src/translator.dart b/web_generator/lib/src/translator.dart index a47c588..2337a5f 100644 --- a/web_generator/lib/src/translator.dart +++ b/web_generator/lib/src/translator.dart
@@ -648,10 +648,11 @@ } class Translator { - final String packageRoot; + final String? packageRoot; final String _librarySubDir; final List<String> _cssStyleDeclarations; final Map<String, Set<String>> _elementTagMap; + final bool _generateForWeb; final _libraries = <String, _Library>{}; final _typeToDeclaration = <String, idl.Node>{}; @@ -668,9 +669,10 @@ /// Singleton so that various helper methods can access info about the AST. static Translator? instance; - Translator(this.packageRoot, this._librarySubDir, this._cssStyleDeclarations, - this._elementTagMap, - {required bool generateAll}) { + Translator( + this._librarySubDir, this._cssStyleDeclarations, this._elementTagMap, + {this.packageRoot, required bool generateAll, bool generateForWeb = true}) + : _generateForWeb = generateForWeb { instance = this; docProvider = DocProvider.create(); browserCompatData = BrowserCompatData.read(generateAll: generateAll); @@ -815,7 +817,7 @@ final libraryPath = '$_librarySubDir/${shortName.kebabToSnake}.dart'; assert(!_libraries.containsKey(libraryPath)); - final library = _Library(shortName, '$packageRoot/$libraryPath'); + final library = _Library(shortName, '${packageRoot ?? '.'}/$libraryPath'); for (var i = 0; i < ast.length; i++) { library.add(ast[i]); @@ -1117,8 +1119,7 @@ // from: https://github.com/w3c/webidl2.js/blob/main/README.md#default-and-const-values final body = switch (constant.valueType) { 'string' => code.literalString((constant.value as JSString).toDart), - 'boolean' => code.literalBool( - (constant.value as JSString).toDart.toLowerCase() == 'true'), + 'boolean' => code.literalBool((constant.value as JSBoolean).toDart), 'number' => code.literalNum(num.parse((constant.value as JSString).toDart)), 'null' => code.literalNull, @@ -1375,39 +1376,51 @@ ]; } - code.Library _library(_Library library) => code.Library((b) => b - ..comments.addAll([ - ...licenseHeader, - '', - ...mozLicenseHeader, - ]) - // TODO(https://github.com/dart-lang/sdk/issues/56450): Remove this once - // this bug has been resolved. - ..ignoreForFile.addAll([ - 'unintended_html_in_doc_comment', - ]) - ..generatedByComment = generatedFileDisclaimer - // TODO(srujzs): This is to address the issue around extension type object - // literal constructors in https://github.com/dart-lang/sdk/issues/54801. - // Once this package moves to an SDK version that contains a fix for that, - // this can be removed. - ..annotations.addAll(_jsOverride('', alwaysEmit: true)) - ..body.addAll([ - for (final typedef in library.typedefs.where(_usedTypes.contains)) - _typedef(typedef.name, _desugarTypedef(_RawType(typedef.name, false))!), - for (final callback in library.callbacks.where(_usedTypes.contains)) - _typedef( - callback.name, _desugarTypedef(_RawType(callback.name, false))!), - for (final callbackInterface - in library.callbackInterfaces.where(_usedTypes.contains)) - _typedef(callbackInterface.name, - _desugarTypedef(_RawType(callbackInterface.name, false))!), - for (final enum_ in library.enums.where(_usedTypes.contains)) - _typedef(enum_.name, _desugarTypedef(_RawType(enum_.name, false))!), - for (final interfacelike - in library.interfacelikes.where(_usedTypes.contains)) - ..._interfacelike(interfacelike), - ])); + code.Library _library(_Library library) => code.Library((b) { + if (_generateForWeb) { + b.comments.addAll([ + ...licenseHeader, + '', + ...mozLicenseHeader, + ]); + } + // TODO(https://github.com/dart-lang/sdk/issues/56450): Remove + // this once this bug has been resolved. + b + ..ignoreForFile.addAll([ + 'unintended_html_in_doc_comment', + 'constant_identifier_names', + if (library.interfacelikes + .where((i) => i.type == 'namespace') + .isNotEmpty) + 'non_constant_identifier_names' + ]) + ..generatedByComment = generatedFileDisclaimer + // TODO(srujzs): This is to address the issue around extension type + // object literal constructors in + // https://github.com/dart-lang/sdk/issues/54801. + // Once this package moves to an SDK version that contains a fix + // for that, this can be removed. + ..annotations.addAll(_jsOverride('', alwaysEmit: true)) + ..body.addAll([ + for (final typedef in library.typedefs.where(_usedTypes.contains)) + _typedef(typedef.name, + _desugarTypedef(_RawType(typedef.name, false))!), + for (final callback in library.callbacks.where(_usedTypes.contains)) + _typedef(callback.name, + _desugarTypedef(_RawType(callback.name, false))!), + for (final callbackInterface + in library.callbackInterfaces.where(_usedTypes.contains)) + _typedef(callbackInterface.name, + _desugarTypedef(_RawType(callbackInterface.name, false))!), + for (final enum_ in library.enums.where(_usedTypes.contains)) + _typedef( + enum_.name, _desugarTypedef(_RawType(enum_.name, false))!), + for (final interfacelike + in library.interfacelikes.where(_usedTypes.contains)) + ..._interfacelike(interfacelike), + ]); + }); code.Library generateRootImport(Iterable<String> files) => code.Library((b) => b @@ -1430,7 +1443,9 @@ } } - dartLibraries['dom.dart'] = generateRootImport(dartLibraries.keys); + if (_generateForWeb) { + dartLibraries['dom.dart'] = generateRootImport(dartLibraries.keys); + } return dartLibraries; }
diff --git a/web_generator/lib/src/type_aliases.dart b/web_generator/lib/src/type_aliases.dart index a2bf791..eaa5f33 100644 --- a/web_generator/lib/src/type_aliases.dart +++ b/web_generator/lib/src/type_aliases.dart
@@ -12,6 +12,7 @@ // Note that this is a special sentinel that doesn't actually exist in the set // of JS types today (although this might in the future). 'undefined': 'JSUndefined', + 'void': 'JSVoid', 'Function': 'JSFunction', 'SharedArrayBuffer': 'JSObject', @@ -69,5 +70,6 @@ // `Translator._typeReference` for more details. 'JSDouble': 'num', 'JSNumber': 'num', + 'JSVoid': 'void', 'JSUndefined': 'void', };
diff --git a/web_generator/test/integration/idl/callbacks_expected.dart b/web_generator/test/integration/idl/callbacks_expected.dart new file mode 100644 index 0000000..0021fa2 --- /dev/null +++ b/web_generator/test/integration/idl/callbacks_expected.dart
@@ -0,0 +1,23 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +typedef VoidCallback = JSFunction; +typedef StringCallback = JSFunction; +typedef Comparator = JSFunction; +typedef Transformer = JSFunction; +typedef AsyncOperationCallback = JSFunction; +extension type AsyncOperations._(JSObject _) implements JSObject { + external void performOperation(AsyncOperationCallback whenFinished); +} +extension type Processor._(JSObject _) implements JSObject { + external void run(VoidCallback onComplete); + external void compare(Comparator cmp); + external void stringManipulate(String string, StringCallback callback); + external void transform(JSAny? data, Transformer transformer); +}
diff --git a/web_generator/test/integration/idl/callbacks_input.idl b/web_generator/test/integration/idl/callbacks_input.idl new file mode 100644 index 0000000..b58be8a --- /dev/null +++ b/web_generator/test/integration/idl/callbacks_input.idl
@@ -0,0 +1,17 @@ +callback VoidCallback = void (); +callback StringCallback = void (DOMString result); +callback Comparator = long (DOMString a, DOMString b); +callback Transformer = any (any input); +callback AsyncOperationCallback = undefined (DOMString status); + +[Exposed=Window] +interface AsyncOperations { + undefined performOperation(AsyncOperationCallback whenFinished); +}; + +interface Processor { + void run(VoidCallback onComplete); + void compare(Comparator cmp); + void stringManipulate(DOMString string, StringCallback callback); + void transform(any data, Transformer transformer); +};
diff --git a/web_generator/test/integration/idl/constructors_expected.dart b/web_generator/test/integration/idl/constructors_expected.dart new file mode 100644 index 0000000..1c95d0c --- /dev/null +++ b/web_generator/test/integration/idl/constructors_expected.dart
@@ -0,0 +1,38 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +extension type Shape._(JSObject _) implements JSObject { + external factory Shape(); +} +extension type Done._(JSObject _) implements JSObject {} +extension type Coordinate._(JSObject _) implements JSObject { + external int get x; + external set x(int value); + external int get y; + external set y(int value); +} +extension type DoneList._(JSObject _) implements JSObject { + external factory DoneList(int length); + + external Done item(int index); + external int get length; +} +extension type Circle._(JSObject _) implements Shape, JSObject { + external factory Circle(num radius); + + external static Coordinate triangulate(Circle c1, Circle c2, Circle c3); + external static int get triangulationCount; + external double get r; + external set r(num value); + external double get cx; + external set cx(num value); + external double get cy; + external set cy(num value); + external double get circumference; +}
diff --git a/web_generator/test/integration/idl/constructors_input.idl b/web_generator/test/integration/idl/constructors_input.idl new file mode 100644 index 0000000..dc91471 --- /dev/null +++ b/web_generator/test/integration/idl/constructors_input.idl
@@ -0,0 +1,32 @@ +interface Shape { + constructor(); +}; + +/// Wordplay around "Node" +interface Done { + +}; + +interface Coordinate { + attribute long x; + attribute long y; +}; + +[Exposed=Window] +interface DoneList { + constructor(unsigned long length); + Done item(unsigned long index); + readonly attribute unsigned long length; +}; + +[Exposed=Window] +interface Circle : Shape { + constructor(double radius); + attribute double r; + attribute double cx; + attribute double cy; + readonly attribute double circumference; + + static readonly attribute long triangulationCount; + static Coordinate triangulate(Circle c1, Circle c2, Circle c3); +};
diff --git a/web_generator/test/integration/idl/dictionaries_expected.dart b/web_generator/test/integration/idl/dictionaries_expected.dart new file mode 100644 index 0000000..dd9b314 --- /dev/null +++ b/web_generator/test/integration/idl/dictionaries_expected.dart
@@ -0,0 +1,45 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +extension type ConfigOptions._(JSObject _) implements JSObject { + external factory ConfigOptions({ + required String endpoint, + bool useCache, + int timeout, + JSArray<JSString>? tags, + }); + + external String get endpoint; + external set endpoint(String value); + external bool get useCache; + external set useCache(bool value); + external int get timeout; + external set timeout(int value); + external JSArray<JSString>? get tags; + external set tags(JSArray<JSString>? value); +} +extension type ExtendedOptions._(JSObject _) + implements ConfigOptions, JSObject { + external factory ExtendedOptions({ + required String endpoint, + bool useCache, + int timeout, + JSArray<JSString>? tags, + String? userToken, + String mode, + }); + + external String? get userToken; + external set userToken(String? value); + external String get mode; + external set mode(String value); +} +extension type Configurable._(JSObject _) implements JSObject { + external void applySettings([ExtendedOptions options]); +}
diff --git a/web_generator/test/integration/idl/dictionaries_input.idl b/web_generator/test/integration/idl/dictionaries_input.idl new file mode 100644 index 0000000..ed73f60 --- /dev/null +++ b/web_generator/test/integration/idl/dictionaries_input.idl
@@ -0,0 +1,15 @@ +dictionary ConfigOptions { + required DOMString endpoint; + boolean useCache = true; + long timeout = 5000; + sequence<DOMString>? tags; +}; + +dictionary ExtendedOptions : ConfigOptions { + DOMString? userToken; + DOMString mode = "default"; +}; + +interface Configurable { + void applySettings(optional ExtendedOptions options); +};
diff --git a/web_generator/test/integration/idl/enum_expected.dart b/web_generator/test/integration/idl/enum_expected.dart new file mode 100644 index 0000000..f0dff91 --- /dev/null +++ b/web_generator/test/integration/idl/enum_expected.dart
@@ -0,0 +1,21 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +typedef LogLevel = String; +typedef Direction = String; +typedef DisplayMode = String; +extension type Logger._(JSObject _) implements JSObject { + external void log(String message, [LogLevel level]); + external void logWithDirection( + String message, [ + LogLevel level, + Direction dir, + ]); + external void logWithDisplayMode(String message, DisplayMode display); +}
diff --git a/web_generator/test/integration/idl/enum_input.idl b/web_generator/test/integration/idl/enum_input.idl new file mode 100644 index 0000000..5a5e291 --- /dev/null +++ b/web_generator/test/integration/idl/enum_input.idl
@@ -0,0 +1,26 @@ +enum LogLevel { + "debug", + "info", + "warn", + "error" +}; + +enum Direction { + "up", + "down", + "left", + "right" +}; + +enum DisplayMode { + "fullscreen", + "standalone", + "minimal-ui", + "browser" +}; + +interface Logger { + void log(DOMString message, optional LogLevel level = "info"); + void logWithDirection(DOMString message, optional LogLevel level = "info", optional Direction dir = "up"); + void logWithDisplayMode(DOMString message, DisplayMode display); +};
diff --git a/web_generator/test/integration/idl/indexers_expected.dart b/web_generator/test/integration/idl/indexers_expected.dart new file mode 100644 index 0000000..4ee2c64 --- /dev/null +++ b/web_generator/test/integration/idl/indexers_expected.dart
@@ -0,0 +1,16 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +extension type OrderedMap._(JSObject _) implements JSObject { + external JSAny? operator [](int index); + external void operator []=(int index, JSAny? value); + external JSAny? get(String name); + external void set(String name, JSAny? value); + external int get size; +}
diff --git a/web_generator/test/integration/idl/indexers_input.idl b/web_generator/test/integration/idl/indexers_input.idl new file mode 100644 index 0000000..a0145b5 --- /dev/null +++ b/web_generator/test/integration/idl/indexers_input.idl
@@ -0,0 +1,9 @@ +interface OrderedMap { + readonly attribute unsigned long size; + + getter any (unsigned long index); + setter undefined (unsigned long index, any value); + + getter any get(DOMString name); + setter undefined set(DOMString name, any value); +};
diff --git a/web_generator/test/integration/idl/interfaces_expected.dart b/web_generator/test/integration/idl/interfaces_expected.dart new file mode 100644 index 0000000..07d9ec2 --- /dev/null +++ b/web_generator/test/integration/idl/interfaces_expected.dart
@@ -0,0 +1,61 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +typedef LogLevel = String; +extension type ConfigOptions._(JSObject _) implements JSObject {} +extension type EmptyInterface._(JSObject _) implements JSObject {} +extension type BaseInterface._(JSObject _) implements JSObject { + external void f(); + external void g(); +} +extension type Paint._(JSObject _) implements JSObject {} +extension type SolidColor._(JSObject _) implements Paint, JSObject { + external double get red; + external set red(num value); + external double get green; + external set green(num value); + external double get blue; + external set blue(num value); +} +extension type MyInterface._(JSObject _) implements JSObject { + external void doSomething(); + external bool isReady(); + external JSPromise<JSString> saveData([ConfigOptions config]); + external void log(String message, [LogLevel level]); + external String operator [](int index); + external String get name; + external set name(String value); + external int get id; +} +extension type MySubInterface._(JSObject _) implements JSObject { + external void reset(); + external String get info; + external set info(String value); +} +extension type MyException._(JSObject _) implements JSObject { + external factory MyException([String message, String name]); + + external String get name; + external String get message; + external int get code; +} +extension type ProtocolXError._(JSObject _) implements MyException, JSObject { + external factory ProtocolXError( + ProtocolXErrorOptions options, [ + String message, + ]); + + external int get errorCode; +} +extension type ProtocolXErrorOptions._(JSObject _) implements JSObject { + external factory ProtocolXErrorOptions({required int errorCode}); + + external int get errorCode; + external set errorCode(int value); +}
diff --git a/web_generator/test/integration/idl/interfaces_input.idl b/web_generator/test/integration/idl/interfaces_input.idl new file mode 100644 index 0000000..16864a9 --- /dev/null +++ b/web_generator/test/integration/idl/interfaces_input.idl
@@ -0,0 +1,66 @@ +enum LogLevel { + "debug", + "info", + "warn", + "error" +}; + + +interface ConfigOptions { + +}; + +interface EmptyInterface { }; + +interface BaseInterface { + undefined f(); + undefined g(); +}; + +interface Paint { }; + +interface SolidColor : Paint { + attribute double red; + attribute double green; + attribute double blue; +}; + +interface MyInterface { + attribute DOMString name; + readonly attribute unsigned long id; + + void doSomething(); + boolean isReady(); + + Promise<DOMString> saveData(optional ConfigOptions config); + + void log(DOMString message); + void log(DOMString message, LogLevel level); + + getter DOMString (unsigned long index); +}; + +interface MySubInterface { + attribute DOMString info; + void reset(); +}; + +[Exposed=*, + Serializable] +interface MyException { // but see below note about JavaScript binding + constructor(optional DOMString message = "", optional DOMString name = "Error"); + readonly attribute DOMString name; + readonly attribute DOMString message; + readonly attribute unsigned short code; +}; + +[Exposed=Window, Serializable] +interface ProtocolXError : MyException { + constructor(optional DOMString message = "", ProtocolXErrorOptions options); + + readonly attribute unsigned long long errorCode; +}; + +dictionary ProtocolXErrorOptions { + required [EnforceRange] unsigned long long errorCode; +};
diff --git a/web_generator/test/integration/idl/methods_expected.dart b/web_generator/test/integration/idl/methods_expected.dart new file mode 100644 index 0000000..699b495 --- /dev/null +++ b/web_generator/test/integration/idl/methods_expected.dart
@@ -0,0 +1,34 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +extension type MyMethodExamples._(JSObject _) implements JSObject { + external static bool isValid(String candidate); + external void reset(); + external void configure([bool force, String? label]); + external void log( + String message, [ + JSAny? extra1, + JSAny? extra2, + JSAny? extra3, + JSAny? extra4, + ]); + external void update(JSAny keyOrKeys, [String value]); + external JSPromise<JSString> fetchRemoteValue([String? endpoint]); + external void init(); +} +extension type Dimensions._(JSObject _) implements JSObject { + external int get width; + external set width(int value); + external int get height; + external set height(int value); +} +extension type Button._(JSObject _) implements JSObject { + external bool isMouseOver(); + external void setDimensions(JSAny sizeOrWidth, [int height]); +}
diff --git a/web_generator/test/integration/idl/methods_input.idl b/web_generator/test/integration/idl/methods_input.idl new file mode 100644 index 0000000..6e97649 --- /dev/null +++ b/web_generator/test/integration/idl/methods_input.idl
@@ -0,0 +1,34 @@ +interface MyMethodExamples { + void reset(); + + void configure(optional boolean force = false, optional DOMString? label = null); + + void log(DOMString message, any... extras); + + void update(DOMString key, DOMString value); + void update(sequence<DOMString> keys); + + Promise<DOMString> fetchRemoteValue(optional DOMString? endpoint); + + static boolean isValid(DOMString candidate); + + [Deprecated="Use configure() instead."] + void init(); +}; + +[Exposed=Window] +interface Dimensions { + attribute unsigned long width; + attribute unsigned long height; +}; + +[Exposed=Window] +interface Button { + + // An operation that takes no arguments and returns a boolean. + boolean isMouseOver(); + + // Overloaded operations. + undefined setDimensions(Dimensions size); + undefined setDimensions(unsigned long width, unsigned long height); +};
diff --git a/web_generator/test/integration/idl/mixin_interfaces_expected.dart b/web_generator/test/integration/idl/mixin_interfaces_expected.dart new file mode 100644 index 0000000..fcc2315 --- /dev/null +++ b/web_generator/test/integration/idl/mixin_interfaces_expected.dart
@@ -0,0 +1,17 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +extension type Position._(JSObject _) implements JSObject { + external void somethingMixedIn(); + external void moveTo(num newX, num newY); + external double get x; + external set x(num value); + external double get y; + external set y(num value); +}
diff --git a/web_generator/test/integration/idl/mixin_interfaces_input.idl b/web_generator/test/integration/idl/mixin_interfaces_input.idl new file mode 100644 index 0000000..cf4d22d --- /dev/null +++ b/web_generator/test/integration/idl/mixin_interfaces_input.idl
@@ -0,0 +1,23 @@ +interface mixin EventTargetMixin { + void addEventListener(DOMString type, EventListener callback, optional boolean capture = false); + void removeEventListener(DOMString type, EventListener callback, optional boolean capture = false); + boolean dispatchEvent(Event event); +}; + +interface mixin PositionMixin { + attribute double x; + attribute double y; + void moveTo(double newX, double newY); +}; + +interface Position {}; + +Position includes PositionMixin; + +interface mixin MyMixin { + void somethingMixedIn(); +}; + +partial interface mixin Position { + void somethingMixedIn(); +};
diff --git a/web_generator/test/integration/idl/namespaces_expected.dart b/web_generator/test/integration/idl/namespaces_expected.dart new file mode 100644 index 0000000..520b157 --- /dev/null +++ b/web_generator/test/integration/idl/namespaces_expected.dart
@@ -0,0 +1,28 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// ignore_for_file: unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +typedef Comparator = JSFunction; +typedef LogLevel = String; +extension type ConfigOptions._(JSObject _) implements JSObject {} +@JS() +external $MyLibrary get MyLibrary; +@JS('MyLibrary') +extension type $MyLibrary._(JSObject _) implements JSObject { + static const int VERSION_MAJOR = 1; + + static const int VERSION_MINOR = 4; + + external void initialize(); + external String stringify(JSObject input); + external JSPromise<JSAny?> fetchResource(String url); + external void log(String message, [LogLevel level]); + external ConfigOptions getDefaultConfig(); + external void forEach(JSArray<JSString> items, Comparator compareFn); +}
diff --git a/web_generator/test/integration/idl/namespaces_input.idl b/web_generator/test/integration/idl/namespaces_input.idl new file mode 100644 index 0000000..d8f1929 --- /dev/null +++ b/web_generator/test/integration/idl/namespaces_input.idl
@@ -0,0 +1,27 @@ +enum LogLevel { + "debug", + "info", + "warn", + "error" +}; + +callback Comparator = long (DOMString a, DOMString b); + +interface ConfigOptions { }; + +[Exposed=Window] +namespace MyLibrary { + const unsigned short VERSION_MAJOR = 1; + const unsigned short VERSION_MINOR = 4; + + void initialize(); + DOMString stringify(object input); + Promise<any> fetchResource(DOMString url); + + void log(DOMString message); + void log(DOMString message, LogLevel level); + + ConfigOptions getDefaultConfig(); + + void forEach(sequence<DOMString> items, Comparator compareFn); +};
diff --git a/web_generator/test/integration/idl/properties_expected.dart b/web_generator/test/integration/idl/properties_expected.dart new file mode 100644 index 0000000..d2f5dca --- /dev/null +++ b/web_generator/test/integration/idl/properties_expected.dart
@@ -0,0 +1,29 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +extension type PropertyTest._(JSObject _) implements JSObject { + static const bool DEBUG = false; + + external static int get testCount; + external String operator [](JSAny indexOrName); + external void operator []=(int index, String value); + external String get title; + external set title(String value); + external int get createdAt; + external String? get optionalName; + external set optionalName(String? value); + external JSArray<JSString> get tags; + external set tags(JSArray<JSString> value); + external JSUint8Array get binaryData; + external set binaryData(JSUint8Array value); + external JSAny? get flexibleValue; + external set flexibleValue(JSAny? value); + external String get testInfo; + external set testInfo(String value); +}
diff --git a/web_generator/test/integration/idl/properties_input.idl b/web_generator/test/integration/idl/properties_input.idl new file mode 100644 index 0000000..64ebe3a --- /dev/null +++ b/web_generator/test/integration/idl/properties_input.idl
@@ -0,0 +1,25 @@ +interface PropertyTest { + const boolean DEBUG = false; + + attribute DOMString title; + + static readonly attribute long testCount; + + readonly attribute unsigned long createdAt; + + attribute DOMString? optionalName; + + attribute sequence<DOMString> tags; + attribute Uint8Array binaryData; + + attribute (DOMString or long)? flexibleValue; + + getter DOMString (unsigned long index); + setter void (unsigned long index, DOMString value); + + getter DOMString (DOMString name); + + deleter boolean (DOMString name); + + stringifier attribute DOMString testInfo; +};
diff --git a/web_generator/test/integration/idl/typedefs_expected.dart b/web_generator/test/integration/idl/typedefs_expected.dart new file mode 100644 index 0000000..c3a7e27 --- /dev/null +++ b/web_generator/test/integration/idl/typedefs_expected.dart
@@ -0,0 +1,16 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +typedef ArrayBufferView = JSObject; +typedef BufferSource = JSObject; +typedef Timestamp = int; +extension type DataHandler._(JSObject _) implements JSObject { + external void setInput(BufferSource data); + external Timestamp getLastUpdated(); +}
diff --git a/web_generator/test/integration/idl/typedefs_input.idl b/web_generator/test/integration/idl/typedefs_input.idl new file mode 100644 index 0000000..11d13f4 --- /dev/null +++ b/web_generator/test/integration/idl/typedefs_input.idl
@@ -0,0 +1,15 @@ +typedef (Int8Array or Int16Array or Int32Array or + Uint8Array or Uint16Array or Uint32Array or Uint8ClampedArray or + BigInt64Array or BigUint64Array or + Float16Array or Float32Array or Float64Array or DataView) ArrayBufferView; + +typedef (ArrayBuffer or ArrayBufferView) BufferSource; +typedef (DOMString or USVString) StringInput; +typedef sequence<DOMString> StringList; +typedef unsigned long long Timestamp; + + +interface DataHandler { + void setInput(BufferSource data); + Timestamp getLastUpdated(); +};
diff --git a/web_generator/test/integration/idl/types_expected.dart b/web_generator/test/integration/idl/types_expected.dart new file mode 100644 index 0000000..c582396 --- /dev/null +++ b/web_generator/test/integration/idl/types_expected.dart
@@ -0,0 +1,18 @@ +// Generated from Web IDL definitions. + +// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment + +@JS() +library; + +import 'dart:js_interop'; + +extension type Global._(JSObject _) implements JSObject { + static const bool DEBUG = false; + + static const int LF = 10; + + static const int BIT_MASK = 64512; + + static const double AVOGADRO = 6.022e+23; +}
diff --git a/web_generator/test/integration/idl/types_input.idl b/web_generator/test/integration/idl/types_input.idl new file mode 100644 index 0000000..e5cd371 --- /dev/null +++ b/web_generator/test/integration/idl/types_input.idl
@@ -0,0 +1,6 @@ +interface Global { + const boolean DEBUG = false; + const octet LF = 10; + const unsigned long BIT_MASK = 0x0000fc00; + const double AVOGADRO = 6.022e23; +};
diff --git a/web_generator/test/integration/idl_test.dart b/web_generator/test/integration/idl_test.dart new file mode 100644 index 0000000..7aa6b9b --- /dev/null +++ b/web_generator/test/integration/idl_test.dart
@@ -0,0 +1,72 @@ +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:web_generator/src/cli.dart'; + +/// Actual test output can be found in `.dart_tool/idl` +void main() { + final bindingsGenPath = p.join('lib', 'src'); + group('IDL Integration Test', () { + final testGenFolder = p.join('test', 'integration', 'idl'); + final inputDir = Directory(testGenFolder); + + setUpAll(() async { + // set up npm + await runProc('npm', ['install'], workingDirectory: bindingsGenPath); + + // compile file + await compileDartMain(dir: bindingsGenPath); + }); + + for (final inputFile in inputDir + .listSync() + .whereType<File>() + .where((f) => p.basenameWithoutExtension(f.path).endsWith('_input'))) { + final inputFileName = p.basenameWithoutExtension(inputFile.path); + final inputName = inputFileName.replaceFirst('_input', ''); + + final outputActualPath = + p.join('.dart_tool', 'idl', '${inputName}_actual.dart'); + final outputExpectedPath = + p.join(testGenFolder, '${inputName}_expected.dart'); + + test(inputName, () async { + final inputFilePath = p.relative(inputFile.path, from: bindingsGenPath); + final outFilePath = p.relative(outputActualPath, from: bindingsGenPath); + // run the entrypoint + await runProc( + 'node', + [ + 'main.mjs', + '--input=$inputFilePath', + '--output=${p.dirname(outFilePath)}', + '--idl' + ], + workingDirectory: bindingsGenPath); + + await File( + p.join(p.dirname(outputActualPath), '${inputName}_input.dart')) + .rename(outputActualPath); + + // read files + final expectedOutput = await File(outputExpectedPath).readAsString(); + final actualOutput = await File(outputActualPath).readAsString(); + + expect(actualOutput, expectedOutput); + }); + + tearDownAll(() { + inputDir + .listSync() + .whereType<File>() + .where( + (f) => p.basenameWithoutExtension(f.path).endsWith('_actual')) + .forEach((f) => f.deleteSync()); + }); + } + }); +}
diff --git a/web_generator/test/integration/interop_gen/functions_expected.dart b/web_generator/test/integration/interop_gen/functions_expected.dart new file mode 100644 index 0000000..4c8c2f8 --- /dev/null +++ b/web_generator/test/integration/interop_gen/functions_expected.dart
@@ -0,0 +1,47 @@ +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:js_interop' as _i1; + +@_i1.JS() +external String greetUser(String name); +@_i1.JS() +external void logMessages( + _i1.JSArray<_i1.JSString> messages, [ + _i1.JSArray<_i1.JSString> messages2, + _i1.JSArray<_i1.JSString> messages3, + _i1.JSArray<_i1.JSString> messages4, +]); +@_i1.JS() +external _i1.JSPromise<U> delay<U extends _i1.JSAny?>(num ms, [U? returnValue]); +@_i1.JS() +external _i1.JSArray<_i1.JSNumber> toArray(num a); +@_i1.JS() +external double square(num a); +@_i1.JS() +external double pow(num a); +@_i1.JS('pow') +external double pow$1(num a, num power); +@_i1.JS('toArray') +external _i1.JSArray<_i1.JSString> toArray$1(String a); +@_i1.JS() +external _i1.JSObject createUser(String name, [num? age, String? role]); +@_i1.JS() +external T firstElement<T extends _i1.JSAny?>(_i1.JSArray<T> arr); +@_i1.JS() +external void throwError(String msg); +@_i1.JS() +external _i1.JSArray<T> wrapInArray<T extends _i1.JSAny?>(T value); +@_i1.JS() +external T identity<T extends _i1.JSAny?>(T value); +@_i1.JS() +external void someFunction<A extends _i1.JSAny?>(_i1.JSArray<A> arr); +@_i1.JS('someFunction') +external B someFunction$1<A extends _i1.JSAny?, B extends _i1.JSAny?>( + _i1.JSArray<A> arr, +); +@_i1.JS() +external T logTuple<T extends _i1.JSArray<_i1.JSAny?>>( + T args, [ + T args2, + T args3, + T args4, +]);
diff --git a/web_generator/test/integration/interop_gen/functions_input.d.ts b/web_generator/test/integration/interop_gen/functions_input.d.ts new file mode 100644 index 0000000..6fc8665 --- /dev/null +++ b/web_generator/test/integration/interop_gen/functions_input.d.ts
@@ -0,0 +1,16 @@ +export declare function greetUser(name: string): string; +export declare function logMessages(...messages: string[]): void; +export declare function delay<U>(ms: number, returnValue?: U): Promise<U>; +export declare function toArray(a: number): number[]; +export declare function square(a: number): number; +export declare function pow(a: number): number; +export declare function pow(a: number, power: number): number; +export declare function toArray(a: string): string[]; +export declare function createUser(name: string, age?: number, role?: string): object; +export declare function firstElement<T>(arr: T[]): T; +export declare function throwError(msg: string): void; +export declare function wrapInArray<T>(value: T): T[]; +export declare function identity<T = string>(value: T): T; +export declare function someFunction<A>(arr: A[]): undefined; +export declare function someFunction<A, B>(arr: A[]): B; +export declare function logTuple<T extends any[]>(...args: T): T;
diff --git a/web_generator/test/integration/interop_gen/variables_expected.dart b/web_generator/test/integration/interop_gen/variables_expected.dart index 2609c67..1df172a 100644 --- a/web_generator/test/integration/interop_gen/variables_expected.dart +++ b/web_generator/test/integration/interop_gen/variables_expected.dart
@@ -2,11 +2,11 @@ import 'dart:js_interop' as _i1; @_i1.JS() -external int counter; +external double counter; @_i1.JS() external String get appName; @_i1.JS() -external int globalCounter; +external double globalCounter; @_i1.JS() external _i1.JSObject globalObject; @_i1.JS() @@ -14,18 +14,22 @@ @_i1.JS() external String username; @_i1.JS() -external int get foo; +external double get foo; @_i1.JS() -external int get bar; +external double get bar; @_i1.JS() -external int free; +external double free; @_i1.JS() -external int dom; +external double dom; @_i1.JS() external String fred; @_i1.JS() external String doctor; @_i1.JS() -external _i1.JSAny something; +external _i1.JSAny? something; @_i1.JS() external _i1.JSAny? get maybeValue; +@_i1.JS() +external _i1.JSArray<_i1.JSString> get names; +@_i1.JS() +external _i1.JSArray<_i1.JSString> get newNames;
diff --git a/web_generator/test/integration/interop_gen/variables_input.d.ts b/web_generator/test/integration/interop_gen/variables_input.d.ts index 933449b..1bc7d05 100644 --- a/web_generator/test/integration/interop_gen/variables_input.d.ts +++ b/web_generator/test/integration/interop_gen/variables_input.d.ts
@@ -9,3 +9,5 @@ export declare let fred: string, doctor: string; export declare let something: any; export declare const maybeValue: unknown; +export declare const names: string[]; +export declare const newNames: Array<string>;