From bf6b30751228e8f328f0dab08d475ef1ecc86386 Mon Sep 17 00:00:00 2001 From: k-vasily Date: Wed, 11 Mar 2026 16:48:49 +0000 Subject: [PATCH 1/2] feat: add --prefer-unknown option for TypeScript and Flow Add a `--prefer-unknown` CLI flag that emits `unknown` instead of `any` in TypeScript output, and `mixed` instead of `any` in Flow output. This is a fresh implementation of the feature proposed in PR #2194 by @RichiCoder1, adapted to the current codebase structure where TypeScriptFlow was split into multiple files. Closes #1619 --- .../language/TypeScriptFlow/FlowRenderer.ts | 15 +++++++++++++-- .../TypeScriptFlowBaseRenderer.ts | 13 ++++++++----- .../TypeScriptFlow/TypeScriptRenderer.ts | 19 +++++++++++++++---- .../src/language/TypeScriptFlow/language.ts | 5 +++++ test/languages.ts | 1 + 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/packages/quicktype-core/src/language/TypeScriptFlow/FlowRenderer.ts b/packages/quicktype-core/src/language/TypeScriptFlow/FlowRenderer.ts index 02e82aee57..99ead7d74f 100644 --- a/packages/quicktype-core/src/language/TypeScriptFlow/FlowRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptFlow/FlowRenderer.ts @@ -5,15 +5,26 @@ import type { ClassType, EnumType } from "../../Type"; import type { JavaScriptTypeAnnotations } from "../JavaScript"; import { TypeScriptFlowBaseRenderer } from "./TypeScriptFlowBaseRenderer"; -import { tsFlowTypeAnnotations } from "./utils"; export class FlowRenderer extends TypeScriptFlowBaseRenderer { + protected anyType(): string { + return this._tsFlowOptions.preferUnknown ? "mixed" : "any"; + } + protected forbiddenNamesForGlobalNamespace(): string[] { return ["Class", "Date", "Object", "String", "Array", "JSON", "Error"]; } protected get typeAnnotations(): JavaScriptTypeAnnotations { - return Object.assign({ never: "" }, tsFlowTypeAnnotations); + const a = this.anyType(); + return Object.assign({ never: "" }, { + any: `: ${a}`, + anyArray: `: ${a}[]`, + anyMap: `: { [k: string]: ${a} }`, + string: ": string", + stringArray: ": string[]", + boolean: ": boolean", + }); } protected emitEnum(e: EnumType, enumName: Name): void { diff --git a/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptFlowBaseRenderer.ts b/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptFlowBaseRenderer.ts index 2acc4da237..95d6dd8ec2 100644 --- a/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptFlowBaseRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptFlowBaseRenderer.ts @@ -64,7 +64,7 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer { return matchType( t, - (_anyType) => singleWord("any"), + (_anyType) => singleWord(this.anyType()), (_nullType) => singleWord("null"), (_boolType) => singleWord("boolean"), (_integerType) => singleWord("number"), @@ -113,6 +113,8 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer { ); } + protected abstract anyType(): string; + protected abstract emitEnum(e: EnumType, enumName: Name): void; protected abstract emitClassBlock(c: ClassType, className: Name): void; @@ -198,7 +200,7 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer { protected deserializerFunctionLine(t: Type, name: Name): Sourcelike { const jsonType = - this._tsFlowOptions.rawType === "json" ? "string" : "any"; + this._tsFlowOptions.rawType === "json" ? "string" : this.anyType(); return [ "function to", name, @@ -212,7 +214,7 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer { protected serializerFunctionLine(t: Type, name: Name): Sourcelike { const camelCaseName = modifySource(camelCase, name); const returnType = - this._tsFlowOptions.rawType === "json" ? "string" : "any"; + this._tsFlowOptions.rawType === "json" ? "string" : this.anyType(); return [ "function ", camelCaseName, @@ -228,9 +230,10 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer { } protected get castFunctionLines(): [string, string] { + const a = this.anyType(); return [ - "function cast(val: any, typ: any): T", - "function uncast(val: T, typ: any): any", + `function cast(val: ${a}, typ: ${a}): T`, + `function uncast(val: T, typ: ${a}): ${a}`, ]; } diff --git a/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptRenderer.ts b/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptRenderer.ts index 82d272484c..ceb00a668f 100644 --- a/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptFlow/TypeScriptRenderer.ts @@ -6,16 +6,19 @@ import { isNamedType } from "../../Type/TypeUtils"; import type { JavaScriptTypeAnnotations } from "../JavaScript"; import { TypeScriptFlowBaseRenderer } from "./TypeScriptFlowBaseRenderer"; -import { tsFlowTypeAnnotations } from "./utils"; export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer { + protected anyType(): string { + return this._tsFlowOptions.preferUnknown ? "unknown" : "any"; + } + protected forbiddenNamesForGlobalNamespace(): string[] { return ["Array", "Date"]; } protected deserializerFunctionLine(t: Type, name: Name): Sourcelike { const jsonType = - this._tsFlowOptions.rawType === "json" ? "string" : "any"; + this._tsFlowOptions.rawType === "json" ? "string" : this.anyType(); return [ "public static to", name, @@ -29,7 +32,7 @@ export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer { protected serializerFunctionLine(t: Type, name: Name): Sourcelike { const camelCaseName = modifySource(camelCase, name); const returnType = - this._tsFlowOptions.rawType === "json" ? "string" : "any"; + this._tsFlowOptions.rawType === "json" ? "string" : this.anyType(); return [ "public static ", camelCaseName, @@ -45,7 +48,15 @@ export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer { } protected get typeAnnotations(): JavaScriptTypeAnnotations { - return Object.assign({ never: ": never" }, tsFlowTypeAnnotations); + const a = this.anyType(); + return Object.assign({ never: ": never" }, { + any: `: ${a}`, + anyArray: `: ${a}[]`, + anyMap: `: { [k: string]: ${a} }`, + string: ": string", + stringArray: ": string[]", + boolean: ": boolean", + }); } protected emitModuleExports(): void { diff --git a/packages/quicktype-core/src/language/TypeScriptFlow/language.ts b/packages/quicktype-core/src/language/TypeScriptFlow/language.ts index 1a5bdde8d0..a0427f66f6 100644 --- a/packages/quicktype-core/src/language/TypeScriptFlow/language.ts +++ b/packages/quicktype-core/src/language/TypeScriptFlow/language.ts @@ -40,6 +40,11 @@ export const tsFlowOptions = Object.assign({}, javaScriptOptions, { false, ), readonly: new BooleanOption("readonly", "Use readonly type members", false), + preferUnknown: new BooleanOption( + "prefer-unknown", + "Use unknown instead of any type", + false, + ), }); export const typeScriptLanguageConfig = { diff --git a/test/languages.ts b/test/languages.ts index 9573f3e574..f4f564de4d 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -839,6 +839,7 @@ export const TypeScriptLanguage: Language = { { "acronym-style": "pascal" }, { converters: "all-objects" }, { readonly: "true" }, + { "prefer-unknown": "true" }, ], sourceFiles: ["src/language/TypeScript/index.ts"], }; From 4c999957a1daeb8c84411fe38d4118bf86ba6895 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Sun, 19 Jul 2026 18:38:49 -0400 Subject: [PATCH 2/2] Make prefer-unknown the default for TypeScript and Flow The next release is a major, so the breaking output change is sanctioned: generated type declarations and converter signatures now use unknown (TypeScript) / mixed (Flow) instead of any by default, which also makes the standard typescript/flow fixtures exercise the new output across the whole corpus. Opt out with --no-prefer-unknown (CLI) or "prefer-unknown": false; the quick-test entries pin the legacy any output for both fixtures. Co-Authored-By: Claude Fable 5 --- .../src/language/JavaScript/JavaScriptRenderer.ts | 5 ++++- .../src/language/TypeScriptFlow/FlowRenderer.ts | 8 +++++++- .../src/language/TypeScriptFlow/language.ts | 2 +- test/languages.ts | 4 ++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/quicktype-core/src/language/JavaScript/JavaScriptRenderer.ts b/packages/quicktype-core/src/language/JavaScript/JavaScriptRenderer.ts index 0df9b8ccab..c14a44efdb 100644 --- a/packages/quicktype-core/src/language/JavaScript/JavaScriptRenderer.ts +++ b/packages/quicktype-core/src/language/JavaScript/JavaScriptRenderer.ts @@ -242,7 +242,10 @@ export class JavaScriptRenderer extends ConvenienceRenderer { /** The expression a deserializer returns when runtime typechecks * are disabled. Subclasses can wrap it in a cast to the target * type if `parsedJson`'s type isn't assignable to it. */ - protected uncheckedParsedJson(_t: Type, parsedJson: Sourcelike): Sourcelike { + protected uncheckedParsedJson( + _t: Type, + parsedJson: Sourcelike, + ): Sourcelike { return parsedJson; } diff --git a/packages/quicktype-core/src/language/TypeScriptFlow/FlowRenderer.ts b/packages/quicktype-core/src/language/TypeScriptFlow/FlowRenderer.ts index a67484fcda..9e48bfb147 100644 --- a/packages/quicktype-core/src/language/TypeScriptFlow/FlowRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptFlow/FlowRenderer.ts @@ -25,7 +25,13 @@ export class FlowRenderer extends TypeScriptFlowBaseRenderer { this._tsFlowOptions.rawType !== "json" && this._tsFlowOptions.preferUnknown ) { - return ["((", parsedJson, ": any): ", this.sourceFor(t).source, ")"]; + return [ + "((", + parsedJson, + ": any): ", + this.sourceFor(t).source, + ")", + ]; } return parsedJson; diff --git a/packages/quicktype-core/src/language/TypeScriptFlow/language.ts b/packages/quicktype-core/src/language/TypeScriptFlow/language.ts index f45d9a97a5..a894ee2833 100644 --- a/packages/quicktype-core/src/language/TypeScriptFlow/language.ts +++ b/packages/quicktype-core/src/language/TypeScriptFlow/language.ts @@ -48,7 +48,7 @@ export const tsFlowOptions = { preferUnknown: new BooleanOption( "prefer-unknown", "Use unknown (TypeScript) or mixed (Flow) instead of any", - false, + true, ), }; diff --git a/test/languages.ts b/test/languages.ts index ec3f6a7409..07f9a417a1 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -864,7 +864,7 @@ export const TypeScriptLanguage: Language = { { "acronym-style": "pascal" }, { converters: "all-objects" }, { readonly: "true" }, - { "prefer-unknown": "true" }, + { "prefer-unknown": "false" }, ], sourceFiles: ["src/language/TypeScript/index.ts"], }; @@ -944,7 +944,7 @@ export const FlowLanguage: Language = { { "runtime-typecheck": "false" }, { "runtime-typecheck-ignore-unknown-properties": "true" }, { "nice-property-names": "true" }, - { "prefer-unknown": "true" }, + { "prefer-unknown": "false" }, ], sourceFiles: ["src/language/Flow/index.ts"], };