diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 2a2c38a5..00afaa49 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,4 +1,4 @@ -# Created with package:mono_repo v6.6.2 +# Created with package:mono_repo v6.6.3 name: Dart CI on: push: @@ -36,7 +36,7 @@ jobs: name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: mono_repo self validate - run: dart pub global activate mono_repo 6.6.2 + run: dart pub global activate mono_repo 6.6.3 - name: mono_repo self validate run: dart pub global run mono_repo generate --validate job_002: @@ -340,7 +340,7 @@ jobs: - job_004 - job_005 job_010: - name: "generate_and_analyze; Dart dev; PKG: web_generator; `dart pub -C ../web get && dart bin/update_bindings.dart && dart analyze --fatal-infos ../web`" + name: "generate_and_analyze; Dart dev; PKG: web_generator; `dart pub -C ../web get && dart bin/update_idl_bindings.dart && dart analyze --fatal-infos ../web`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies @@ -365,8 +365,8 @@ jobs: run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: web_generator - - name: "web_generator; dart pub -C ../web get && dart bin/update_bindings.dart && dart analyze --fatal-infos ../web" - run: "dart pub -C ../web get && dart bin/update_bindings.dart && dart analyze --fatal-infos ../web" + - name: "web_generator; dart pub -C ../web get && dart bin/update_idl_bindings.dart && dart analyze --fatal-infos ../web" + run: "dart pub -C ../web get && dart bin/update_idl_bindings.dart && dart analyze --fatal-infos ../web" if: "always() && steps.web_generator_pub_upgrade.conclusion == 'success'" working-directory: web_generator needs: @@ -380,7 +380,7 @@ jobs: - job_008 - job_009 job_011: - name: "generate_all_and_analyze; Dart dev; PKG: web_generator; `dart pub -C ../web get && dart bin/update_bindings.dart --generate-all && dart analyze --fatal-infos ../web`" + name: "generate_all_and_analyze; Dart dev; PKG: web_generator; `dart pub -C ../web get && dart bin/update_idl_bindings.dart --generate-all && dart analyze --fatal-infos ../web`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies @@ -405,8 +405,8 @@ jobs: run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: web_generator - - name: "web_generator; dart pub -C ../web get && dart bin/update_bindings.dart --generate-all && dart analyze --fatal-infos ../web" - run: "dart pub -C ../web get && dart bin/update_bindings.dart --generate-all && dart analyze --fatal-infos ../web" + - name: "web_generator; dart pub -C ../web get && dart bin/update_idl_bindings.dart --generate-all && dart analyze --fatal-infos ../web" + run: "dart pub -C ../web get && dart bin/update_idl_bindings.dart --generate-all && dart analyze --fatal-infos ../web" if: "always() && steps.web_generator_pub_upgrade.conclusion == 'success'" working-directory: web_generator needs: diff --git a/.gitignore b/.gitignore index e4377b96..e58fb81e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ pubspec.lock web_generator/lib/src/*.js web_generator/lib/src/*.js.* -web_generator/lib/src/node_modules/ +web_generator/lib/src/node_modules/ \ No newline at end of file diff --git a/tool/ci.sh b/tool/ci.sh index 7ab0b8e3..a3e8ee40 100755 --- a/tool/ci.sh +++ b/tool/ci.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Created with package:mono_repo v6.6.2 +# Created with package:mono_repo v6.6.3 # Support built in commands on windows out of the box. @@ -72,12 +72,12 @@ for PKG in ${PKGS}; do dart fix --compare-to-golden test_fixes || EXIT_CODE=$? ;; command_1) - echo 'dart pub -C ../web get && dart bin/update_bindings.dart && dart analyze --fatal-infos ../web' - dart pub -C ../web get && dart bin/update_bindings.dart && dart analyze --fatal-infos ../web || EXIT_CODE=$? + echo 'dart pub -C ../web get && dart bin/update_idl_bindings.dart && dart analyze --fatal-infos ../web' + dart pub -C ../web get && dart bin/update_idl_bindings.dart && dart analyze --fatal-infos ../web || EXIT_CODE=$? ;; command_2) - echo 'dart pub -C ../web get && dart bin/update_bindings.dart --generate-all && dart analyze --fatal-infos ../web' - dart pub -C ../web get && dart bin/update_bindings.dart --generate-all && dart analyze --fatal-infos ../web || EXIT_CODE=$? + echo 'dart pub -C ../web get && dart bin/update_idl_bindings.dart --generate-all && dart analyze --fatal-infos ../web' + dart pub -C ../web get && dart bin/update_idl_bindings.dart --generate-all && dart analyze --fatal-infos ../web || EXIT_CODE=$? ;; format) echo 'dart format --output=none --set-exit-if-changed .' diff --git a/web_generator/README.md b/web_generator/README.md index d750a827..2b826bed 100644 --- a/web_generator/README.md +++ b/web_generator/README.md @@ -12,7 +12,7 @@ run on Node. To regenerate `web` bindings from the current IDL versions, run: ```shell -dart bin/update_bindings.dart +dart bin/update_idl_bindings.dart ``` ## Update to the latest Web IDL versions and regenerate @@ -20,11 +20,11 @@ dart bin/update_bindings.dart To re-generate the package from newer IDL versions, you can either run: ```shell -dart bin/update_bindings.dart --update +dart bin/update_idl_bindings.dart --update ``` or, manually edit `lib/src/package.json` to use specific IDL versions, and -re-run `update_bindings.dart`. +re-run `update_idl_bindings.dart`. ### Updating the dartdoc info from MDN @@ -38,7 +38,7 @@ dart bin/scrape_mdn.dart That will collect the MDN documentation into `third_party/mdn/mdn.json`; changes to that file should be committed to git. You'll need to run -`update_bindings.dart` to produce Dart code using the updated documentation. +`update_idl_bindings.dart` to produce Dart code using the updated documentation. ## Generation conventions @@ -80,7 +80,7 @@ definitions: To ignore the compatibility data and emit all members, run: ```shell -dart bin/update_bindings.dart --generate-all +dart bin/update_idl_bindings.dart --generate-all ``` This is useful if you want to avoid having to write bindings manually for some @@ -90,10 +90,10 @@ experimental and non-standard APIs. Based on: - + | Item | Version | | --- | --: | | `@webref/css` | [6.20.3](https://www.npmjs.com/package/@webref/css/v/6.20.3) | | `@webref/elements` | [2.4.0](https://www.npmjs.com/package/@webref/elements/v/2.4.0) | | `@webref/idl` | [3.60.1](https://www.npmjs.com/package/@webref/idl/v/3.60.1) | - + diff --git a/web_generator/bin/gen_interop_bindings.dart b/web_generator/bin/gen_interop_bindings.dart new file mode 100644 index 00000000..10e74479 --- /dev/null +++ b/web_generator/bin/gen_interop_bindings.dart @@ -0,0 +1,100 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:io/ansi.dart' as ansi; +import 'package:io/io.dart'; +import 'package:path/path.dart' as p; +import 'package:web_generator/src/cli.dart'; + +void main(List arguments) async { + final ArgResults argResult; + + try { + argResult = _parser.parse(arguments); + } on FormatException catch (e) { + print(''' +${ansi.lightRed.wrap(e.message)} + +$_usage'''); + exitCode = ExitCode.usage.code; + return; + } + + if (argResult['help'] as bool) { + print(_usage); + return; + } + + if (argResult.rest.isEmpty) { + print(''' +${ansi.lightRed.wrap('At least one argument is needed')} + +$_usage'''); + exitCode = ExitCode.usage.code; + return; + } + + assert(p.fromUri(Platform.script).endsWith(_thisScript.toFilePath())); + + // Run `npm install` or `npm update` as needed. + final update = argResult['update'] as bool; + await runProc( + 'npm', + [update ? 'update' : 'install'], + workingDirectory: bindingsGeneratorPath, + ); + + final contextFile = await createJsTypeSupertypeContext(); + + // Compute JS type supertypes for union calculation in translator. + await generateJsTypeSupertypes(contextFile.path); + + if (argResult['compile'] as bool) { + // Compile Dart to Javascript. + await compileDartMain(); + } + + final inputFile = argResult.rest.first; + final outputFile = argResult['output'] as String? ?? + p.join(p.current, inputFile.replaceAll('.d.ts', '.dart')); + final configFile = + argResult['config'] as String? ?? p.join(p.current, 'webgen.yaml'); + final relativeOutputPath = + p.relative(outputFile, from: bindingsGeneratorPath); + // Run app with `node`. + await runProc( + 'node', + [ + 'main.mjs', + '--declaration', + '--input=${p.relative(inputFile, from: bindingsGeneratorPath)}', + '--output=$relativeOutputPath', + '--config=$configFile' + ], + workingDirectory: bindingsGeneratorPath, + ); + + await contextFile.delete(); + return; +} + +final _thisScript = Uri.parse('bin/gen_interop_bindings.dart'); + +final _usage = ''' +${ansi.styleBold.wrap('Dart Interop Gen')}: +$_thisScript dts <.d.ts file> [options] + +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: 'The output path to generate the Dart interface code') + ..addOption('config', + hide: true, + abbr: 'c', + help: 'The configuration file to use for this tool (NOTE: Unimplemented)') + ..addFlag('help', negatable: false); diff --git a/web_generator/bin/update_bindings.dart b/web_generator/bin/update_idl_bindings.dart similarity index 54% rename from web_generator/bin/update_bindings.dart rename to web_generator/bin/update_idl_bindings.dart index 861587bb..019b422a 100644 --- a/web_generator/bin/update_bindings.dart +++ b/web_generator/bin/update_idl_bindings.dart @@ -2,19 +2,15 @@ // 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:convert'; import 'dart:io'; -import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/element/element2.dart'; -import 'package:analyzer/dart/element/type.dart'; import 'package:args/args.dart'; import 'package:io/ansi.dart' as ansi; import 'package:io/io.dart'; import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as p; +import 'package:web_generator/src/cli.dart'; void main(List arguments) async { final ArgResults argResult; @@ -39,32 +35,21 @@ $_usage'''); // Run `npm install` or `npm update` as needed. final update = argResult['update'] as bool; - await _runProc( + await runProc( 'npm', [update ? 'update' : 'install'], - workingDirectory: _bindingsGeneratorPath, + workingDirectory: bindingsGeneratorPath, ); + final contextFile = await createJsTypeSupertypeContext(); + // Compute JS type supertypes for union calculation in translator. - await _generateJsTypeSupertypes(); + await generateJsTypeSupertypes(contextFile.path); if (argResult['compile'] as bool) { final webPkgLangVersion = await _webPackageLanguageVersion(_webPackagePath); // Compile Dart to Javascript. - await _runProc( - Platform.executable, - [ - 'compile', - 'js', - '--enable-asserts', - '--server-mode', - '-DlanguageVersion=$webPkgLangVersion', - 'dart_main.dart', - '-o', - 'dart_main.js', - ], - workingDirectory: _bindingsGeneratorPath, - ); + await compileDartMain(langVersion: webPkgLangVersion); } // Determine the set of previously generated files. @@ -82,14 +67,15 @@ $_usage'''); // Run app with `node`. final generateAll = argResult['generate-all'] as bool; - await _runProc( + await runProc( 'node', [ 'main.mjs', - '--output-directory=${p.join(_webPackagePath, 'lib', 'src')}', + '--idl', + '--output=${p.join(_webPackagePath, 'lib', 'src')}', if (generateAll) '--generate-all', ], - workingDirectory: _bindingsGeneratorPath, + workingDirectory: bindingsGeneratorPath, ); // Delete previously generated files that have not been updated. @@ -100,6 +86,9 @@ $_usage'''); } } + // delete context file + await contextFile.delete(); + // Update readme. final readmeFile = File(p.normalize(p.fromUri(Platform.script.resolve('../README.md')))); @@ -151,8 +140,7 @@ final _webPackagePath = p.fromUri(Platform.script.resolve('../../web')); String _packageLockVersion(String package) { final packageLockData = jsonDecode( - File(p.join(_bindingsGeneratorPath, 'package-lock.json')) - .readAsStringSync(), + File(p.join(bindingsGeneratorPath, 'package-lock.json')).readAsStringSync(), ) as Map; final packages = packageLockData['packages'] as Map; @@ -160,13 +148,11 @@ String _packageLockVersion(String package) { return webRefIdl['version'] as String; } -final _bindingsGeneratorPath = p.fromUri(Platform.script.resolve('../lib/src')); - const _webRefCss = '@webref/css'; const _webRefElements = '@webref/elements'; const _webRefIdl = '@webref/idl'; -final _thisScript = Uri.parse('bin/update_bindings.dart'); +final _thisScript = Uri.parse('bin/update_idl_bindings.dart'); final _scriptPOSIXPath = _thisScript.toFilePath(windows: false); final _startComment = @@ -174,92 +160,24 @@ final _startComment = final _endComment = ''; -Future _runProc( - String executable, - List arguments, { - required String workingDirectory, -}) async { - print(ansi.styleBold.wrap(['*', executable, ...arguments].join(' '))); - final proc = await Process.start( - executable, - arguments, - mode: ProcessStartMode.inheritStdio, - runInShell: Platform.isWindows, - workingDirectory: workingDirectory, - ); - final procExit = await proc.exitCode; - if (procExit != 0) { - throw ProcessException(executable, arguments, 'Process failed', procExit); - } -} - -// Generates a map of the JS type hierarchy defined in `dart:js_interop` that is -// used by the translator to handle IDL types. -Future _generateJsTypeSupertypes() async { - // Use a file that uses `dart:js_interop` for analysis. - final contextCollection = AnalysisContextCollection( - includedPaths: [p.join(_webPackagePath, 'lib', 'src', 'dom.dart')]); - final dartJsInterop = (await contextCollection.contexts.single.currentSession - .getLibraryByUri('dart:js_interop') as LibraryElementResult) - .element2; - final definedNames = dartJsInterop.exportNamespace.definedNames2; - // `SplayTreeMap` to avoid moving types around in `dart:js_interop` affecting - // the code generation. - final jsTypeSupertypes = SplayTreeMap(); - for (final name in definedNames.keys) { - final element = definedNames[name]; - if (element is ExtensionTypeElement2) { - // JS types are any extension type that starts with 'JS' in - // `dart:js_interop`. - bool isJSType(InterfaceElement2 element) => - element is ExtensionTypeElement2 && - element.library2 == dartJsInterop && - element.name3!.startsWith('JS'); - if (!isJSType(element)) continue; - - String? parentJsType; - final supertype = element.supertype; - final immediateSupertypes = [ - if (supertype != null) supertype, - ...element.interfaces, - ]..removeWhere((supertype) => supertype.isDartCoreObject); - // We should have at most one non-trivial supertype. - assert(immediateSupertypes.length <= 1); - for (final supertype in immediateSupertypes) { - if (isJSType(supertype.element3)) { - parentJsType = "'${supertype.element3.name3!}'"; - } - } - // Ensure that the hierarchy forms a tree. - assert((parentJsType == null) == (name == 'JSAny')); - jsTypeSupertypes["'$name'"] = parentJsType; - } - } +final _usage = ''' +Global Options: +${_parser.usage} - final jsTypeSupertypesScript = ''' -// 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. +${ansi.styleBold.wrap('IDL Command')}: $_thisScript idl [options] -// Updated by $_scriptPOSIXPath. Do not modify by hand. +Usage: +${_parser.commands['idl']?.usage} -const Map jsTypeSupertypes = { -${jsTypeSupertypes.entries.map((e) => " ${e.key}: ${e.value},").join('\n')} -}; -'''; - final jsTypeSupertypesPath = - p.join(_bindingsGeneratorPath, 'js_type_supertypes.dart'); - await File(jsTypeSupertypesPath).writeAsString(jsTypeSupertypesScript); -} +${ansi.styleBold.wrap('Typescript Gen Command')}: $_thisScript dts <.d.ts file> [options] -final _usage = ''' Usage: -${_parser.usage}'''; +${_parser.commands['dts']?.usage}'''; final _parser = ArgParser() + ..addFlag('help', negatable: false, help: 'Show help information') ..addFlag('update', abbr: 'u', help: 'Update npm dependencies') ..addFlag('compile', defaultsTo: true) - ..addFlag('help', negatable: false) ..addFlag('generate-all', negatable: false, help: 'Generate bindings for all IDL definitions, including experimental ' diff --git a/web_generator/lib/src/bcd.dart b/web_generator/lib/src/bcd.dart index 3889e37b..d7fe0dce 100644 --- a/web_generator/lib/src/bcd.dart +++ b/web_generator/lib/src/bcd.dart @@ -7,7 +7,7 @@ import 'dart:js_interop'; import 'package:path/path.dart' as p; -import 'filesystem_api.dart'; +import 'js/filesystem_api.dart'; /// A class to read from the browser-compat-data files and parse interface and /// property status (standards track, experimental, deprecated) and supported diff --git a/web_generator/lib/src/cli.dart b/web_generator/lib/src/cli.dart new file mode 100644 index 00000000..dec055a1 --- /dev/null +++ b/web_generator/lib/src/cli.dart @@ -0,0 +1,116 @@ +import 'dart:collection'; +import 'dart:io'; +import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/element/element2.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:io/ansi.dart' as ansi; + +import 'package:path/path.dart' as p; + +final bindingsGeneratorPath = p.fromUri(Platform.script.resolve('../lib/src')); + +Future compileDartMain({String? langVersion}) async { + await runProc( + Platform.executable, + [ + 'compile', + 'js', + '--enable-asserts', + '--server-mode', + if (langVersion != null) '-DlanguageVersion=$langVersion', + 'dart_main.dart', + '-o', + 'dart_main.js', + ], + workingDirectory: bindingsGeneratorPath, + ); +} + +Future runProc(String executable, List arguments, + {required String workingDirectory, bool detached = false}) async { + print(ansi.styleBold.wrap(['*', executable, ...arguments].join(' '))); + final proc = await Process.start( + executable, + arguments, + mode: detached ? ProcessStartMode.detached : ProcessStartMode.inheritStdio, + runInShell: Platform.isWindows, + workingDirectory: workingDirectory, + ); + final procExit = await proc.exitCode; + if (procExit != 0) { + throw ProcessException(executable, arguments, 'Process failed', procExit); + } +} + +Future createJsTypeSupertypeContext() async { + final contextFile = + await File(p.join(bindingsGeneratorPath, '_js_supertypes_src.dart')) + .create(); + await contextFile.writeAsString(''' +import 'dart:js_interop'; + +@JS() +external JSPromise get promise; +'''); + return contextFile; +} + +/// Generates a map of the JS type hierarchy defined in `dart:js_interop` that's +/// used by both translators. +Future generateJsTypeSupertypes(String contextFile) async { + // Use a file that uses `dart:js_interop` for analysis. + final contextCollection = + AnalysisContextCollection(includedPaths: [contextFile]); + final dartJsInterop = (await contextCollection.contexts.single.currentSession + .getLibraryByUri('dart:js_interop') as LibraryElementResult) + .element2; + final definedNames = dartJsInterop.exportNamespace.definedNames2; + // `SplayTreeMap` to avoid moving types around in `dart:js_interop` affecting + // the code generation. + final jsTypeSupertypes = SplayTreeMap(); + for (final name in definedNames.keys) { + final element = definedNames[name]; + if (element is ExtensionTypeElement2) { + // JS types are any extension type that starts with 'JS' in + // `dart:js_interop`. + bool isJSType(InterfaceElement2 element) => + element is ExtensionTypeElement2 && + element.library2 == dartJsInterop && + element.name3!.startsWith('JS'); + if (!isJSType(element)) continue; + + String? parentJsType; + final supertype = element.supertype; + final immediateSupertypes = [ + if (supertype != null) supertype, + ...element.interfaces, + ]..removeWhere((supertype) => supertype.isDartCoreObject); + // We should have at most one non-trivial supertype. + assert(immediateSupertypes.length <= 1); + for (final supertype in immediateSupertypes) { + if (isJSType(supertype.element3)) { + parentJsType = "'${supertype.element3.name3!}'"; + } + } + // Ensure that the hierarchy forms a tree. + assert((parentJsType == null) == (name == 'JSAny')); + jsTypeSupertypes["'$name'"] = parentJsType; + } + } + + final jsTypeSupertypesScript = ''' +// 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. + +// Do not modify by hand. + +const Map jsTypeSupertypes = { +${jsTypeSupertypes.entries.map((e) => " ${e.key}: ${e.value},").join('\n')} +}; +'''; + final jsTypeSupertypesPath = + p.join(bindingsGeneratorPath, 'js_type_supertypes.dart'); + await File(jsTypeSupertypesPath).writeAsString(jsTypeSupertypesScript); +} diff --git a/web_generator/lib/src/dart_main.dart b/web_generator/lib/src/dart_main.dart index 1304eb51..e0e44808 100644 --- a/web_generator/lib/src/dart_main.dart +++ b/web_generator/lib/src/dart_main.dart @@ -9,8 +9,10 @@ import 'package:code_builder/code_builder.dart' as code; import 'package:dart_style/dart_style.dart'; import 'package:pub_semver/pub_semver.dart'; -import 'filesystem_api.dart'; +import 'dts/parser.dart'; +import 'dts/transform.dart'; import 'generate_bindings.dart'; +import 'js/filesystem_api.dart'; import 'util.dart'; // Generates DOM bindings for Dart. @@ -24,16 +26,44 @@ void main(List args) async { if (languageVersionString.isEmpty) { languageVersionString = DartFormatter.latestLanguageVersion.toString(); } - final ArgResults argResult; - argResult = _parser.parse(args); - await _generateAndWriteBindings( - outputDirectory: argResult['output-directory'] as String, - generateAll: argResult['generate-all'] as bool, - languageVersion: Version.parse(languageVersionString), - ); + + final argResult = _parser.parse(args); + + if (argResult.wasParsed('idl')) { + await generateIDLBindings( + outputDirectory: argResult['output'] as String, + generateAll: argResult['generate-all'] as bool, + languageVersion: Version.parse(languageVersionString), + ); + } else if (argResult.wasParsed('declaration')) { + await generateJSInteropBindings( + inputs: argResult['input'] as Iterable, + output: argResult['output'] as String, + languageVersion: Version.parse(languageVersionString), + ); + } +} + +// TODO(nikeokoronkwo): Add support for configuration +Future generateJSInteropBindings({ + required Iterable inputs, + required String output, + required Version languageVersion, +}) async { + // generate + final jsDeclarations = parseDeclarationFiles(inputs); + + // transform declarations + final dartDeclarations = transformDeclarations(jsDeclarations); + + // generate + final generatedCode = dartDeclarations.generate(); + + // write code to file + fs.writeFileSync(output.toJS, generatedCode.toJS); } -Future _generateAndWriteBindings({ +Future generateIDLBindings({ required String outputDirectory, required bool generateAll, required Version languageVersion, @@ -66,9 +96,19 @@ String _emitLibrary(code.Library library, Version languageVersion) { } final _parser = ArgParser() - ..addOption('output-directory', - mandatory: true, help: 'Directory where bindings will be generated to.') + ..addFlag('idl', negatable: false) + ..addFlag('declaration', negatable: false) + ..addOption('output', + mandatory: true, + abbr: 'o', + help: 'Output where bindings will be generated to ' + '(directory for IDL, file for TS Declarations)') ..addFlag('generate-all', negatable: false, - help: 'Generate bindings for all IDL definitions, including experimental ' - 'and non-standard APIs.'); + help: '[IDL] Generate bindings for all IDL definitions, ' + 'including experimental and non-standard APIs.') + ..addMultiOption('input', + abbr: 'i', + help: '[TS Declarations] The input file to read and generate types for') + ..addOption('config', + abbr: 'c', hide: true, valueHelp: '[file].yaml', help: 'Configuration'); diff --git a/web_generator/lib/src/doc_provider.dart b/web_generator/lib/src/doc_provider.dart index 2743c170..c83a59c9 100644 --- a/web_generator/lib/src/doc_provider.dart +++ b/web_generator/lib/src/doc_provider.dart @@ -7,8 +7,8 @@ import 'dart:js_interop'; import 'package:path/path.dart' as p; -import 'filesystem_api.dart'; import 'formatting.dart'; +import 'js/filesystem_api.dart'; class DocProvider { static DocProvider create() { diff --git a/web_generator/lib/src/dts/config.dart b/web_generator/lib/src/dts/config.dart new file mode 100644 index 00000000..4e9103b4 --- /dev/null +++ b/web_generator/lib/src/dts/config.dart @@ -0,0 +1 @@ +final class Config {} diff --git a/web_generator/lib/src/dts/parser.dart b/web_generator/lib/src/dts/parser.dart new file mode 100644 index 00000000..439ef2b5 --- /dev/null +++ b/web_generator/lib/src/dts/parser.dart @@ -0,0 +1,2 @@ +// TODO(nikeokoronkwo): Implement this function +dynamic parseDeclarationFiles(Iterable files) {} diff --git a/web_generator/lib/src/dts/transform.dart b/web_generator/lib/src/dts/transform.dart new file mode 100644 index 00000000..1b495b2a --- /dev/null +++ b/web_generator/lib/src/dts/transform.dart @@ -0,0 +1,9 @@ +class DeclarationMap { + String generate() { + throw UnimplementedError(); + } +} + +DeclarationMap transformDeclarations(Object? jsDeclarations) { + throw UnimplementedError(); +} diff --git a/web_generator/lib/src/generate_bindings.dart b/web_generator/lib/src/generate_bindings.dart index af299862..36324f71 100644 --- a/web_generator/lib/src/generate_bindings.dart +++ b/web_generator/lib/src/generate_bindings.dart @@ -4,12 +4,12 @@ import 'dart:js_interop'; +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'; -import 'webidl_api.dart' as webidl; -import 'webref_css_api.dart'; -import 'webref_elements_api.dart'; -import 'webref_idl_api.dart'; /// Generate CSS property names for setting / getting CSS properties in JS. Future> _generateCSSStyleDeclarations() async { diff --git a/web_generator/lib/src/filesystem_api.dart b/web_generator/lib/src/js/filesystem_api.dart similarity index 100% rename from web_generator/lib/src/filesystem_api.dart rename to web_generator/lib/src/js/filesystem_api.dart diff --git a/web_generator/lib/src/js/typescript.dart b/web_generator/lib/src/js/typescript.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/web_generator/lib/src/js/typescript.dart @@ -0,0 +1 @@ + diff --git a/web_generator/lib/src/js/typescript.types.dart b/web_generator/lib/src/js/typescript.types.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/web_generator/lib/src/js/typescript.types.dart @@ -0,0 +1 @@ + diff --git a/web_generator/lib/src/webidl_api.dart b/web_generator/lib/src/js/webidl_api.dart similarity index 100% rename from web_generator/lib/src/webidl_api.dart rename to web_generator/lib/src/js/webidl_api.dart diff --git a/web_generator/lib/src/webref_css_api.dart b/web_generator/lib/src/js/webref_css_api.dart similarity index 100% rename from web_generator/lib/src/webref_css_api.dart rename to web_generator/lib/src/js/webref_css_api.dart diff --git a/web_generator/lib/src/webref_elements_api.dart b/web_generator/lib/src/js/webref_elements_api.dart similarity index 100% rename from web_generator/lib/src/webref_elements_api.dart rename to web_generator/lib/src/js/webref_elements_api.dart diff --git a/web_generator/lib/src/webref_idl_api.dart b/web_generator/lib/src/js/webref_idl_api.dart similarity index 100% rename from web_generator/lib/src/webref_idl_api.dart rename to web_generator/lib/src/js/webref_idl_api.dart diff --git a/web_generator/lib/src/js_type_supertypes.dart b/web_generator/lib/src/js_type_supertypes.dart index 9878f768..bfacf0b8 100644 --- a/web_generator/lib/src/js_type_supertypes.dart +++ b/web_generator/lib/src/js_type_supertypes.dart @@ -2,7 +2,7 @@ // 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. -// Updated by bin/update_bindings.dart. Do not modify by hand. +// Do not modify by hand. const Map jsTypeSupertypes = { 'JSAny': null, diff --git a/web_generator/lib/src/main.mjs b/web_generator/lib/src/main.mjs index 697c6caa..23550452 100644 --- a/web_generator/lib/src/main.mjs +++ b/web_generator/lib/src/main.mjs @@ -8,6 +8,7 @@ import { createRequire } from 'module'; import * as css from '@webref/css'; import * as elements from '@webref/elements'; import * as idl from '@webref/idl'; +import * as ts from 'typescript'; const require = createRequire(import.meta.url); @@ -18,6 +19,7 @@ globalThis.css = css; globalThis.elements = elements; globalThis.fs = fs; globalThis.idl = idl; +globalThis.ts = ts; globalThis.location = { href: `file://${process.cwd()}/` } globalThis.dartMainRunner = async function (main, args) { diff --git a/web_generator/lib/src/package-lock.json b/web_generator/lib/src/package-lock.json index dfe46c0a..e6d9b3d6 100644 --- a/web_generator/lib/src/package-lock.json +++ b/web_generator/lib/src/package-lock.json @@ -12,7 +12,8 @@ "@mdn/browser-compat-data": "^5.5.2", "@webref/css": "^6.11.0", "@webref/elements": "^2.2.2", - "@webref/idl": "^3.43.1" + "@webref/idl": "^3.43.1", + "typescript": "^5.8.3" }, "devDependencies": { "webidl2": "^24.2.2" @@ -79,6 +80,19 @@ "node": ">=0.10.0" } }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/webidl2": { "version": "24.4.1", "resolved": "https://registry.npmjs.org/webidl2/-/webidl2-24.4.1.tgz", diff --git a/web_generator/lib/src/package.json b/web_generator/lib/src/package.json index a3049889..58ac69ed 100644 --- a/web_generator/lib/src/package.json +++ b/web_generator/lib/src/package.json @@ -12,9 +12,10 @@ "@mdn/browser-compat-data": "^5.5.2", "@webref/css": "^6.11.0", "@webref/elements": "^2.2.2", - "@webref/idl": "^3.43.1" + "@webref/idl": "^3.43.1", + "typescript": "^5.8.3" }, "devDependencies": { "webidl2": "^24.2.2" } -} \ No newline at end of file +} diff --git a/web_generator/lib/src/translator.dart b/web_generator/lib/src/translator.dart index 2321a2c1..a47c588e 100644 --- a/web_generator/lib/src/translator.dart +++ b/web_generator/lib/src/translator.dart @@ -11,12 +11,12 @@ import 'banned_names.dart'; import 'bcd.dart'; import 'doc_provider.dart'; import 'formatting.dart'; +import 'js/webidl_api.dart' as idl; +import 'js/webref_elements_api.dart'; import 'singletons.dart'; import 'type_aliases.dart'; import 'type_union.dart'; import 'util.dart'; -import 'webidl_api.dart' as idl; -import 'webref_elements_api.dart'; typedef TranslationResult = Map; diff --git a/web_generator/lib/src/util.dart b/web_generator/lib/src/util.dart index 2c80fa6e..b9d00210 100644 --- a/web_generator/lib/src/util.dart +++ b/web_generator/lib/src/util.dart @@ -4,7 +4,7 @@ import 'dart:js_interop'; -import 'filesystem_api.dart'; +import 'js/filesystem_api.dart'; // TODO(joshualitt): Let's find a better place for these. @JS('Object.entries') diff --git a/web_generator/mono_pkg.yaml b/web_generator/mono_pkg.yaml index 63fa719e..3ff7f001 100644 --- a/web_generator/mono_pkg.yaml +++ b/web_generator/mono_pkg.yaml @@ -11,10 +11,10 @@ stages: - generate_and_analyze: - command: - dart pub -C ../web get - - dart bin/update_bindings.dart + - dart bin/update_idl_bindings.dart - dart analyze --fatal-infos ../web - generate_all_and_analyze: - command: - dart pub -C ../web get - - dart bin/update_bindings.dart --generate-all + - dart bin/update_idl_bindings.dart --generate-all - dart analyze --fatal-infos ../web diff --git a/web_generator/test/ts_bindings_test.dart b/web_generator/test/ts_bindings_test.dart new file mode 100644 index 00000000..d0c57a69 --- /dev/null +++ b/web_generator/test/ts_bindings_test.dart @@ -0,0 +1,72 @@ +// ignore_for_file: library_annotations +@TestOn('vm') +@Tags(['node']) + +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:web_generator/src/cli.dart'; + +void main() { + final testGenFolder = p.join('test', 'gen'); + final testGenDTSFiles = p.join(testGenFolder, 'input'); + + group('Web Generator TS Bindings Integration Test', () { + final inputDir = Directory(testGenDTSFiles); + + setUp(() async { + // set up npm + await runProc('npm', ['install'], + workingDirectory: bindingsGeneratorPath, detached: true); + + // compile file + await runProc( + Platform.executable, + [ + 'compile', + 'js', + '--enable-asserts', + '--server-mode', + 'dart_main.dart', + '-o', + 'dart_main.js', + ], + workingDirectory: bindingsGeneratorPath, + detached: true); + }); + + for (final inputFile in inputDir.listSync().whereType()) { + final inputFileName = p.basenameWithoutExtension(inputFile.path); + + final outputActualPath = + p.join('test', 'gen', 'expected', '${inputFileName}_actual.dart'); + final outputExpectedPath = + p.join('test', 'gen', 'expected', '${inputFileName}_expected.dart'); + + test(inputFileName, () async { + final inputFilePath = + p.relative(inputFile.path, from: bindingsGeneratorPath); + final outFilePath = + p.relative(outputActualPath, from: bindingsGeneratorPath); + // run the entrypoint + await runProc( + 'node', + [ + 'main.mjs', + '--input=$inputFilePath', + '--output=$outFilePath', + '--declaration' + ], + workingDirectory: bindingsGeneratorPath, + detached: true); + + // read files + final expectedOutput = await File(outputExpectedPath).readAsString(); + final actualOutput = await File(outputActualPath).readAsString(); + + expect(actualOutput, expectedOutput); + }); + } + }); +}