Parenthesize TS unions correctly. Fixes #523
This commit is contained in:
Родитель
f4f903e982
Коммит
260d484bef
|
@ -764,7 +764,7 @@ export abstract class ConvenienceRenderer extends Renderer {
|
|||
protected registerHandlebarsHelpers(context: StringMap): void {
|
||||
super.registerHandlebarsHelpers(context);
|
||||
|
||||
handlebars.registerHelper("with_type", function(t: any, options: any): any {
|
||||
handlebars.registerHelper("with_type", function (t: any, options: any): any {
|
||||
return options.fn(context.allTypes[t.index]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
allUpperWordStyle
|
||||
} from "../Strings";
|
||||
import { defined, intercalate } from "../Support";
|
||||
import { Sourcelike, annotated } from "../Source";
|
||||
import { Sourcelike, annotated, MultiWord, singleWord, multiWord, parenIfNeeded } from "../Source";
|
||||
import { anyTypeIssueAnnotation, nullTypeIssueAnnotation } from "../Annotation";
|
||||
|
||||
export default class ElmTargetLanguage extends TargetLanguage {
|
||||
|
@ -116,26 +116,6 @@ function elmNameStyle(original: string, upper: boolean): string {
|
|||
const upperNamingFunction = funPrefixNamer("upper", n => elmNameStyle(n, true));
|
||||
const lowerNamingFunction = funPrefixNamer("lower", n => elmNameStyle(n, false));
|
||||
|
||||
type MultiWord = {
|
||||
source: Sourcelike;
|
||||
needsParens: boolean;
|
||||
};
|
||||
|
||||
function singleWord(source: Sourcelike): MultiWord {
|
||||
return { source, needsParens: false };
|
||||
}
|
||||
|
||||
function multiWord(a: Sourcelike, b: Sourcelike): MultiWord {
|
||||
return { source: [a, " ", b], needsParens: true };
|
||||
}
|
||||
|
||||
function parenIfNeeded({ source, needsParens }: MultiWord): Sourcelike {
|
||||
if (needsParens) {
|
||||
return ["(", source, ")"];
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
type RequiredOrOptional = {
|
||||
reqOrOpt: string;
|
||||
fallback: string;
|
||||
|
@ -258,16 +238,16 @@ class ElmRenderer extends ConvenienceRenderer {
|
|||
_integerType => singleWord("Int"),
|
||||
_doubleType => singleWord("Float"),
|
||||
_stringType => singleWord("String"),
|
||||
arrayType => multiWord(this.arrayType, parenIfNeeded(this.elmType(arrayType.items))),
|
||||
arrayType => multiWord(" ", this.arrayType, parenIfNeeded(this.elmType(arrayType.items))),
|
||||
classType => singleWord(this.nameForNamedType(classType)),
|
||||
mapType => multiWord("Dict String", parenIfNeeded(this.elmType(mapType.values))),
|
||||
mapType => multiWord(" ", "Dict String", parenIfNeeded(this.elmType(mapType.values))),
|
||||
enumType => singleWord(this.nameForNamedType(enumType)),
|
||||
unionType => {
|
||||
const nullable = nullableFromUnion(unionType);
|
||||
if (nullable !== null) {
|
||||
const nullableType = this.elmType(nullable);
|
||||
if (noOptional) return nullableType;
|
||||
return multiWord("Maybe", parenIfNeeded(nullableType));
|
||||
return multiWord(" ", "Maybe", parenIfNeeded(nullableType));
|
||||
}
|
||||
return singleWord(this.nameForNamedType(unionType));
|
||||
}
|
||||
|
@ -276,7 +256,7 @@ class ElmRenderer extends ConvenienceRenderer {
|
|||
|
||||
private elmProperty(p: ClassProperty): Sourcelike {
|
||||
if (p.isOptional) {
|
||||
return multiWord("Maybe", parenIfNeeded(this.elmType(p.type, true))).source;
|
||||
return multiWord(" ", "Maybe", parenIfNeeded(this.elmType(p.type, true))).source;
|
||||
} else {
|
||||
return this.elmType(p.type).source;
|
||||
}
|
||||
|
@ -291,25 +271,25 @@ class ElmRenderer extends ConvenienceRenderer {
|
|||
return matchType<MultiWord>(
|
||||
t,
|
||||
_anyType => singleWord("Jdec.value"),
|
||||
_nullType => multiWord("Jdec.null", "()"),
|
||||
_nullType => multiWord(" ", "Jdec.null", "()"),
|
||||
_boolType => singleWord("Jdec.bool"),
|
||||
_integerType => singleWord("Jdec.int"),
|
||||
_doubleType => singleWord("Jdec.float"),
|
||||
_stringType => singleWord("Jdec.string"),
|
||||
arrayType =>
|
||||
multiWord(
|
||||
multiWord(" ",
|
||||
["Jdec.", decapitalize(this.arrayType)],
|
||||
parenIfNeeded(this.decoderNameForType(arrayType.items))
|
||||
),
|
||||
classType => singleWord(this.decoderNameForNamedType(classType)),
|
||||
mapType => multiWord("Jdec.dict", parenIfNeeded(this.decoderNameForType(mapType.values))),
|
||||
mapType => multiWord(" ", "Jdec.dict", parenIfNeeded(this.decoderNameForType(mapType.values))),
|
||||
enumType => singleWord(this.decoderNameForNamedType(enumType)),
|
||||
unionType => {
|
||||
const nullable = nullableFromUnion(unionType);
|
||||
if (nullable !== null) {
|
||||
const nullableDecoder = this.decoderNameForType(nullable);
|
||||
if (noOptional) return nullableDecoder;
|
||||
return multiWord("Jdec.nullable", parenIfNeeded(nullableDecoder));
|
||||
return multiWord(" ", "Jdec.nullable", parenIfNeeded(nullableDecoder));
|
||||
}
|
||||
return singleWord(this.decoderNameForNamedType(unionType));
|
||||
}
|
||||
|
@ -318,7 +298,7 @@ class ElmRenderer extends ConvenienceRenderer {
|
|||
|
||||
private decoderNameForProperty(p: ClassProperty): MultiWord {
|
||||
if (p.isOptional) {
|
||||
return multiWord("Jdec.nullable", parenIfNeeded(this.decoderNameForType(p.type, true)));
|
||||
return multiWord(" ", "Jdec.nullable", parenIfNeeded(this.decoderNameForType(p.type, true)));
|
||||
} else {
|
||||
return this.decoderNameForType(p.type);
|
||||
}
|
||||
|
@ -333,22 +313,22 @@ class ElmRenderer extends ConvenienceRenderer {
|
|||
return matchType<MultiWord>(
|
||||
t,
|
||||
_anyType => singleWord("identity"),
|
||||
_nullType => multiWord("always", "Jenc.null"),
|
||||
_nullType => multiWord(" ", "always", "Jenc.null"),
|
||||
_boolType => singleWord("Jenc.bool"),
|
||||
_integerType => singleWord("Jenc.int"),
|
||||
_doubleType => singleWord("Jenc.float"),
|
||||
_stringType => singleWord("Jenc.string"),
|
||||
arrayType =>
|
||||
multiWord(["make", this.arrayType, "Encoder"], parenIfNeeded(this.encoderNameForType(arrayType.items))),
|
||||
multiWord(" ", ["make", this.arrayType, "Encoder"], parenIfNeeded(this.encoderNameForType(arrayType.items))),
|
||||
classType => singleWord(this.encoderNameForNamedType(classType)),
|
||||
mapType => multiWord("makeDictEncoder", parenIfNeeded(this.encoderNameForType(mapType.values))),
|
||||
mapType => multiWord(" ", "makeDictEncoder", parenIfNeeded(this.encoderNameForType(mapType.values))),
|
||||
enumType => singleWord(this.encoderNameForNamedType(enumType)),
|
||||
unionType => {
|
||||
const nullable = nullableFromUnion(unionType);
|
||||
if (nullable !== null) {
|
||||
const nullableEncoder = this.encoderNameForType(nullable);
|
||||
if (noOptional) return nullableEncoder;
|
||||
return multiWord("makeNullableEncoder", parenIfNeeded(nullableEncoder));
|
||||
return multiWord(" ", "makeNullableEncoder", parenIfNeeded(nullableEncoder));
|
||||
}
|
||||
return singleWord(this.encoderNameForNamedType(unionType));
|
||||
}
|
||||
|
@ -357,7 +337,7 @@ class ElmRenderer extends ConvenienceRenderer {
|
|||
|
||||
private encoderNameForProperty(p: ClassProperty): MultiWord {
|
||||
if (p.isOptional) {
|
||||
return multiWord("makeNullableEncoder", parenIfNeeded(this.encoderNameForType(p.type, true)));
|
||||
return multiWord(" ", "makeNullableEncoder", parenIfNeeded(this.encoderNameForType(p.type, true)));
|
||||
} else {
|
||||
return this.encoderNameForType(p.type);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
} from "../Strings";
|
||||
import { intercalate } from "../Support";
|
||||
|
||||
import { Sourcelike, modifySource } from "../Source";
|
||||
import { Sourcelike, modifySource, MultiWord, singleWord, parenIfNeeded, multiWord } from "../Source";
|
||||
import { Namer, Name } from "../Naming";
|
||||
import { ConvenienceRenderer } from "../ConvenienceRenderer";
|
||||
import { TargetLanguage } from "../TargetLanguage";
|
||||
|
@ -147,39 +147,39 @@ class TypeScriptRenderer extends ConvenienceRenderer {
|
|||
});
|
||||
};
|
||||
|
||||
sourceFor = (t: Type): Sourcelike => {
|
||||
return matchType<Sourcelike>(
|
||||
private sourceFor(t: Type): MultiWord {
|
||||
return matchType<MultiWord>(
|
||||
t,
|
||||
_anyType => "any",
|
||||
_nullType => "null",
|
||||
_boolType => "boolean",
|
||||
_integerType => "number",
|
||||
_doubleType => "number",
|
||||
_stringType => "string",
|
||||
_anyType => singleWord("any"),
|
||||
_nullType => singleWord("null"),
|
||||
_boolType => singleWord("boolean"),
|
||||
_integerType => singleWord("number"),
|
||||
_doubleType => singleWord("number"),
|
||||
_stringType => singleWord("string"),
|
||||
arrayType => {
|
||||
const itemType = this.sourceFor(arrayType.items);
|
||||
if (
|
||||
(arrayType.items instanceof UnionType && this._inlineUnions) ||
|
||||
arrayType.items instanceof ArrayType
|
||||
) {
|
||||
return ["Array<", itemType, ">"];
|
||||
return singleWord(["Array<", itemType.source, ">"]);
|
||||
} else {
|
||||
return [itemType, "[]"];
|
||||
return singleWord([parenIfNeeded(itemType), "[]"]);
|
||||
}
|
||||
},
|
||||
classType => this.nameForNamedType(classType),
|
||||
mapType => ["{ [key: string]: ", this.sourceFor(mapType.values), " }"],
|
||||
enumType => this.nameForNamedType(enumType),
|
||||
classType => singleWord(this.nameForNamedType(classType)),
|
||||
mapType => singleWord(["{ [key: string]: ", this.sourceFor(mapType.values).source, " }"]),
|
||||
enumType => singleWord(this.nameForNamedType(enumType)),
|
||||
unionType => {
|
||||
if (this._inlineUnions || nullableFromUnion(unionType) !== null) {
|
||||
const children = unionType.children.map(this.sourceFor);
|
||||
return intercalate(" | ", children).toArray();
|
||||
const children = unionType.children.map(c => parenIfNeeded(this.sourceFor(c)));
|
||||
return multiWord(" | ", ...children.toArray());
|
||||
} else {
|
||||
return this.nameForNamedType(unionType);
|
||||
return singleWord(this.nameForNamedType(unionType));
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
typeMapTypeFor = (t: Type): Sourcelike => {
|
||||
return matchType<Sourcelike>(
|
||||
|
@ -246,7 +246,7 @@ class TypeScriptRenderer extends ConvenienceRenderer {
|
|||
}
|
||||
table.push([
|
||||
[name, nullable !== null ? "?" : "", ": "],
|
||||
[this.sourceFor(nullable !== null ? nullable : t), ";"]
|
||||
[this.sourceFor(nullable !== null ? nullable : t).source, ";"]
|
||||
]);
|
||||
});
|
||||
this.emitTable(table);
|
||||
|
@ -260,7 +260,7 @@ class TypeScriptRenderer extends ConvenienceRenderer {
|
|||
}
|
||||
this.emitBlock("export module Convert", "", () => {
|
||||
this.forEachTopLevel("interposing", (t, name) => {
|
||||
this.emitBlock(["export function to", name, "(json: string): ", this.sourceFor(t)], "", () => {
|
||||
this.emitBlock(["export function to", name, "(json: string): ", this.sourceFor(t).source], "", () => {
|
||||
if (this._omitRuntimeTypecheck) {
|
||||
this.emitLine("return JSON.parse(json);");
|
||||
} else {
|
||||
|
@ -271,7 +271,7 @@ class TypeScriptRenderer extends ConvenienceRenderer {
|
|||
|
||||
const camelCaseName = modifySource(camelCase, name);
|
||||
this.emitBlock(
|
||||
["export function ", camelCaseName, "ToJson(value: ", this.sourceFor(t), "): string"],
|
||||
["export function ", camelCaseName, "ToJson(value: ", this.sourceFor(t).source, "): string"],
|
||||
"",
|
||||
() => {
|
||||
this.emitLine("return JSON.stringify(value, null, 2);");
|
||||
|
@ -366,8 +366,8 @@ function O(className: string) {
|
|||
if (this._inlineUnions) {
|
||||
return;
|
||||
}
|
||||
const children = u.children.map(this.sourceFor);
|
||||
this.emitLine("export type ", unionName, " = ", intercalate(" | ", children).toArray(), ";");
|
||||
const children = multiWord(" | ", ...u.children.map(c => parenIfNeeded(this.sourceFor(c))).toArray());
|
||||
this.emitLine("export type ", unionName, " = ", children.source, ";");
|
||||
};
|
||||
|
||||
protected emitSourceStructure() {
|
||||
|
|
|
@ -66,7 +66,7 @@ export function newline(): NewlineSource {
|
|||
}
|
||||
|
||||
export type Sourcelike = Source | string | Name | SourcelikeArray;
|
||||
export interface SourcelikeArray extends Array<Sourcelike> {}
|
||||
export interface SourcelikeArray extends Array<Sourcelike> { }
|
||||
|
||||
export function sourcelikeToSource(sl: Sourcelike): Source {
|
||||
if (sl instanceof Array) {
|
||||
|
@ -263,3 +263,32 @@ export function serializeRenderResult(
|
|||
finishLine();
|
||||
return { lines, annotations: List(annotations) };
|
||||
}
|
||||
|
||||
export type MultiWord = {
|
||||
source: Sourcelike;
|
||||
needsParens: boolean;
|
||||
};
|
||||
|
||||
export function singleWord(source: Sourcelike): MultiWord {
|
||||
return { source, needsParens: false };
|
||||
}
|
||||
|
||||
export function multiWord(separator: Sourcelike, ...words: Sourcelike[]): MultiWord {
|
||||
assert(words.length > 0, "Zero words is not multiple");
|
||||
if (words.length === 1) {
|
||||
return singleWord(words[0]);
|
||||
}
|
||||
const items: Sourcelike[] = [];
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
if (i > 0) items.push(separator);
|
||||
items.push(words[i]);
|
||||
}
|
||||
return { source: items, needsParens: true };
|
||||
}
|
||||
|
||||
export function parenIfNeeded({ source, needsParens }: MultiWord): Sourcelike {
|
||||
if (needsParens) {
|
||||
return ["(", source, ")"];
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче