Skip to content
Draft
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
22 changes: 17 additions & 5 deletions web_generator/bin/gen_interop_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:args/args.dart';
import 'package:io/ansi.dart' as ansi;
import 'package:io/io.dart';
import 'package:path/path.dart' as p;
import 'package:web_generator/src/base_path.dart';
import 'package:web_generator/src/cli.dart';

void main(List<String> arguments) async {
Expand Down Expand Up @@ -58,10 +59,20 @@ $_usage''');
await compileDartMain();
}

// TODO(nikeokoronkwo): Multi-file input
final inputFile = argResult.rest.firstOrNull;
final outputFile = argResult['output'] as String? ??
p.join(p.current, inputFile?.replaceAll('.d.ts', '.dart'));
final inputFiles = argResult.rest;
if (inputFiles.isEmpty) {
throw Exception('Input files is empty: $_usage');
}
final String outputFile;
if (argResult['output'] case final out?) {
outputFile = out as String;
} else if (inputFiles.singleOrNull case final singleInput?) {
outputFile = p.join(p.current, singleInput.replaceAll('.d.ts', '.dart'));
} else {
final base = basePath(inputFiles);
outputFile =
p.join(p.current, p.extension(base) == '' ? base : p.dirname(base));
}
final defaultWebGenConfigPath = p.join(p.current, 'webgen.yaml');
final configFile = argResult['config'] as String? ??
(File(defaultWebGenConfigPath).existsSync()
Expand All @@ -83,7 +94,8 @@ $_usage''');
'main.mjs',
'--declaration',
if (argResult.rest.isNotEmpty) ...[
'--input=${p.relative(inputFile!, from: bindingsGeneratorPath)}',
...inputFiles.map(
(inp) => '--input=${p.relative(inp, from: bindingsGeneratorPath)}'),
'--output=$relativeOutputPath',
],
if (tsConfigRelativePath case final tsConfig?) '--ts-config=$tsConfig',
Expand Down
4 changes: 4 additions & 0 deletions web_generator/lib/src/_js_supertypes_src.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import 'dart:js_interop';

@JS()
external JSPromise get promise;
32 changes: 29 additions & 3 deletions web_generator/lib/src/ast/declarations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:code_builder/code_builder.dart';
import 'package:collection/collection.dart';

import '../interop_gen/namer.dart';
import '../interop_gen/qualified_name.dart';
import '../interop_gen/transform.dart';
import '../js/typescript.types.dart';
import 'base.dart';
import 'builtin.dart';
Expand All @@ -27,8 +29,10 @@ abstract class NestableDeclaration extends NamedDeclaration
: (dartName ?? name);
}

abstract class ParentDeclaration {
abstract class ParentDeclaration extends NestableDeclaration {
Set<TSNode> get nodes;

NodeMap<Declaration> get nodeMap;
}

/// A declaration that defines a type (class or interface)
Expand Down Expand Up @@ -490,6 +494,8 @@ class TypeAliasDeclaration extends NestableDeclaration

final Type type;

final String namedReference;

@override
String? dartName;

Expand All @@ -507,6 +513,7 @@ class TypeAliasDeclaration extends NestableDeclaration
this.typeParameters = const [],
required this.type,
required this.exported,
required this.namedReference,
this.documentation,
this.parent})
: dartName = null;
Expand All @@ -531,8 +538,8 @@ class TypeAliasDeclaration extends NestableDeclaration

