Skip to content

Commit cc3aa64

Browse files
[interop] Update Configuration to allow TS Compiler Configuration and non-exported decl generation (#443)
* added tsconfig.json support * formatting and analyzing * implemented ignoring errors * add support for generating all declarations * rewrote ts config parsing using ts functions (required) Instead of manually marging objects to form compiler options, TS API requires using built-in functions to properly resolve the config into a compiler options objects, with enums, correct types, etc. Functions implemented were `parseJsonConfigFileContent` and `getParsedCommandLineOfConfigFile` * formatting and doc for previous commit * allowed for anonymous diagnostics and updated entrypoint * formatting * stray comment
1 parent 4310354 commit cc3aa64

File tree

7 files changed

+279
-38
lines changed

7 files changed

+279
-38
lines changed

web_generator/bin/gen_interop_bindings.dart

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ $_usage''');
2929
return;
3030
}
3131

32-
if (argResult.rest.isEmpty) {
32+
if (argResult.rest.isEmpty && !argResult.wasParsed('config')) {
3333
print('''
3434
${ansi.lightRed.wrap('At least one argument is needed')}
3535
@@ -58,25 +58,37 @@ $_usage''');
5858
await compileDartMain();
5959
}
6060

61-
final inputFile = argResult.rest.first;
61+
final inputFile = argResult.rest.firstOrNull;
6262
final outputFile = argResult['output'] as String? ??
63-
p.join(p.current, inputFile.replaceAll('.d.ts', '.dart'));
63+
p.join(p.current, inputFile?.replaceAll('.d.ts', '.dart'));
6464
final defaultWebGenConfigPath = p.join(p.current, 'webgen.yaml');
6565
final configFile = argResult['config'] as String? ??
6666
(File(defaultWebGenConfigPath).existsSync()
6767
? defaultWebGenConfigPath
6868
: null);
69+
final relativeConfigFile = configFile != null
70+
? p.relative(configFile, from: bindingsGeneratorPath)
71+
: null;
6972
final relativeOutputPath =
7073
p.relative(outputFile, from: bindingsGeneratorPath);
74+
final tsConfigPath = argResult['ts-config'] as String?;
75+
final tsConfigRelativePath = tsConfigPath != null
76+
? p.relative(tsConfigPath, from: bindingsGeneratorPath)
77+
: null;
7178
// Run app with `node`.
7279
await runProc(
7380
'node',
7481
[
7582
'main.mjs',
7683
'--declaration',
77-
'--input=${p.relative(inputFile, from: bindingsGeneratorPath)}',
78-
'--output=$relativeOutputPath',
79-
if (configFile case final config?) '--config=$config'
84+
if (argResult.rest.isNotEmpty) ...[
85+
'--input=${p.relative(inputFile!, from: bindingsGeneratorPath)}',
86+
'--output=$relativeOutputPath',
87+
],
88+
if (tsConfigRelativePath case final tsConfig?) '--ts-config=$tsConfig',
89+
if (relativeConfigFile case final config?) '--config=$config',
90+
if (argResult.wasParsed('ignore-errors')) '--ignore-errors',
91+
if (argResult.wasParsed('generate-all')) '--generate-all',
8092
],
8193
workingDirectory: bindingsGeneratorPath,
8294
);
@@ -100,6 +112,14 @@ final _parser = ArgParser()
100112
..addFlag('compile', defaultsTo: true)
101113
..addOption('output',
102114
abbr: 'o', help: 'The output path to generate the Dart interface code')
115+
..addOption('ts-config',
116+
help: 'Path to TS Configuration Options File (tsconfig.json) to pass'
117+
' to the parser/transformer')
118+
..addFlag('ignore-errors', help: 'Ignore Generator Errors', negatable: false)
119+
..addFlag('generate-all',
120+
help: 'Generate all declarations '
121+
'(including private declarations)',
122+
negatable: false)
103123
..addOption('config',
104124
hide: true,
105125
abbr: 'c',

web_generator/lib/src/config.dart

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:convert';
6+
57
import 'package:dart_style/dart_style.dart';
68
import 'package:path/path.dart' as p;
79
import 'package:pub_semver/pub_semver.dart';
@@ -49,12 +51,31 @@ abstract interface class Config {
4951
/// If empty, all declarations will be generated by default
5052
List<String> get includedDeclarations;
5153

54+
/// An object consisting of TS Configurations from a tsconfig.json file
55+
/// used for configuring the TypeScript Program/Compiler
56+
Map<String, dynamic>? get tsConfig;
57+
58+
/// The TS Configuration file (tsconfig.json) if any
59+
String? get tsConfigFile;
60+
61+
/// Whether to ignore source code warnings and errors
62+
/// (they will still be printed)
63+
bool get ignoreErrors;
64+
65+
/// Whether to generate code for all declarations, including non-exported
66+
/// declarations
67+
bool get generateAll;
68+
5269
factory Config(
5370
{required List<String> input,
5471
required String output,
5572
required Version languageVersion,
5673
FunctionConfig? functions,
57-
List<String> includedDeclarations}) = ConfigImpl._;
74+
Map<String, dynamic>? tsConfig,
75+
List<String> includedDeclarations,
76+
bool generateAll,
77+
bool ignoreErrors,
78+
String? tsConfigFile}) = ConfigImpl._;
5879
}
5980

6081
class ConfigImpl implements Config {
@@ -85,12 +106,28 @@ class ConfigImpl implements Config {
85106
@override
86107
List<String> includedDeclarations;
87108

109+
@override
110+
Map<String, dynamic>? tsConfig;
111+
112+
@override
113+
String? tsConfigFile;
114+
115+
@override
116+
bool ignoreErrors;
117+
118+
@override
119+
bool generateAll;
120+
88121
ConfigImpl._(
89122
{required this.input,
90123
required this.output,
91124
required this.languageVersion,
92125
this.functions,
93-
this.includedDeclarations = const []});
126+
this.tsConfig,
127+
this.includedDeclarations = const [],
128+
this.ignoreErrors = false,
129+
this.generateAll = false,
130+
this.tsConfigFile});
94131

95132
@override
96133
bool get singleFileOutput => input.length == 1;
@@ -127,6 +164,18 @@ class YamlConfig implements Config {
127164
@override
128165
List<String> includedDeclarations;
129166

167+
@override
168+
Map<String, dynamic>? tsConfig;
169+
170+
@override
171+
String? tsConfigFile;
172+
173+
@override
174+
bool ignoreErrors;
175+
176+
@override
177+
bool generateAll;
178+
130179
YamlConfig._(
131180
{required this.filename,
132181
required this.input,
@@ -136,7 +185,11 @@ class YamlConfig implements Config {
136185
this.preamble,
137186
this.functions,
138187
this.includedDeclarations = const [],
139-
String? languageVersion})
188+
this.tsConfig,
189+
this.tsConfigFile,
190+
String? languageVersion,
191+
this.ignoreErrors = false,
192+
this.generateAll = false})
140193
: languageVersion = languageVersion == null
141194
? DartFormatter.latestLanguageVersion
142195
: Version.parse(languageVersion);
@@ -159,6 +212,8 @@ class YamlConfig implements Config {
159212
final allFiles =
160213
expandGlobs(inputFiles, extension: '.d.ts', cwd: p.dirname(filename));
161214

215+
final tsConfig = yaml['ts_config'] as YamlMap?;
216+
162217
return YamlConfig._(
163218
filename: Uri.file(filename),
164219
input:
@@ -169,12 +224,17 @@ class YamlConfig implements Config {
169224
description: yaml['description'] as String?,
170225
languageVersion: yaml['language_version'] as String?,
171226
preamble: yaml['preamble'] as String?,
172-
// TODO: Can we consider using `json_serializable`?
227+
tsConfig: tsConfig != null
228+
? jsonDecode(jsonEncode(tsConfig)) as Map<String, dynamic>
229+
: null,
230+
tsConfigFile: yaml['ts_config_file'] as String?,
173231
functions: FunctionConfig(
174232
varArgs: (yaml['functions'] as YamlMap?)?['varargs'] as int?),
175233
includedDeclarations: (yaml['include'] as YamlList?)
176234
?.map<String>((node) => node.toString())
177235
.toList() ??
178-
[]);
236+
[],
237+
ignoreErrors: yaml['ignore_errors'] as bool? ?? false,
238+
generateAll: yaml['generate_all'] as bool? ?? false);
179239
}
180240
}

web_generator/lib/src/dart_main.dart

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,15 @@ void main(List<String> args) async {
5454
filename: filename,
5555
);
5656
} else {
57+
final tsConfigFile = argResult['ts-config'] as String?;
5758
config = Config(
58-
input:
59-
expandGlobs(argResult['input'] as List<String>, extension: '.d.ts'),
60-
output: argResult['output'] as String,
61-
languageVersion: Version.parse(languageVersionString),
62-
);
59+
input: expandGlobs(argResult['input'] as List<String>,
60+
extension: '.d.ts'),
61+
output: argResult['output'] as String,
62+
languageVersion: Version.parse(languageVersionString),
63+
tsConfigFile: tsConfigFile,
64+
ignoreErrors: argResult.wasParsed('ignore-errors'),
65+
generateAll: argResult['generate-all'] as bool);
6366
}
6467

6568
await generateJSInteropBindings(config);
@@ -68,7 +71,7 @@ void main(List<String> args) async {
6871

6972
Future<void> generateJSInteropBindings(Config config) async {
7073
// generate
71-
final jsDeclarations = parseDeclarationFiles(config.input);
74+
final jsDeclarations = parseDeclarationFiles(config);
7275

7376
// transform declarations
7477
final manager =
@@ -183,10 +186,16 @@ final _parser = ArgParser()
183186
'(directory for IDL, file for TS Declarations)')
184187
..addFlag('generate-all',
185188
negatable: false,
186-
help: '[IDL] Generate bindings for all IDL definitions, '
187-
'including experimental and non-standard APIs.')
189+
help: 'Generate bindings for all IDL/TS Declaration definitions, '
190+
'including experimental and non-standard APIs (IDL) '
191+
'/ non-exported APIs (TS Declarations).')
192+
..addOption('ts-config',
193+
help: '[TS Declarations] Path to TS Configuration Options File '
194+
'(tsconfig.json) to pass to the parser/transformer')
188195
..addMultiOption('input',
189196
abbr: 'i',
190197
help: '[TS Declarations] The input file to read and generate types for')
198+
..addFlag('ignore-errors',
199+
help: '[TS Declarations] Ignore Generator Errors', negatable: false)
191200
..addOption('config',
192201
abbr: 'c', hide: true, valueHelp: '[file].yaml', help: 'Configuration');

web_generator/lib/src/interop_gen/parser.dart

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import 'dart:js_interop';
66

7+
import 'package:path/path.dart' as p;
8+
9+
import '../config.dart';
710
import '../js/node.dart';
811
import '../js/typescript.dart' as ts;
912

@@ -14,11 +17,60 @@ class ParserResult {
1417
ParserResult({required this.program, required this.files});
1518
}
1619

17-
/// Parses the given TypeScript declaration [files], provides any diagnostics,
18-
/// if any, and generates a [ts.TSProgram] for transformation
19-
ParserResult parseDeclarationFiles(Iterable<String> files) {
20-
final program = ts.createProgram(files.jsify() as JSArray<JSString>,
21-
ts.TSCompilerOptions(declaration: true));
20+
/// Parses the given TypeScript declaration files in the [config],
21+
/// provides diagnostics if any, and generates a [ts.TSProgram]
22+
/// for transformation.
23+
///
24+
/// If a TS Config is passed, this function also produces compiler
25+
/// options from the TS config file/config object to use alongside the compiler
26+
ParserResult parseDeclarationFiles(Config config) {
27+
final files = config.input;
28+
final ignoreErrors = config.ignoreErrors;
29+
30+
// create host for parsing TS configuration
31+
// TODO: @srujzs we can also create our own host
32+
// Do you think we should allow TS handle such functions,
33+
// or we should ourselves
34+
final host = ts.sys;
35+
var compilerOptions = ts.TSCompilerOptions(declaration: true);
36+
if (config.tsConfigFile case final tsConfigFile?) {
37+
final parsedCommandLine = ts.getParsedCommandLineOfConfigFile(
38+
p.absolute(tsConfigFile),
39+
ts.TSCompilerOptions(declaration: true),
40+
host);
41+
42+
if (parsedCommandLine != null) {
43+
compilerOptions = parsedCommandLine.options;
44+
45+
final diagnostics = parsedCommandLine.errors.toDart;
46+
47+
// handle any diagnostics
48+
handleDiagnostics(diagnostics);
49+
if (!ignoreErrors && diagnostics.isNotEmpty) {
50+
exit(1);
51+
}
52+
}
53+
} else if (config.tsConfig case final tsConfig?
54+
when config.filename != null) {
55+
final parsedCommandLine = ts.parseJsonConfigFileContent(
56+
tsConfig.jsify() as JSObject,
57+
host,
58+
p.dirname(config.filename!.toFilePath()),
59+
ts.TSCompilerOptions(declaration: true));
60+
61+
compilerOptions = parsedCommandLine.options;
62+
63+
final diagnostics = parsedCommandLine.errors.toDart;
64+
65+
// handle any diagnostics
66+
handleDiagnostics(diagnostics);
67+
if (!ignoreErrors && diagnostics.isNotEmpty) {
68+
exit(1);
69+
}
70+
}
71+
72+
final program =
73+
ts.createProgram(files.jsify() as JSArray<JSString>, compilerOptions);
2274

2375
// get diagnostics
2476
final diagnostics = [
@@ -28,6 +80,17 @@ ParserResult parseDeclarationFiles(Iterable<String> files) {
2880
];
2981

3082
// handle diagnostics
83+
handleDiagnostics(diagnostics);
84+
85+
if (diagnostics.isNotEmpty && !ignoreErrors) {
86+
// exit
87+
exit(1);
88+
}
89+
90+
return ParserResult(program: program, files: files);
91+
}
92+
93+
void handleDiagnostics(List<ts.TSDiagnostic> diagnostics) {
3194
for (final diagnostic in diagnostics) {
3295
if (diagnostic.file case final diagnosticFile?) {
3396
final ts.TSLineAndCharacter(line: line, character: char) =
@@ -36,13 +99,10 @@ ParserResult parseDeclarationFiles(Iterable<String> files) {
3699
ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
37100
printErr('${diagnosticFile.fileName} '
38101
'(${line.toDartInt + 1},${char.toDartInt + 1}): $message');
102+
} else {
103+
final message =
104+
ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
105+
printErr('(anonymous): $message');
39106
}
40107
}
41-
42-
if (diagnostics.isNotEmpty) {
43-
// exit
44-
exit(1);
45-
}
46-
47-
return ParserResult(program: program, files: files);
48108
}

0 commit comments

Comments
 (0)