Skip to content

Commit 4b2f02e

Browse files
Add Variable Declaration Support (#382)
* Begin parsing logic for code * wip: Variable Generation Support * wip: ast workings * Successful variable generation * formatting * Modified transformation system to allow - type deduction from name - unique naming (not complete) - filtering - type referencing * updated integration test suite * added namer, formatting and analyzing fixes * added TODO for imported declarations * Fixed bug: extra primitive types being generated * formatting and analyzing changes * Code resolution * removed redundant option and made all decl names final * formatting * added header comments, and some changes
1 parent c8c1c28 commit 4b2f02e

18 files changed

+916
-23
lines changed

web_generator/lib/src/ast.dart

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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:code_builder/code_builder.dart';
6+
7+
import 'interop_gen/generate.dart';
8+
import 'interop_gen/namer.dart';
9+
10+
sealed class Node {
11+
abstract final String? name;
12+
abstract final ID id;
13+
final String? dartName;
14+
15+
Node() : dartName = null;
16+
}
17+
18+
abstract class Declaration extends Node {
19+
@override
20+
abstract final String name;
21+
22+
Spec emit();
23+
}
24+
25+
abstract class NamedDeclaration extends Declaration {
26+
ReferredType asReferredType([List<Type>? typeArgs]) =>
27+
ReferredType(name: name, declaration: this, typeParams: typeArgs ?? []);
28+
}
29+
30+
abstract interface class ExportableDeclaration extends Declaration {
31+
/// Whether this declaration is exported.
32+
bool get exported;
33+
}
34+
35+
abstract class Type extends Node {
36+
Reference emit();
37+
}
38+
39+
enum PrimitiveType implements Type {
40+
string('string'),
41+
any('any'),
42+
object('object'),
43+
number('number'),
44+
boolean('boolean'),
45+
undefined('undefined'),
46+
unknown('unknown');
47+
48+
const PrimitiveType(this.name);
49+
50+
@override
51+
final String name;
52+
53+
@override
54+
ID get id => ID(type: 'type', name: name);
55+
56+
// TODO(https://github.com/dart-lang/web/pull/386): Configuration options: double and num
57+
@override
58+
Reference emit() {
59+
return switch (this) {
60+
PrimitiveType.string => refer('String'),
61+
PrimitiveType.any => refer('JSAny', 'dart:js_interop'),
62+
PrimitiveType.object => refer('JSObject', 'dart:js_interop'),
63+
PrimitiveType.number => refer('int'),
64+
PrimitiveType.boolean => refer('bool'),
65+
PrimitiveType.undefined => TypeReference((t) => t
66+
..symbol = 'JSAny'
67+
..url = 'dart:js_interop'
68+
..isNullable = true),
69+
PrimitiveType.unknown => TypeReference((t) => t
70+
..symbol = 'JSAny'
71+
..url = 'dart:js_interop'
72+
..isNullable = true)
73+
};
74+
}
75+
76+
@override
77+
String? get dartName => null;
78+
}
79+
80+
// TODO(): Refactor name - not all types can be referred to
81+
// (only specific types) Instead change this
82+
// to represent `typeof` declarations.
83+
// TODO(): Create a shared type for such types that
84+
// can be referred to (i.e namespace, interface, class)
85+
// as a type `ReferrableDeclaration`.
86+
class ReferredType<T extends Declaration> extends Type {
87+
@override
88+
String name;
89+
90+
@override
91+
ID get id => ID(type: 'type', name: name);
92+
93+
T declaration;
94+
95+
List<Type> typeParams;
96+
97+
ReferredType(
98+
{required this.name,
99+
required this.declaration,
100+
this.typeParams = const []});
101+
102+
@override
103+
Reference emit() {
104+
// TODO: implement emit
105+
throw UnimplementedError();
106+
}
107+
}
108+
109+
// TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`)
110+
class UnionType extends Type {
111+
List<Type> types;
112+
113+
UnionType({required this.types});
114+
115+
@override
116+
ID get id => ID(type: 'type', name: types.map((t) => t.id).join('|'));
117+
118+
@override
119+
Reference emit() {
120+
throw UnimplementedError();
121+
}
122+
123+
@override
124+
String? get name => null;
125+
}
126+
127+
class VariableDeclaration extends NamedDeclaration
128+
implements ExportableDeclaration {
129+
/// The variable modifier, as represented in TypeScript
130+
VariableModifier modifier;
131+
132+
@override
133+
String name;
134+
135+
Type type;
136+
137+
@override
138+
bool exported;
139+
140+
VariableDeclaration(
141+
{required this.name,
142+
required this.type,
143+
required this.modifier,
144+
required this.exported});
145+
146+
@override
147+
ID get id => ID(type: 'var', name: name);
148+
149+
@override
150+
Spec emit() {
151+
if (modifier == VariableModifier.$const) {
152+
return Method((m) => m
153+
..name = name
154+
..type = MethodType.getter
155+
..annotations.add(generateJSAnnotation())
156+
..external = true
157+
..returns = type.emit());
158+
} else {
159+
// getter and setter -> single variable
160+
return Field((f) => f
161+
..external = true
162+
..name = name
163+
..type = type.emit()
164+
..annotations.add(generateJSAnnotation()));
165+
}
166+
}
167+
}
168+
169+
enum VariableModifier { let, $const, $var }

web_generator/lib/src/banned_names.dart

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,75 @@
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+
/// Dart reserved keywords, used for resolving conflict with a name.
6+
///
7+
/// Source: https://dart.dev/guides/language/language-tour#keywords.
8+
const keywords = {
9+
'abstract',
10+
'as',
11+
'assert',
12+
'async',
13+
'await',
14+
'break',
15+
'case',
16+
'catch',
17+
'class',
18+
'const',
19+
'continue',
20+
'covariant',
21+
'default',
22+
'deferred',
23+
'do',
24+
'dynamic',
25+
'else',
26+
'enum',
27+
'export',
28+
'extends',
29+
'extension',
30+
'external',
31+
'factory',
32+
'false',
33+
'final',
34+
'finally',
35+
'for',
36+
'Function',
37+
'get',
38+
'hide',
39+
'if',
40+
'implements',
41+
'import',
42+
'in',
43+
'interface',
44+
'is',
45+
'late',
46+
'library',
47+
'mixin',
48+
'new',
49+
'null',
50+
'on',
51+
'operator',
52+
'part',
53+
'required',
54+
'rethrow',
55+
'return',
56+
'set',
57+
'show',
58+
'static',
59+
'super',
60+
'switch',
61+
'sync',
62+
'this',
63+
'throw',
64+
'true',
65+
'try',
66+
'typedef',
67+
'var',
68+
'void',
69+
'while',
70+
'with',
71+
'yield',
72+
};
73+
574
const bannedNames = <String>{
675
'assert',
776
'break',

web_generator/lib/src/cli.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'package:path/path.dart' as p;
1414

1515
final bindingsGeneratorPath = p.fromUri(Platform.script.resolve('../lib/src'));
1616

17-
Future<void> compileDartMain({String? langVersion}) async {
17+
Future<void> compileDartMain({String? langVersion, String? dir}) async {
1818
await runProc(
1919
Platform.executable,
2020
[
@@ -27,7 +27,7 @@ Future<void> compileDartMain({String? langVersion}) async {
2727
'-o',
2828
'dart_main.js',
2929
],
30-
workingDirectory: bindingsGeneratorPath,
30+
workingDirectory: dir ?? bindingsGeneratorPath,
3131
);
3232
}
3333

web_generator/lib/src/dart_main.dart

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import 'dart:js_interop';
77
import 'package:args/args.dart';
88
import 'package:code_builder/code_builder.dart' as code;
99
import 'package:dart_style/dart_style.dart';
10+
import 'package:path/path.dart' as p;
1011
import 'package:pub_semver/pub_semver.dart';
1112

12-
import 'dts/parser.dart';
13-
import 'dts/transform.dart';
1413
import 'generate_bindings.dart';
14+
import 'interop_gen/parser.dart';
15+
import 'interop_gen/transform.dart';
1516
import 'js/filesystem_api.dart';
1617
import 'util.dart';
1718

@@ -44,7 +45,7 @@ void main(List<String> args) async {
4445
}
4546
}
4647

47-
// TODO(nikeokoronkwo): Add support for configuration
48+
// TODO(https://github.com/dart-lang/web/issues/376): Add support for configuration
4849
Future<void> generateJSInteropBindings({
4950
required Iterable<String> inputs,
5051
required String output,
@@ -54,13 +55,20 @@ Future<void> generateJSInteropBindings({
5455
final jsDeclarations = parseDeclarationFiles(inputs);
5556

5657
// transform declarations
57-
final dartDeclarations = transformDeclarations(jsDeclarations);
58+
final dartDeclarations = transform(jsDeclarations);
5859

5960
// generate
60-
final generatedCode = dartDeclarations.generate();
61-
62-
// write code to file
63-
fs.writeFileSync(output.toJS, generatedCode.toJS);
61+
final generatedCodeMap = dartDeclarations.generate();
62+
63+
// write code to file(s)
64+
if (inputs.length == 1) {
65+
final singleEntry = generatedCodeMap.entries.single;
66+
fs.writeFileSync(output.toJS, singleEntry.value.toJS);
67+
} else {
68+
for (final entry in generatedCodeMap.entries) {
69+
fs.writeFileSync(p.join(output, entry.key).toJS, entry.value.toJS);
70+
}
71+
}
6472
}
6573

6674
Future<void> generateIDLBindings({

web_generator/lib/src/dts/parser.dart

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

web_generator/lib/src/dts/transform.dart renamed to web_generator/lib/src/interop_gen/generate.dart

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
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-
class DeclarationMap {
6-
String generate() {
7-
throw UnimplementedError();
8-
}
9-
}
5+
import 'package:code_builder/code_builder.dart';
106

11-
DeclarationMap transformDeclarations(Object? jsDeclarations) {
12-
throw UnimplementedError();
7+
Expression generateJSAnnotation([String? name]) {
8+
return refer('JS', 'dart:js_interop')
9+
.call([if (name != null) literalString(name)]);
1310
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
class ID {
6+
final String type;
7+
final String name;
8+
final int? index;
9+
10+
const ID({required this.type, required this.name, this.index});
11+
12+
bool get isUnnamed => name == 'unnamed';
13+
14+
@override
15+
String toString() => '$type#$name${index != null ? '#$index' : ''}';
16+
}
17+
18+
class UniqueNamer {
19+
final Set<String> _usedNames;
20+
21+
UniqueNamer([Iterable<String> used = const <String>[]])
22+
: _usedNames = used.toSet();
23+
24+
static ID parse(String id) {
25+
String? index;
26+
final [type, name, ...ids] = id.split('#');
27+
if (ids.isEmpty) index = ids.single;
28+
29+
return ID(
30+
type: type, name: name, index: index == null ? null : int.parse(index));
31+
}
32+
33+
/// Adds a [name] to used names.
34+
void markUsed(String name) {
35+
_usedNames.add(name);
36+
}
37+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 'dart:js_interop';
6+
7+
import '../js/typescript.dart' as ts;
8+
9+
class ParserResult {
10+
ts.TSProgram program;
11+
Iterable<String> files;
12+
13+
ParserResult({required this.program, required this.files});
14+
}
15+
16+
ParserResult parseDeclarationFiles(Iterable<String> files) {
17+
final program = ts.createProgram(files.jsify() as JSArray<JSString>,
18+
ts.TSCompilerOptions(declaration: true));
19+
20+
return ParserResult(program: program, files: files);
21+
}

0 commit comments

Comments
 (0)