Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,16 @@ 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 {
return parsedJson;
}

protected emitConvertModuleBody(): void {
const converter = (t: Type, name: Name): void => {
const typeMap = this.typeMapTypeFor(t);
Expand All @@ -251,7 +261,11 @@ export class JavaScriptRenderer extends ConvenienceRenderer {
? "JSON.parse(json)"
: "json";
if (!this._jsOptions.runtimeTypecheck) {
this.emitLine("return ", parsedJson, ";");
this.emitLine(
"return ",
this.uncheckedParsedJson(t, parsedJson),
";",
);
} else {
this.emitLine(
"return cast(",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
import type { Name } from "../../Naming.js";
import type { Sourcelike } from "../../Source.js";
import { utf16StringEscape } from "../../support/Strings.js";
import { defined } from "../../support/Support.js";
import type { ClassType, EnumType } from "../../Type/index.js";
import type { ClassType, EnumType, Type } from "../../Type/index.js";
import type { JavaScriptTypeAnnotations } from "../JavaScript/index.js";

import { TypeScriptFlowBaseRenderer } from "./TypeScriptFlowBaseRenderer.js";
import { tsFlowTypeAnnotations } from "./utils.js";

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 uncheckedParsedJson(t: Type, parsedJson: Sourcelike): Sourcelike {
// With `raw-type any` and `prefer-unknown` the deserializer's
// parameter is `mixed`, which can't be returned as the target
// type without a cast.
if (
this._tsFlowOptions.rawType !== "json" &&
this._tsFlowOptions.preferUnknown
) {
return [
"((",
parsedJson,
": any): ",
this.sourceFor(t).source,
")",
];
}

return parsedJson;
}

protected get typeAnnotations(): JavaScriptTypeAnnotations {
return { never: "", ...tsFlowTypeAnnotations };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer {

return matchType<MultiWord>(
t,
(_anyType) => singleWord("any"),
(_anyType) => singleWord(this.anyType()),
(_nullType) => singleWord("null"),
(_boolType) => singleWord("boolean"),
(_integerType) => singleWord("number"),
Expand Down Expand Up @@ -113,6 +113,12 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer {
);
}

/** The type emitted for `any`-typed values in type declarations
* and converter signatures: the language's type-safe top type
* (`unknown` for TypeScript, `mixed` for Flow) with the
* `prefer-unknown` option, plain `any` without it. */
protected abstract anyType(): string;

protected abstract emitEnum(e: EnumType, enumName: Name): void;

protected abstract emitClassBlock(c: ClassType, className: Name): void;
Expand Down Expand Up @@ -198,7 +204,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,
Expand All @@ -212,7 +218,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,
Expand All @@ -227,6 +233,10 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer {
return undefined;
}

// The runtime typecheck helpers are deliberately dynamic, so they
// stay on `any` even with `prefer-unknown`. Only the public API
// surface (type declarations and converter signatures) uses
// `anyType()`.
protected get castFunctionLines(): [string, string] {
return [
"function cast<T>(val: any, typ: any): T",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,31 @@ import { TypeScriptFlowBaseRenderer } from "./TypeScriptFlowBaseRenderer.js";
import { tsFlowTypeAnnotations } from "./utils.js";

export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer {
protected anyType(): string {
return this._tsFlowOptions.preferUnknown ? "unknown" : "any";
}

protected forbiddenNamesForGlobalNamespace(): string[] {
return ["Array", "Date"];
}

protected uncheckedParsedJson(t: Type, parsedJson: Sourcelike): Sourcelike {
// With `raw-type any` and `prefer-unknown` the deserializer's
// parameter is `unknown`, which can't be returned as the target
// type without a cast.
if (
this._tsFlowOptions.rawType !== "json" &&
this._tsFlowOptions.preferUnknown
) {
return [parsedJson, " as ", this.sourceFor(t).source];
}

return parsedJson;
}

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,
Expand All @@ -29,7 +47,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export const tsFlowOptions = {
false,
),
readonly: new BooleanOption("readonly", "Use readonly type members", false),
preferUnknown: new BooleanOption(
"prefer-unknown",
"Use unknown (TypeScript) or mixed (Flow) instead of any",
true,
),
};

export const typeScriptLanguageConfig = {
Expand Down
2 changes: 2 additions & 0 deletions test/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ export const TypeScriptLanguage: Language = {
{ "acronym-style": "pascal" },
{ converters: "all-objects" },
{ readonly: "true" },
{ "prefer-unknown": "false" },
],
sourceFiles: ["src/language/TypeScript/index.ts"],
};
Expand Down Expand Up @@ -943,6 +944,7 @@ export const FlowLanguage: Language = {
{ "runtime-typecheck": "false" },
{ "runtime-typecheck-ignore-unknown-properties": "true" },
{ "nice-property-names": "true" },
{ "prefer-unknown": "false" },
],
sourceFiles: ["src/language/Flow/index.ts"],
};
Expand Down
Loading