/// The declaration node for a TypeScript Namespace
// TODO: Refactor into shared class when supporting modules
class NamespaceDeclaration extends NestableDeclaration
implements ExportableDeclaration, ParentDeclaration {
class NamespaceDeclaration extends ParentDeclaration
implements ExportableDeclaration {
@override
String name;

Expand Down Expand Up @@ -648,6 +655,25 @@ class NamespaceDeclaration extends NestableDeclaration

CompositeDeclaration get asComposite =>
CompositeDeclaration.fromNamespace(this);

@override
NodeMap<Declaration> get nodeMap => NodeMap({
...{for (final decl in topLevelDeclarations) decl.id.toString(): decl},
...[...nestableDeclarations, ...namespaceDeclarations]
.asMap()
.map((_, v) {
final mainName = QualifiedName.raw(v.id.name);
return MapEntry(
ID(
type: v.id.type,
index: v.id.index,
name: mainName.length > 1
? mainName.skip(1).map((p) => p.part).join('.')
: mainName.asName)
.toString(),
v);
})
});
}

/// A composite declaration is formed from merging declarations together,
Expand Down
3 changes: 2 additions & 1 deletion web_generator/lib/src/ast/merger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@ InterfaceDeclaration mergeInterfaces(List<InterfaceDeclaration> interfaces,
id: ID(
type: referenceInterface.id.type, name: referenceInterface.id.name),
typeParameters: interfaces.map((i) => i.typeParameters).flattenedToSet,
extendedTypes: interfaces.map((i) => i.extendedTypes).flattenedToList,
extendedTypes:
interfaces.map((i) => i.extendedTypes).flattenedToSet.toList(),
properties: interfaces
.map((i) => _rescopeDecls(i.properties, namer: namer))
.flattenedToList,
Expand Down
31 changes: 31 additions & 0 deletions web_generator/lib/src/base_path.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2023, 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.

import 'package:collection/collection.dart';
import 'package:path/path.dart' as p;

/// Gets the base, shared path between a list of paths
///
/// The paths must either all be absolute, or from the
/// same origin
String basePath(List<String> paths) {
assert(paths.isNotEmpty, 'No paths provided');

if (paths.singleOrNull case final singlePath?) return singlePath;

final splitPaths = paths.map(p.split);
final minimumSplit = splitPaths.map((p) => p.length).min;

final finalParts = <String>[];

final referenceParts = splitPaths.first;

for (var i = 0; i < minimumSplit; ++i) {
if (splitPaths.every((parts) => parts[i] == referenceParts[i])) {
finalParts.add(referenceParts[i]);
}
}

return finalParts.join(p.separator);
}
86 changes: 68 additions & 18 deletions web_generator/lib/src/interop_gen/transform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
// 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.

import 'dart:collection';
import 'dart:convert';
import 'dart:js_interop';

import 'package:code_builder/code_builder.dart';
import 'package:collection/collection.dart';
import 'package:dart_style/dart_style.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;

import '../ast/base.dart';
import '../ast/declarations.dart';
import '../ast/helpers.dart';
import '../ast/types.dart';
import '../config.dart';
import '../js/helpers.dart';
import '../js/typescript.dart' as ts;
Expand Down Expand Up @@ -118,13 +121,22 @@ extension type NodeMap<N extends Node>._(Map<String, N> decls)

List<N> findByQualifiedName(QualifiedName qName) {
return decls.entries
.where((e) {
.map((e) {
final name = UniqueNamer.parse(e.key).name;
final qualifiedName = QualifiedName.raw(name);
return qualifiedName.map((n) => n.part) == qName.map((n) => n.part);
if (e.value case ParentDeclaration(nodeMap: final parentNodeMap)
when qName.length > 1) {
return parentNodeMap
.findByQualifiedName(QualifiedName.raw(
qName.skip(1).map((p) => p.part).join('.')))
.whereType<N>();
}
return qualifiedName.map((n) => n.part) == qName.map((n) => n.part)
? [e.value]
: null;
})
.map((e) => e.value)
.toList();
.nonNulls
.flattenedToList;
}

void add(N decl) => decls[decl.id.toString()] = decl;
Expand Down Expand Up @@ -201,7 +213,8 @@ class ProgramMap {

/// Find the node definition for a given declaration named [declName]
/// or associated with a TypeScript node [node] from the map of files
List<Node>? getDeclarationRef(String file, TSNode node, [String? declName]) {
List<Node>? getDeclarationRef(String file, TSNode node,
[QualifiedName? declName, TSSymbol? symbol]) {
// check
NodeMap nodeMap;
if (_pathMap.containsKey(file)) {
Expand All @@ -216,27 +229,59 @@ class ProgramMap {
final anonymousTransformer = _activeTransformers.putIfAbsent(
file, () => Transformer(this, null, file: file));

// TODO: Replace with .transformAndReturn once #388 lands
return anonymousTransformer.transformAndReturn(node);
if (declName == null) {
return anonymousTransformer.transformAndReturn(node);
} else {
final referredTypes = anonymousTransformer
.searchForDeclRecursive(
declName,
(symbol ?? typeChecker.getSymbolAtLocation(node))!,
)
.whereType<ReferredType>();
return referredTypes.map((r) => r.declaration).toList();
}
} else {
final transformer =
_activeTransformers.putIfAbsent(file, () => Transformer(this, src));

if (!transformer.nodes.contains(node)) {
// node might be nested
if (declName case final d?
when transformer.nodeMap.findByName(d).isEmpty) {
when transformer.nodeMap.findByQualifiedName(d).isEmpty) {
// find the source file decl
if (src == null) return null;

final symbol = typeChecker.getSymbolAtLocation(src)!;
final exports = symbol.exports?.toDart ?? {};

final targetSymbol = exports[d.toJS]!;

for (final decl in targetSymbol.getDeclarations()?.toDart ??
<TSDeclaration>[]) {
transformer.transform(decl);
final srcSymbol = typeChecker.getSymbolAtLocation(src);
// print((declName, file, node.kind, transformer.nodeMap));
if (srcSymbol == null) {
ts.forEachChild(
src,
(TSNode node) {
if (node.kind == TSSyntaxKind.EndOfFileToken) return;

transformer.transform(node);
}.toJS as ts.TSNodeCallback);
} else {
final exports = srcSymbol.exports?.toDart ?? {};

final targetSymbol = exports[d.first.part.toJS]!;

final referredTypes = transformer
.searchForDeclRecursive(
declName,
targetSymbol,
)
.whereType<ReferredType>();
return referredTypes.map((r) => r.declaration).toList();
}
} else if (declName != null) {
final referredTypes = transformer
.searchForDeclRecursive(
declName,
(symbol ?? typeChecker.getSymbolAtLocation(node))!,
)
.whereType<ReferredType>();
return referredTypes.map((r) => r.declaration).toList();
} else {
transformer.transform(node);
}
Expand All @@ -247,8 +292,13 @@ class ProgramMap {
}
}

final name = declName ?? (node as TSNamedDeclaration).name?.text;
return name == null ? null : nodeMap.findByName(name);
final name = declName?.asName ?? (node as TSNamedDeclaration).name?.text;
return switch (name) {
final String n when n.contains('.') =>
nodeMap.findByQualifiedName(declName ?? QualifiedName.raw(name)),
null => null,
_ => nodeMap.findByName(name)
};
}

(String, NamedDeclaration)? getCommonType(String name,
Expand Down
16 changes: 16 additions & 0 deletions web_generator/lib/src/interop_gen/transform/recursion.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import '../../js/typescript.dart';
import '../../js/typescript.types.dart';

class RecursionMap {
String kind;
String name;
Map<String, RecursionMap> children;

RecursionMap(this.kind, this.name, {this.children = const {}});
}

RecursionMap exploreType(TSType node, {
required TSTypeChecker typeChecker
}) {

}
Loading
Loading