Skip to content

Commit 7e0853d

Browse files
[interop] Adding support for dart:js_interop and package:web types (#403)
* Adding types input * Added support for primitive types represented in `dart:js_interop` * Added support for generating web_rename_map.dart for names that are renamed in package:web * completed integration of `package:web` types * formatting and analyzing * comment resolution * minor change * Removed `BuiltinTypeGenerator` callable type to use static `BuiltinType.referred` for supported types in `dart:js_interop` * fixed analyzer issue * completed nits in merge * removed completed TODO
1 parent 951961e commit 7e0853d

16 files changed

+290
-47
lines changed

web_generator/lib/src/ast/builtin.dart

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import 'package:code_builder/code_builder.dart';
88

99
import '../interop_gen/namer.dart';
10+
import '../web_rename_map.dart';
1011
import 'base.dart';
1112

1213
/// A built in type supported by `dart:js_interop` or by this library
@@ -83,20 +84,90 @@ class BuiltinType extends Type {
8384
PrimitiveType.any || PrimitiveType.unknown => anyType,
8485
PrimitiveType.object => BuiltinType(
8586
name: 'JSObject', fromDartJSInterop: true, isNullable: isNullable),
87+
PrimitiveType.symbol => BuiltinType(
88+
name: 'JSSymbol', fromDartJSInterop: true, isNullable: isNullable),
89+
PrimitiveType.bigint => BuiltinType(
90+
name: 'JSBigInt', fromDartJSInterop: true, isNullable: isNullable),
8691
PrimitiveType.array => BuiltinType(
8792
name: 'JSArray',
8893
typeParams: [typeParams.single],
8994
fromDartJSInterop: true,
9095
isNullable: isNullable),
91-
PrimitiveType.promise => BuiltinType(
92-
name: 'JSPromise',
93-
typeParams: [typeParams.single],
94-
fromDartJSInterop: true,
95-
isNullable: isNullable),
96-
PrimitiveType.function => BuiltinType(
97-
name: 'JSFunction', fromDartJSInterop: true, isNullable: isNullable),
9896
};
9997
}
98+
99+
static BuiltinType? referred(String name,
100+
{bool? isNullable, List<Type> typeParams = const []}) {
101+
final jsName = switch (name) {
102+
'Array' => 'JSArray',
103+
'Promise' => 'JSPromise',
104+
'ArrayBuffer' => 'JSArrayBuffer',
105+
'Function' => 'JSFunction',
106+
'DataView' => 'JSDataView',
107+
'Float32Array' => 'JSFloat32Array',
108+
'Float64Array' => 'JSFloat64Array',
109+
'Int8Array' => 'JSInt8Array',
110+
'Int16Array' => 'JSInt16Array',
111+
'Int32Array' => 'JSInt32Array',
112+
'Int64Array' => 'JSInt64Array',
113+
'Uint8Array' => 'JSUint8Array',
114+
'Uint16Array' => 'JSUint16Array',
115+
'Uint32Array' => 'JSUint32Array',
116+
'Uint8ClampedArray' => 'JSUint8ClampedArray',
117+
_ => null
118+
};
119+
final jsTypeArgs = switch (name) { 'Array' || 'Promise' => 1, _ => 0 };
120+
if (jsName case final typeName?) {
121+
return BuiltinType(
122+
name: typeName,
123+
fromDartJSInterop: true,
124+
typeParams: typeParams.take(jsTypeArgs).toList(),
125+
isNullable: isNullable);
126+
}
127+
return null;
128+
}
129+
}
130+
131+
class PackageWebType extends Type {
132+
@override
133+
final String name;
134+
135+
final List<Type> typeParams;
136+
137+
final bool? isNullable;
138+
139+
@override
140+
ID get id => ID(type: 'type', name: name);
141+
142+
@override
143+
String? get dartName => null;
144+
145+
PackageWebType._(
146+
{required this.name, this.typeParams = const [], this.isNullable});
147+
148+
@override
149+
Reference emit([TypeOptions? options]) {
150+
options ??= TypeOptions();
151+
152+
// TODO: We can make this a shared function as it is called a lot
153+
// between types
154+
return TypeReference((t) => t
155+
..symbol = name
156+
..types.addAll(typeParams
157+
// if there is only one type param, and it is void, ignore
158+
.where((p) => typeParams.length != 1 || p != BuiltinType.$voidType)
159+
.map((p) => p.emit(TypeOptions())))
160+
..url = 'package:web/web.dart'
161+
..isNullable = isNullable ?? options!.nullable);
162+
}
163+
164+
static PackageWebType parse(String name,
165+
{bool? isNullable, List<Type> typeParams = const []}) {
166+
return PackageWebType._(
167+
name: renameMap.containsKey(name) ? renameMap[name]! : name,
168+
isNullable: isNullable,
169+
typeParams: typeParams);
170+
}
100171
}
101172

