Skip to content

Commit ca36e11

Browse files
BridgeJS: TS2Skeleton now generates macro-annotated Swift code instead of JSON skeletons.
1 parent c8d8cdd commit ca36e11

File tree

6 files changed

+256
-38
lines changed

6 files changed

+256
-38
lines changed

Benchmarks/Sources/bridge-js.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
declare function benchmarkHelperNoop(): void;
2-
declare function benchmarkHelperNoopWithNumber(n: number): void;
3-
declare function benchmarkRunner(name: string, body: (n: number) => void): void;
1+
export function benchmarkHelperNoop(): void;
2+
export function benchmarkHelperNoopWithNumber(n: number): void;
3+
export function benchmarkRunner(name: string, body: (n: number) => void): void;

Plugins/BridgeJS/Sources/BridgeJSCore/ImportSwiftMacros.swift

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ public final class ImportSwiftMacros {
3333

3434
for (sourceFile, inputFilePath) in sourceFiles {
3535
progress.print("Processing \(inputFilePath)")
36-
let collector = APICollector(inputFilePath: inputFilePath)
36+
let collector = APICollector(
37+
inputFilePath: inputFilePath,
38+
knownJSClassNames: Self.collectJSClassNames(from: sourceFile)
39+
)
3740
collector.walk(sourceFile)
3841
if !collector.errors.isEmpty {
3942
perSourceErrors.append((inputFilePath: inputFilePath, errors: collector.errors))
@@ -66,22 +69,46 @@ public final class ImportSwiftMacros {
6669
return (outputSwift: outputSwift, outputSkeleton: moduleSkeleton)
6770
}
6871

72+
private static func collectJSClassNames(from sourceFile: SourceFileSyntax) -> Set<String> {
73+
let collector = JSImportTypeNameCollector(viewMode: .sourceAccurate)
74+
collector.walk(sourceFile)
75+
return collector.typeNames
76+
}
77+
78+
private final class JSImportTypeNameCollector: SyntaxAnyVisitor {
79+
var typeNames: Set<String> = []
80+
81+
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
82+
if APICollector.hasJSClassAttributeStatic(node.attributes) {
83+
typeNames.insert(node.name.text)
84+
}
85+
return .visitChildren
86+
}
87+
88+
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
89+
if APICollector.hasJSClassAttributeStatic(node.attributes) {
90+
typeNames.insert(node.name.text)
91+
}
92+
return .visitChildren
93+
}
94+
}
95+
6996
fileprivate final class APICollector: SyntaxAnyVisitor {
7097
var importedFunctions: [ImportedFunctionSkeleton] = []
7198
var importedTypes: [ImportedTypeSkeleton] = []
7299
var errors: [DiagnosticError] = []
73100

74101
private let inputFilePath: String
75-
private var jsClassNames: Set<String> = []
102+
private var jsClassNames: Set<String>
76103

77-
init(inputFilePath: String) {
104+
init(inputFilePath: String, knownJSClassNames: Set<String>) {
78105
self.inputFilePath = inputFilePath
106+
self.jsClassNames = knownJSClassNames
79107
super.init(viewMode: .sourceAccurate)
80108
}
81109

82110
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
83111
if hasJSClassAttribute(node.attributes) {
84-
registerJSClassName(node.name.text)
85112
collectJSImportType(from: node, typeName: node.name.text)
86113
return .skipChildren
87114
}
@@ -90,7 +117,6 @@ public final class ImportSwiftMacros {
90117

91118
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
92119
if hasJSClassAttribute(node.attributes) {
93-
registerJSClassName(node.name.text)
94120
collectJSImportType(from: node, typeName: node.name.text)
95121
return .skipChildren
96122
}
@@ -200,10 +226,6 @@ public final class ImportSwiftMacros {
200226
}
201227
}
202228

203-
private func registerJSClassName(_ name: String) {
204-
jsClassNames.insert(name)
205-
}
206-
207229
private func parseConstructor(
208230
_ initializer: InitializerDeclSyntax,
209231
typeName: String
@@ -259,7 +281,7 @@ public final class ImportSwiftMacros {
259281
return nil
260282
}
261283

262-
let baseName = node.name.text
284+
let baseName = normalizeIdentifier(node.name.text)
263285
let name: String
264286
if isStaticMember, let enclosingTypeName {
265287
name = "\(enclosingTypeName)_\(baseName)"
@@ -318,7 +340,7 @@ public final class ImportSwiftMacros {
318340
let propertyType = parseType(typeAnnotation.type, enclosingTypeName: enclosingTypeName)
319341
let isReadonly = node.bindingSpecifier.tokenKind == .keyword(.let)
320342
return ImportedPropertySkeleton(
321-
name: identifier.identifier.text,
343+
name: normalizeIdentifier(identifier.identifier.text),
322344
isReadonly: isReadonly,
323345
type: propertyType,
324346
documentation: nil
@@ -358,7 +380,7 @@ public final class ImportSwiftMacros {
358380
}
359381

360382
let propertyType = parseType(typeAnnotation.type, enclosingTypeName: enclosingTypeName)
361-
let propertyName = identifier.identifier.text
383+
let propertyName = normalizeIdentifier(identifier.identifier.text)
362384
let isReadonly = node.bindingSpecifier.tokenKind == .keyword(.let)
363385

364386
let prefix: String
@@ -405,7 +427,7 @@ public final class ImportSwiftMacros {
405427
return nil
406428
}
407429
let nameToken = param.secondName ?? param.firstName
408-
let name = nameToken.text
430+
let name = normalizeIdentifier(nameToken.text)
409431
let labelToken = param.secondName == nil ? nil : param.firstName
410432
let label = labelToken?.text == "_" ? nil : labelToken?.text
411433
let bridgeType = parseType(type, enclosingTypeName: enclosingTypeName)
@@ -420,7 +442,7 @@ public final class ImportSwiftMacros {
420442

421443
private func parseType(_ type: TypeSyntax, enclosingTypeName: String?) -> BridgeType {
422444
if let identifier = type.as(IdentifierTypeSyntax.self) {
423-
let name = identifier.name.text
445+
let name = normalizeIdentifier(identifier.name.text)
424446
if name == "Self", let enclosingTypeName {
425447
return .jsObject(enclosingTypeName)
426448
}
@@ -430,13 +452,7 @@ public final class ImportSwiftMacros {
430452
if jsClassNames.contains(name) {
431453
return .jsObject(name)
432454
}
433-
errors.append(
434-
DiagnosticError(
435-
node: type,
436-
message: "Unsupported @JS type '\(name)'."
437-
)
438-
)
439-
return .void
455+
return .jsObject(name)
440456
}
441457

442458
errors.append(
@@ -477,11 +493,26 @@ public final class ImportSwiftMacros {
477493
}
478494
}
479495

496+
static func hasJSClassAttributeStatic(_ attributes: AttributeListSyntax?) -> Bool {
497+
guard let attributes else { return false }
498+
return attributes.contains { attribute in
499+
guard let syntax = attribute.as(AttributeSyntax.self) else { return false }
500+
return ["JSClass", "JSImportClass"].contains(syntax.attributeName.trimmedDescription)
501+
}
502+
}
503+
480504
private func isStatic(_ modifiers: DeclModifierListSyntax?) -> Bool {
481505
guard let modifiers else { return false }
482506
return modifiers.contains { modifier in
483507
modifier.name.tokenKind == .keyword(.static) || modifier.name.tokenKind == .keyword(.class)
484508
}
485509
}
510+
511+
private func normalizeIdentifier(_ name: String) -> String {
512+
guard name.hasPrefix("`"), name.hasSuffix("`"), name.count >= 2 else {
513+
return name
514+
}
515+
return String(name.dropFirst().dropLast())
516+
}
486517
}
487518
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# ts2skeleton
22

3-
This script analyzes the TypeScript type definitions and produces a structured JSON output with skeleton information that can be used to generate Swift bindings.
3+
This script analyzes the TypeScript type definitions and produces macro-annotated Swift declarations that can be fed into BridgeJS for glue generation.

Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @ts-check
22
import * as fs from 'fs';
3-
import { TypeProcessor } from './processor.js';
3+
import { TypeProcessor, renderMacroSwift } from './processor.js';
44
import { parseArgs } from 'util';
55
import ts from 'typescript';
66
import path from 'path';
@@ -73,7 +73,7 @@ class DiagnosticEngine {
7373
}
7474

7575
function printUsage() {
76-
console.error('Usage: ts2skeleton <d.ts file path> -p <tsconfig.json path> [-o output.json]');
76+
console.error('Usage: ts2skeleton <d.ts file path> -p <tsconfig.json path> [-o output.swift]');
7777
}
7878

7979
/**
@@ -140,12 +140,10 @@ export function main(args) {
140140

141141
const processor = new TypeProcessor(program.getTypeChecker(), diagnosticEngine);
142142
const results = processor.processTypeDeclarations(program, filePath);
143-
144-
// Write results to file or stdout
145-
const jsonOutput = JSON.stringify(results, null, 2);
143+
const swiftOutput = renderMacroSwift(results);
146144
if (options.values.output) {
147-
fs.writeFileSync(options.values.output, jsonOutput);
145+
fs.writeFileSync(options.values.output, swiftOutput);
148146
} else {
149-
process.stdout.write(jsonOutput, "utf-8");
147+
process.stdout.write(swiftOutput, "utf-8");
150148
}
151149
}

0 commit comments

Comments
 (0)