Skip to content

Commit 228b50f

Browse files
- Added Record detection in Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js, mapping Record<string, T> to [String: T] (with JSObject fallback for non-string keys) by reading alias type arguments even when the type isn’t a TypeReference.
- Added a new fixture and refreshed Vitest snapshots to cover dictionary outputs, nested/optional records, array/object values, and unsupported key handling in `Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/RecordDictionary.d.ts` and `.../__snapshots__/ts2swift.test.js.snap`. Tests: `cd Plugins/BridgeJS/Sources/TS2Swift/JavaScript && npm test -- --update`.
1 parent ee99ad9 commit 228b50f

File tree

3 files changed

+101
-0
lines changed

3 files changed

+101
-0
lines changed

Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ export class TypeProcessor {
663663
* @private
664664
*/
665665
visitType(type, node) {
666+
const typeArguments = this.getTypeArguments(type);
666667
if (this.checker.isArrayType(type)) {
667668
const typeArgs = this.checker.getTypeArguments(type);
668669
if (typeArgs && typeArgs.length > 0) {
@@ -672,6 +673,11 @@ export class TypeProcessor {
672673
return "[JSObject]";
673674
}
674675

676+
const recordType = this.convertRecordType(type, typeArguments, node);
677+
if (recordType) {
678+
return recordType;
679+
}
680+
675681
// Treat A<B> and A<C> as the same type
676682
if (isTypeReference(type)) {
677683
type = type.target;
@@ -760,6 +766,54 @@ export class TypeProcessor {
760766
return swiftType;
761767
}
762768

769+
/**
770+
* Convert a `Record<string, T>` TypeScript type into a Swift dictionary type.
771+
* Falls back to `JSObject` when keys are not string-compatible or type arguments are missing.
772+
* @param {ts.Type} type
773+
* @param {ts.Type[]} typeArguments
774+
* @param {ts.Node} node
775+
* @returns {string | null}
776+
* @private
777+
*/
778+
convertRecordType(type, typeArguments, node) {
779+
const symbol = type.aliasSymbol ?? type.getSymbol();
780+
if (!symbol || symbol.name !== "Record") {
781+
return null;
782+
}
783+
if (typeArguments.length !== 2) {
784+
this.diagnosticEngine.print("warning", "Record expects two type arguments", node);
785+
return "JSObject";
786+
}
787+
const [keyType, valueType] = typeArguments;
788+
const stringType = this.checker.getStringType();
789+
if (!this.checker.isTypeAssignableTo(keyType, stringType)) {
790+
this.diagnosticEngine.print(
791+
"warning",
792+
`Record key type must be assignable to string: ${this.checker.typeToString(keyType)}`,
793+
node
794+
);
795+
return "JSObject";
796+
}
797+
798+
const valueSwiftType = this.visitType(valueType, node);
799+
return `[String: ${valueSwiftType}]`;
800+
}
801+
802+
/**
803+
* Retrieve type arguments for a given type, including type alias instantiations.
804+
* @param {ts.Type} type
805+
* @returns {ts.Type[]}
806+
* @private
807+
*/
808+
getTypeArguments(type) {
809+
if (isTypeReference(type)) {
810+
return this.checker.getTypeArguments(type);
811+
}
812+
// Non-TypeReference alias instantiations store type arguments separately.
813+
// @ts-ignore: `aliasTypeArguments` is intentionally accessed for alias instantiations.
814+
return type.aliasTypeArguments ?? [];
815+
}
816+
763817
/**
764818
* Derive the type name from a type
765819
* @param {ts.Type} type - TypeScript type

Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,36 @@ exports[`ts2swift > snapshots Swift output for ReExportFrom.d.ts > ReExportFrom
251251
"
252252
`;
253253
254+
exports[`ts2swift > snapshots Swift output for RecordDictionary.d.ts > RecordDictionary 1`] = `
255+
"// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
256+
// DO NOT EDIT.
257+
//
258+
// To update this file, just rebuild your project or run
259+
// \`swift package bridge-js\`.
260+
261+
@_spi(Experimental) @_spi(BridgeJS) import JavaScriptKit
262+
263+
@JSFunction func takeRecord(_ value: [String: Double]) throws(JSException) -> Void
264+
265+
@JSFunction func returnRecord() throws(JSException) -> [String: String]
266+
267+
@JSFunction func optionalRecord(_ value: Optional<[String: Bool]>) throws(JSException) -> Optional<[String: Bool]>
268+
269+
@JSFunction func nestedRecord(_ value: [String: [String: Double]]) throws(JSException) -> [String: [String: Double]]
270+
271+
@JSFunction func recordWithArrayValues(_ values: [String: [Double]]) throws(JSException) -> [String: [Double]]
272+
273+
@JSFunction func recordWithObjects(_ values: [String: Optional<Box>]) throws(JSException) -> [String: Optional<Box>]
274+
275+
@JSClass struct Box {
276+
@JSGetter var value: Double
277+
@JSSetter func setValue(_ value: Double) throws(JSException)
278+
}
279+
280+
@JSFunction func unsupportedKeyRecord(_ values: JSObject) throws(JSException) -> Void
281+
"
282+
`;
283+
254284
exports[`ts2swift > snapshots Swift output for StringEnum.d.ts > StringEnum 1`] = `
255285
"// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
256286
// DO NOT EDIT.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export interface Box {
2+
value: number;
3+
}
4+
5+
export function takeRecord(value: Record<string, number>): void;
6+
7+
export function returnRecord(): Record<string, string>;
8+
9+
export function optionalRecord(value: Record<string, boolean> | null): Record<string, boolean> | null;
10+
11+
export function nestedRecord(value: Record<string, Record<string, number>>): Record<string, Record<string, number>>;
12+
13+
export function recordWithArrayValues(values: Record<string, number[]>): Record<string, number[]>;
14+
15+
export function recordWithObjects(values: Record<string, Box | null>): Record<string, Box | null>;
16+
17+
export function unsupportedKeyRecord(values: Record<number, string>): void;

0 commit comments

Comments
 (0)