Skip to content

Commit 9819d06

Browse files
committed
factor out structure for special-casing certain built-in types' schemas
1 parent b959ab2 commit 9819d06

File tree

2 files changed

+59
-14
lines changed

2 files changed

+59
-14
lines changed

packages/schema-generator/src/formatters/object-formatter.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,8 @@ export class ObjectFormatter implements TypeFormatter {
4040
return { type: "object", additionalProperties: true };
4141
}
4242

43-
// Special-case Date to a string with date-time format (match old behavior)
44-
if (type.symbol?.name === "Date" && type.symbol?.valueDeclaration) {
45-
// Check if this is the built-in Date type (not a user-defined type named "Date")
46-
const sourceFile = type.symbol.valueDeclaration.getSourceFile();
47-
48-
if (
49-
sourceFile.fileName.includes("lib.") ||
50-
sourceFile.fileName.includes("typescript/lib") ||
51-
sourceFile.fileName.includes("ES2023.d.ts") ||
52-
sourceFile.fileName.includes("DOM.d.ts")
53-
) {
54-
return { type: "string", format: "date-time" };
55-
}
56-
}
43+
const builtin = this.lookupBuiltInSchema(type);
44+
if (builtin) return builtin;
5745

5846
// Do not early-return for empty object types. Instead, try to enumerate
5947
// properties via the checker to allow type literals to surface members.
@@ -177,4 +165,38 @@ export class ObjectFormatter implements TypeFormatter {
177165

178166
return schema;
179167
}
168+
169+
private lookupBuiltInSchema(type: ts.Type): SchemaDefinition | undefined {
170+
const symbol = type.symbol;
171+
if (!symbol?.valueDeclaration) return undefined;
172+
const builtin = BUILT_IN_SCHEMAS.find((entry) => entry.test(type));
173+
return builtin ? structuredClone(builtin.schema) : undefined;
174+
}
175+
}
176+
177+
type BuiltInSchemaEntry = {
178+
test: (type: ts.Type) => boolean;
179+
schema: SchemaDefinition;
180+
};
181+
182+
const BUILT_IN_SCHEMAS: BuiltInSchemaEntry[] = [
183+
{
184+
test: isNativeType("Date"),
185+
schema: { type: "string", format: "date-time" },
186+
},
187+
];
188+
189+
function isNativeType(name: string) {
190+
return (type: ts.Type) => {
191+
const symbol = type.symbol;
192+
if (!symbol || symbol.name !== name || !symbol.valueDeclaration) {
193+
return false;
194+
}
195+
const sourceFile = symbol.valueDeclaration.getSourceFile();
196+
const fileName = sourceFile.fileName;
197+
return fileName.includes("lib.") ||
198+
fileName.includes("typescript/lib") ||
199+
fileName.includes("ES2023.d.ts") ||
200+
fileName.includes("DOM.d.ts");
201+
};
180202
}

packages/schema-generator/test/schema-generator.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,27 @@ type Wrapper = {
125125
);
126126
});
127127
});
128+
129+
describe("built-in mappings", () => {
130+
it("formats Date as string with date-time format without hoisting", async () => {
131+
const generator = new SchemaGenerator();
132+
const code = `
133+
interface HasDate {
134+
createdAt: Date;
135+
}`;
136+
const { type, checker } = await getTypeFromCode(code, "HasDate");
137+
138+
const schema = generator.generateSchema(type, checker);
139+
const objectSchema = schema as Record<string, unknown>;
140+
const props = objectSchema.properties as
141+
| Record<string, Record<string, unknown>>
142+
| undefined;
143+
144+
expect(objectSchema.definitions).toBeUndefined();
145+
expect(props?.createdAt).toEqual({
146+
type: "string",
147+
format: "date-time",
148+
});
149+
});
150+
});
128151
});

0 commit comments

Comments
 (0)