Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions web_generator/bin/gen_interop_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ $_usage''');
if (relativeConfigFile case final config?) '--config=$config',
if (argResult.wasParsed('ignore-errors')) '--ignore-errors',
if (argResult.wasParsed('generate-all')) '--generate-all',
if (argResult.wasParsed('strict-unsupported')) '--strict-unsupported',
],
workingDirectory: bindingsGeneratorPath,
);
Expand Down Expand Up @@ -120,6 +121,11 @@ final _parser = ArgParser()
help: 'Generate all declarations '
'(including private declarations)',
negatable: false)
..addFlag('strict-unsupported',
help:
'Treat unsupported declarations/types as errors. Only used for development of the generator',
negatable: false,
hide: true)
..addOption('config',
hide: true,
abbr: 'c',
Expand Down
18 changes: 16 additions & 2 deletions web_generator/lib/src/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ abstract interface class Config {
/// declarations
bool get generateAll;

/// Treat any unsupported/unimplemented types/declarations as errors
///
/// This is to be used for development purposes only and is not supported
/// as a config option in the configuration file
bool get strictUnsupported;

factory Config(
{required List<String> input,
required String output,
Expand All @@ -75,7 +81,8 @@ abstract interface class Config {
List<String> includedDeclarations,
bool generateAll,
bool ignoreErrors,
String? tsConfigFile}) = ConfigImpl._;
String? tsConfigFile,
bool strictUnsupported}) = ConfigImpl._;
}

class ConfigImpl implements Config {
Expand Down Expand Up @@ -118,6 +125,9 @@ class ConfigImpl implements Config {
@override
bool generateAll;

@override
bool strictUnsupported;

ConfigImpl._(
{required this.input,
required this.output,
Expand All @@ -127,7 +137,8 @@ class ConfigImpl implements Config {
this.includedDeclarations = const [],
this.ignoreErrors = false,
this.generateAll = false,
this.tsConfigFile});
this.tsConfigFile,
this.strictUnsupported = false});

@override
bool get singleFileOutput => input.length == 1;
Expand Down Expand Up @@ -176,6 +187,9 @@ class YamlConfig implements Config {
@override
bool generateAll;

@override
bool get strictUnsupported => false;

YamlConfig._(
{required this.filename,
required this.input,
Expand Down
9 changes: 8 additions & 1 deletion web_generator/lib/src/dart_main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ void main(List<String> args) async {
languageVersion: Version.parse(languageVersionString),
tsConfigFile: tsConfigFile,
ignoreErrors: argResult.wasParsed('ignore-errors'),
generateAll: argResult['generate-all'] as bool);
generateAll: argResult['generate-all'] as bool,
strictUnsupported: argResult.wasParsed('strict-unsupported'));
}

await generateJSInteropBindings(config);
Expand Down Expand Up @@ -202,5 +203,11 @@ final _parser = ArgParser()
help: '[TS Declarations] The input file to read and generate types for')
..addFlag('ignore-errors',
help: '[TS Declarations] Ignore Generator Errors', negatable: false)
..addFlag(
'strict-unsupported',
help:
'[TS Declarations] Treat unsupported declarations/types as errors. Only used for development of the generator',
negatable: false,
)
..addOption('config',
abbr: 'c', hide: true, valueHelp: '[file].yaml', help: 'Configuration');
9 changes: 7 additions & 2 deletions web_generator/lib/src/interop_gen/transform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,12 @@ class ProgramMap {

final bool generateAll;

final bool strictUnsupported;

ProgramMap(this.program, List<String> files,
{this.filterDeclSet = const [], bool? generateAll})
{this.filterDeclSet = const [],
bool? generateAll,
this.strictUnsupported = false})
: typeChecker = program.getTypeChecker(),
generateAll = generateAll ?? false,
files = p.PathSet.of(files);
Expand Down Expand Up @@ -323,7 +327,8 @@ class TransformerManager {
TransformerManager.fromParsedResults(ParserResult result, {Config? config})
: programMap = ProgramMap(result.program, result.files.toList(),
filterDeclSet: config?.includedDeclarations ?? [],
generateAll: config?.generateAll);
generateAll: config?.generateAll,
strictUnsupported: config?.strictUnsupported ?? false);

TransformResult transform() {
final outputNodeMap = <String, NodeMap>{};
Expand Down
33 changes: 23 additions & 10 deletions web_generator/lib/src/interop_gen/transform/transformer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ class Transformer {

bool get generateAll => programMap.generateAll;

bool get errorIfUnsupported => programMap.strictUnsupported;

Transformer(this.programMap, this._sourceFile,
{Set<String> exportSet = const {}, String? file})
: exportSet = exportSet.map((e) => ExportReference(e, as: e)).toSet(),
Expand Down Expand Up @@ -152,7 +154,12 @@ class Transformer {
(node.name as TSIdentifier).text != 'global':
return [_transformNamespace(node, namer: namer, parent: parent)];
default:
throw Exception('Unsupported Declaration Kind: ${node.kind}');
if (errorIfUnsupported) {
throw Exception('Unsupported Declaration Kind: ${node.kind}');
} else {
print('WARN: Unsupported Declaration Kind: ${node.kind}');
return [];
}
}
}

Expand Down Expand Up @@ -1460,13 +1467,23 @@ class Transformer {
TSSyntaxKind.BigIntKeyword => PrimitiveType.bigint,
TSSyntaxKind.SymbolKeyword => PrimitiveType.symbol,
TSSyntaxKind.NeverKeyword => PrimitiveType.never,
_ => throw UnsupportedError(
'The given type with kind ${type.kind} is not supported yet')
_ => null
};

return BuiltinType.primitiveType(primitiveType,
shouldEmitJsType: typeArg ? true : null,
isNullable: primitiveType == PrimitiveType.any ? true : isNullable);
if (primitiveType != null) {
return BuiltinType.primitiveType(primitiveType,
shouldEmitJsType: typeArg ? true : null,
isNullable:
primitiveType == PrimitiveType.any ? true : isNullable);
} else if (errorIfUnsupported) {
throw UnsupportedError(
'The given type with kind ${type.kind} is not supported yet');
} else {
print('WARN: The given type with kind ${type.kind} is '
'not supported yet');
return BuiltinType.primitiveType(PrimitiveType.any,
isNullable: isNullable);
}
}
}

Expand Down Expand Up @@ -2160,10 +2177,6 @@ class Transformer {
void updateFilteredDeclsForDecl(Node? decl, NodeMap filteredDeclarations) {
switch (decl) {
case final VariableDeclaration v:
print((
v.name,
v.type is TupleType ? (v.type as TupleType).name : null
));
filteredDeclarations.add(v.type);
break;
case final CallableDeclaration f:
Expand Down
8 changes: 8 additions & 0 deletions web_generator/test/assets/unsupported_test.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface Foo {
name: string;
length: number;
raw: ArrayBuffer;
}
export interface Fee {
raw: Foo['raw']
}
15 changes: 15 additions & 0 deletions web_generator/test/assets/unsupported_test_expected.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// ignore_for_file: constant_identifier_names, non_constant_identifier_names

// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:js_interop' as _i1;

extension type Foo._(_i1.JSObject _) implements _i1.JSObject {
external String name;

external double length;

external _i1.JSArrayBuffer raw;
}
extension type Fee._(_i1.JSObject _) implements _i1.JSObject {
external _i1.JSAny raw;
}
74 changes: 74 additions & 0 deletions web_generator/test/unsupported_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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.

@TestOn('vm')
library;

import 'dart:convert';
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('Interop Gen Unsupported Test', () {
final testFile = p.join('test', 'assets', 'unsupported_test.d.ts');
final outputFile = p.join('.dart_tool', 'unsupported_test.dart');
final expectedFile =
p.join('test', 'assets', 'unsupported_test_expected.dart');

setUpAll(() async {
// set up npm
await runProc('npm', ['install'], workingDirectory: bindingsGenPath);

// compile file
await compileDartMain(dir: bindingsGenPath);
});

test('Strict Unsupported', () async {
final inputFilePath = p.relative(testFile, from: bindingsGenPath);
final outputFilePath = p.relative(outputFile, from: bindingsGenPath);

final process = await runProcWithResult(
'node',
[
'main.mjs',
'--input=$inputFilePath',
'--output=$outputFilePath',
'--strict-unsupported',
'--declaration'
],
workingDirectory: bindingsGenPath);

final stderr = await process.stderr.transform(utf8.decoder).toList();

expect(stderr, isNotEmpty);
expect(await process.exitCode, isNot(0));
});

test('Not Strict Unsupported', () async {
final inputFilePath = p.relative(testFile, from: bindingsGenPath);
final outputFilePath = p.relative(outputFile, from: bindingsGenPath);

await runProc(
'node',
[
'main.mjs',
'--input=$inputFilePath',
'--output=$outputFilePath',
'--declaration'
],
workingDirectory: bindingsGenPath);

// read files
final expectedOutput = await File(expectedFile).readAsString();
final actualOutput = await File(outputFile).readAsString();

expect(actualOutput, expectedOutput);
});
});
}