102173
enum PrimitiveType {
@@ -110,7 +181,7 @@ enum PrimitiveType {
110181
object,
111182
unknown,
112183
undefined,
184+
symbol,
113185
array,
114-
promise,
115-
function
186+
bigint
116187
}

web_generator/lib/src/ast/helpers.dart

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,6 @@ import 'builtin.dart';
99
import 'declarations.dart';
1010
import 'types.dart';
1111

12-
BuiltinType? getSupportedType(String name, [List<Type> typeParams = const []]) {
13-
final type = switch (name) {
14-
'Array' => PrimitiveType.array,
15-
'Promise' => PrimitiveType.promise,
16-
_ => null
17-
};
18-
19-
if (type == null) return null;
20-
21-
return BuiltinType.primitiveType(type, typeParams: [
22-
getJSTypeAlternative(typeParams.singleOrNull ?? BuiltinType.anyType)
23-
]);
24-
}
25-
2612
Type getJSTypeAlternative(Type type) {
2713
if (type is BuiltinType) {
2814
if (type.fromDartJSInterop) return type;

web_generator/lib/src/dart_main.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'generate_bindings.dart';
1616
import 'interop_gen/parser.dart';
1717
import 'interop_gen/transform.dart';
1818
import 'js/filesystem_api.dart';
19+
import 'js/node.dart';
1920
import 'util.dart';
2021

2122
// Generates DOM bindings for Dart.
@@ -102,9 +103,30 @@ Future<void> generateIDLBindings({
102103

103104
ensureDirectoryExists('$output/$librarySubDir');
104105

105-
final bindings = await generateBindings(packageRoot, librarySubDir,
106+
final (bindings, renameMap) = await generateBindings(
107+
packageRoot, librarySubDir,
106108
generateAll: generateAll);
107109

110+
if (renameMap.isNotEmpty) {
111+
final lib = code.Library((l) => l
112+
..comments.add('''
113+
// Copyright (c) ${DateTime.now().year}, the Dart project authors. Please see the AUTHORS file
114+
// for details. All rights reserved. Use of this source code is governed by a
115+
// BSD-style license that can be found in the LICENSE file.
116+
117+
// AUTO GENERATED: DO NOT EDIT
118+
''')
119+
..body.add(code.Field((f) => f
120+
..name = 'renameMap'
121+
..type = code.refer('Map<String, String>')
122+
..modifier = code.FieldModifier.constant
123+
..assignment = code.literalConstMap(renameMap).code)));
124+
final libCode = _emitLibrary(lib, languageVersion);
125+
fs.writeFileSync(
126+
p.join(p.dirname(p.fromUri(url)), 'web_rename_map.dart').toJS,
127+
libCode.toJS);
128+
}
129+
108130
for (var entry in bindings.entries) {
109131
final libraryPath = entry.key;
110132
final library = entry.value;

web_generator/lib/src/generate_bindings.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Future<Map<String, Set<String>>> _generateElementTagMap() async {
7171
return elementMap;
7272
}
7373

74-
Future<TranslationResult> generateBindings(
74+
Future<(TranslationResult, Map<String, String>)> generateBindings(
7575
String packageRoot, String librarySubDir,
7676
{required bool generateAll}) async {
7777
final cssStyleDeclarations = await _generateCSSStyleDeclarations();
@@ -87,7 +87,10 @@ Future<TranslationResult> generateBindings(
8787
translator.collect(shortname, ast);
8888
}
8989
translator.addInterfacesAndNamespaces();
90-
return translator.translate();
90+
final result = translator.translate();
91+
final renamedTypes = translator.renamedClasses;
92+
93+
return (result, renamedTypes);
9194
}
9295

9396
Future<TranslationResult> generateBindingsForFiles(

web_generator/lib/src/interop_gen/transform/transformer.dart

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:js_interop';
6+
import 'package:path/path.dart' as p;
67
import '../../ast/base.dart';
78
import '../../ast/builtin.dart';
89
import '../../ast/declarations.dart';
@@ -180,16 +181,6 @@ class Transformer {
180181
}).toList();
181182
}
182183

183-
TSNode? _getDeclarationByName(TSIdentifier name) {
184-
final symbol = typeChecker.getSymbolAtLocation(name);
185-
186-
final declarations = symbol?.getDeclarations();
187-
// TODO(https://github.com/dart-lang/web/issues/387): Some declarations may not be defined on file,
188-
// and may be from an import statement
189-
// We should be able to handle these
190-
return declarations?.toDart.first;
191-
}
192-
193184
TypeAliasDeclaration _transformTypeAlias(TSTypeAliasDeclaration typealias) {
194185
final name = typealias.name.text;
195186

@@ -205,9 +196,6 @@ class Transformer {
205196

206197
return TypeAliasDeclaration(
207198
name: name,
208-
// TODO: Can we find a way not to make the types be JS types
209-
// by default if possible. Leaving this for now,
210-
// so that using such typealiases in generics does not break
211199
type: _transformType(type),
212200
typeParameters:
213201
typeParams?.map(_transformTypeParamDeclaration).toList() ?? [],
@@ -293,22 +281,41 @@ class Transformer {
293281
// TODO(https://github.com/dart-lang/web/issues/380): A better name
294282
// for this, and adding support for "supported declarations"
295283
// (also a better name for that)
296-
final supportedType = getSupportedType(
297-
name,
298-
(typeArguments ?? [])
299-
.map((type) => _transformType(type, typeArg: true))
284+
final supportedType = BuiltinType.referred(name,
285+
typeParams: (typeArguments ?? [])
286+
.map((t) => getJSTypeAlternative(_transformType(t)))
300287
.toList());
301-
if (supportedType != null) {
302-
return supportedType;
288+
if (supportedType case final resultType?) {
289+
return resultType;
303290
}
304291

292+
final symbol = typeChecker.getSymbolAtLocation(refType.typeName);
293+
final declarations = symbol?.getDeclarations();
294+
305295
// TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
306-
final declaration = _getDeclarationByName(refType.typeName);
296+
// TODO(https://github.com/dart-lang/web/issues/387): Some declarations may not be defined on file,
297+
// and may be from an import statement
298+
// We should be able to handle these
299+
final declaration = declarations?.toDart.first;
307300

308301
if (declaration == null) {
309302
throw Exception('Found no declaration matching $name');
310303
}
311304

305+
// check if this is from dom
306+
final declarationSource = declaration.getSourceFile().fileName;
307+
if (p.basename(declarationSource) == 'lib.dom.d.ts' ||
308+
declarationSource.contains('dom')) {
309+
// dom declaration: supported by package:web
310+
// TODO(nikeokoronkwo): It is possible that we may get a type
311+
// that isn't in `package:web`
312+
return PackageWebType.parse(name,
313+
typeParams: (typeArguments ?? [])
314+
.map(_transformType)
315+
.map(getJSTypeAlternative)
316+
.toList());
317+
}
318+
312319
if (declaration.kind == TSSyntaxKind.TypeParameter) {
313320
return GenericType(name: name);
314321
}
@@ -430,6 +437,8 @@ class Transformer {
430437
TSSyntaxKind.UnknownKeyword => PrimitiveType.unknown,
431438
TSSyntaxKind.BooleanKeyword => PrimitiveType.boolean,
432439
TSSyntaxKind.VoidKeyword => PrimitiveType.$void,
440+
TSSyntaxKind.BigIntKeyword => PrimitiveType.bigint,
441+
TSSyntaxKind.SymbolKeyword => PrimitiveType.symbol,
433442
_ => throw UnsupportedError(
434443
'The given type with kind ${type.kind} is not supported yet')
435444
};

web_generator/lib/src/js/node.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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+
@JS()
8+
external String get url;

web_generator/lib/src/js/typescript.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ extension type TSTypeChecker._(JSObject _) implements JSObject {
4343
}
4444

4545
@JS('SourceFile')
46-
extension type TSSourceFile._(JSObject _) implements TSNode {}
46+
extension type TSSourceFile._(JSObject _) implements TSDeclaration {
47+
external String fileName;
48+
}
4749

4850
extension type TSNodeCallback<T extends JSAny>._(JSObject _)
4951
implements JSObject {

web_generator/lib/src/js/typescript.types.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ extension type const TSSyntaxKind._(num _) {
5151
static const TSSyntaxKind SetKeyword = TSSyntaxKind._(153);
5252
static const TSSyntaxKind UnknownKeyword = TSSyntaxKind._(159);
5353
static const TSSyntaxKind VoidKeyword = TSSyntaxKind._(116);
54+
static const TSSyntaxKind BigIntKeyword = TSSyntaxKind._(163);
55+
static const TSSyntaxKind SymbolKeyword = TSSyntaxKind._(155);
5456

5557
// types
5658
static const TSSyntaxKind UnionType = TSSyntaxKind._(192);
@@ -80,6 +82,7 @@ extension type TSNode._(JSObject _) implements JSObject {
8082
external TSNodeFlags get flags;
8183
external String getText([TSSourceFile? sourceFile]);
8284
external String getFullText([TSSourceFile? sourceFile]);
85+
external TSSourceFile getSourceFile();
8386
}
8487

8588
@JS('TypeNode')

web_generator/lib/src/main.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ globalThis.idl = idl;
2323
globalThis.webidl2 = webidl2;
2424
globalThis.ts = ts;
2525
globalThis.location = { href: `file://${process.cwd()}/` }
26+
globalThis.url = import.meta.url;
2627

2728
globalThis.dartMainRunner = async function (main, args) {
2829
const dartArgs = process.argv.slice(2);

web_generator/lib/src/translator.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,9 @@ class Translator {
661661
final _interfacelikes = <String, _PartialInterfacelike>{};
662662
final _includes = <String, List<String>>{};
663663
final _usedTypes = <idl.Node>{};
664+
final _renamedClasses = <String, String>{};
665+
666+
Map<String, String> get renamedClasses => _renamedClasses;
664667

665668
late String _currentlyTranslatingUrl;
666669
late DocProvider docProvider;
@@ -1356,6 +1359,10 @@ class Translator {
13561359

13571360
final rawType = _RawType(dartClassName, false);
13581361

1362+
if (!isNamespace && jsName != dartClassName) {
1363+
_renamedClasses[jsName] = dartClassName;
1364+
}
1365+
13591366
return [
13601367
if (getterName != null) _topLevelGetter(rawType, getterName),
13611368
_extensionType(

0 commit comments

Comments
 (0)