Skip to content

Commit fb8a149

Browse files
Add Support for Configuration of Dart JS Interop Gen (#386)
* Base config * Added basic support for config and yaml config * Refactored intro for configuration * Added preamble option * minor changes * Resolved all issues
1 parent 3e11172 commit fb8a149

File tree

9 files changed

+256
-23
lines changed

9 files changed

+256
-23
lines changed

web_generator/lib/src/config.dart

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:dart_style/dart_style.dart';
6+
import 'package:pub_semver/pub_semver.dart';
7+
import 'package:yaml/yaml.dart';
8+
9+
abstract interface class Config {
10+
/// The name for the configuration
11+
String? get name;
12+
13+
/// The description for the configuration
14+
String? get description;
15+
16+
/// Preamble to add at the top of generated code
17+
String? get preamble;
18+
19+
/// The input files
20+
List<String> get input;
21+
22+
/// The output file or directory to write bindings to
23+
String get output;
24+
25+
/// The configuration file
26+
Uri? get filename;
27+
28+
/// The Dart Language Version to use
29+
Version get languageVersion;
30+
31+
bool get singleFileOutput => input.length == 1;
32+
33+
factory Config({
34+
required List<String> input,
35+
required String output,
36+
required Version languageVersion,
37+
}) = ConfigImpl._;
38+
}
39+
40+
class ConfigImpl implements Config {
41+
@override
42+
String? description;
43+
44+
@override
45+
Uri? filename;
46+
47+
@override
48+
List<String> input;
49+
50+
@override
51+
String? name;
52+
53+
@override
54+
String output;
55+
56+
@override
57+
Version languageVersion;
58+
59+
@override
60+
String? preamble;
61+
62+
ConfigImpl._({
63+
required this.input,
64+
required this.output,
65+
required this.languageVersion,
66+
});
67+
68+
@override
69+
// TODO: implement singleFileOutput
70+
bool get singleFileOutput => input.length == 1;
71+
}
72+
73+
class YamlConfig implements Config {
74+
@override
75+
Uri filename;
76+
77+
@override
78+
List<String> input;
79+
80+
@override
81+
String? description;
82+
83+
@override
84+
String? name;
85+
86+
@override
87+
String output;
88+
89+
@override
90+
bool get singleFileOutput => input.length == 1;
91+
92+
@override
93+
Version languageVersion;
94+
95+
@override
96+
String? preamble;
97+
98+
YamlConfig._(
99+
{required this.filename,
100+
required this.input,
101+
required this.output,
102+
this.description,
103+
this.name,
104+
this.preamble,
105+
String? languageVersion})
106+
: languageVersion = languageVersion == null
107+
? DartFormatter.latestLanguageVersion
108+
: Version.parse(languageVersion);
109+
110+
factory YamlConfig.fromYaml(YamlMap yaml, {required String filename}) {
111+
List<String> input;
112+
final yamlInput = yaml['input'];
113+
if (yamlInput is YamlList) {
114+
input = yamlInput.map((y) => y is String ? y : y.toString()).toList();
115+
} else if (yamlInput is String) {
116+
input = [yamlInput];
117+
} else {
118+
throw TypeError();
119+
}
120+
121+
return YamlConfig._(
122+
filename: Uri.file(filename),
123+
input: input,
124+
output: yaml['output'] as String,
125+
name: yaml['name'] as String?,
126+
description: yaml['description'] as String?,
127+
languageVersion: yaml['language_version'] as String?,
128+
preamble: yaml['preamble'] as String?);
129+
}
130+
}

web_generator/lib/src/dart_main.dart

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import 'package:code_builder/code_builder.dart' as code;
99
import 'package:dart_style/dart_style.dart';
1010
import 'package:path/path.dart' as p;
1111
import 'package:pub_semver/pub_semver.dart';
12+
import 'package:yaml/yaml.dart';
1213

14+
import 'config.dart';
1315
import 'generate_bindings.dart';
1416
import 'interop_gen/parser.dart';
1517
import 'interop_gen/transform.dart';
@@ -21,7 +23,6 @@ import 'util.dart';
2123
// TODO(joshualitt): Use static interop methods for JSArray and JSPromise.
2224
// TODO(joshualitt): Find a way to generate bindings for JS builtins. This will
2325
// probably involve parsing the TC39 spec.
24-
2526
void main(List<String> args) async {
2627
var languageVersionString = const String.fromEnvironment('languageVersion');
2728
if (languageVersionString.isEmpty) {
@@ -40,36 +41,45 @@ void main(List<String> args) async {
4041
languageVersion: Version.parse(languageVersionString),
4142
);
4243
} else if (argResult.wasParsed('declaration')) {
43-
await generateJSInteropBindings(
44-
inputs: argResult['input'] as Iterable<String>,
45-
output: argResult['output'] as String,
46-
languageVersion: Version.parse(languageVersionString),
47-
);
44+
final Config config;
45+
46+
if (argResult.wasParsed('config')) {
47+
final filename = argResult['config'] as String;
48+
final configContent = fs.readFileSync(
49+
filename.toJS, JSReadFileOptions(encoding: 'utf8'.toJS)) as JSString;
50+
final yaml = loadYamlDocument(configContent.toDart);
51+
config =
52+
YamlConfig.fromYaml(yaml.contents as YamlMap, filename: filename);
53+
} else {
54+
config = Config(
55+
input: argResult['input'] as List<String>,
56+
output: argResult['output'] as String,
57+
languageVersion: Version.parse(languageVersionString),
58+
);
59+
}
60+
61+
await generateJSInteropBindings(config);
4862
}
4963
}
5064

51-
// TODO(https://github.com/dart-lang/web/issues/376): Add support for configuration
52-
Future<void> generateJSInteropBindings({
53-
required Iterable<String> inputs,
54-
required String output,
55-
required Version languageVersion,
56-
}) async {
65+
Future<void> generateJSInteropBindings(Config config) async {
5766
// generate
58-
final jsDeclarations = parseDeclarationFiles(inputs);
67+
final jsDeclarations = parseDeclarationFiles(config.input);
5968

6069
// transform declarations
6170
final dartDeclarations = transform(jsDeclarations);
6271

6372
// generate
6473
final generatedCodeMap = dartDeclarations.generate();
6574

66-
// write code to file(s)
67-
if (inputs.length == 1) {
68-
final singleEntry = generatedCodeMap.entries.single;
69-
fs.writeFileSync(output.toJS, singleEntry.value.toJS);
75+
// write code to file
76+
if (generatedCodeMap.length == 1) {
77+
final entry = generatedCodeMap.entries.first;
78+
fs.writeFileSync(config.output.toJS, entry.value.toJS);
7079
} else {
7180
for (final entry in generatedCodeMap.entries) {
72-
fs.writeFileSync(p.join(output, entry.key).toJS, entry.value.toJS);
81+
fs.writeFileSync(
82+
p.join(config.output, p.basename(entry.key)).toJS, entry.value.toJS);
7383
}
7484
}
7585
}

web_generator/lib/src/interop_gen/config.dart

Lines changed: 0 additions & 5 deletions
This file was deleted.

web_generator/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ dependencies:
2121
path: ^1.8.3
2222
pub_semver: ^2.1.5
2323
test: ^1.24.4
24+
yaml: ^3.1.3
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: IDL Demo Gen
2+
preamble: |
3+
// GENERATED FILE: DO NOT EDIT
4+
input: intro.d.ts
5+
output: output.dart
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: Nanoid and Lodash
2+
preamble: |
3+
// GENERATED FILE: DO NOT EDIT
4+
input:
5+
nanoid: 'nanoid.d.ts'
6+
output: output
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: Nanoid and Lodash
2+
preamble: |
3+
// GENERATED FILE: DO NOT EDIT
4+
input:
5+
- 'nanoid.d.ts'
6+
- 'lodash.d.ts'
7+
output:
8+
- nanoid.dart
9+
- lodash.dart
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: Nanoid and Lodash
2+
preamble: |
3+
// GENERATED FILE: DO NOT EDIT
4+
input:
5+
- 'nanoid.d.ts'
6+
- 'lodash.d.ts'
7+
output: output

web_generator/test/config_test.dart

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@TestOn('vm')
6+
library;
7+
8+
import 'dart:io';
9+
10+
import 'package:path/path.dart' as p;
11+
import 'package:test/test.dart';
12+
import 'package:web_generator/src/config.dart';
13+
import 'package:yaml/yaml.dart';
14+
15+
final configurationTests = {
16+
'basic': {
17+
'name': 'IDL Demo Gen',
18+
'single': true,
19+
'input': ['intro.d.ts'],
20+
'valid': true
21+
},
22+
'multi_file': {
23+
'name': 'Nanoid and Lodash',
24+
'single': false,
25+
'input': ['nanoid.d.ts', 'lodash.d.ts'],
26+
'valid': true
27+
},
28+
'invalid': {'valid': false},
29+
'invalid_output': {'valid': false}
30+
};
31+
32+
void main() {
33+
group('Configuration Test', () {
34+
group('YAML Config Test', () {
35+
final assetsDir = p.join('test', 'assets');
36+
37+
final assets = Directory(assetsDir);
38+
39+
for (final file in assets.listSync().whereType<File>().where((f) =>
40+
p.basenameWithoutExtension(f.path).endsWith('_config') &&
41+
p.extension(f.path) == '.yaml')) {
42+
final fileName =
43+
p.basenameWithoutExtension(file.path).replaceFirst('_config', '');
44+
final fileContents = file.readAsStringSync();
45+
final configTest = configurationTests[fileName];
46+
47+
if (configTest == null) continue;
48+
49+
test(fileName, () {
50+
final yaml = loadYamlDocument(fileContents);
51+
52+
if (configTest.containsKey('valid') && configTest['valid'] == false) {
53+
expect(
54+
() => YamlConfig.fromYaml(yaml.contents as YamlMap,
55+
filename: p.basename(file.path)),
56+
throwsA(isA<TypeError>()));
57+
} else {
58+
// validate
59+
final config = YamlConfig.fromYaml(yaml.contents as YamlMap,
60+
filename: p.basename(file.path));
61+
62+
expect(config.name, equals(configTest['name']));
63+
expect(config.input, equals(configTest['input']));
64+
expect(config.singleFileOutput, equals(configTest['single']));
65+
}
66+
});
67+
}
68+
});
69+
});
70+
}

0 commit comments

Comments
 (0)