Skip to content

Commit da1238c

Browse files
release: Quicktype v23.1.0 (#2610)
* feat: Add const typing for Language Names (#2554) * feat: feat: Strongly type Language Options (#2611)
1 parent 1265276 commit da1238c

File tree

52 files changed

+1161
-2411
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1161
-2411
lines changed

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "tabWidth": 4, "printWidth": 120, "trailingComma": "none", "arrowParens": "avoid" }
1+
{ "tabWidth": 4, "printWidth": 120, "trailingComma": "none", "arrowParens": "avoid", "quoteProps": "consistent" }

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ main();
186186

187187
The argument to `quicktype` is a complex object with many optional properties. [Explore its definition](https://github.com/quicktype/quicktype/blob/master/packages/quicktype-core/src/Run.ts#L637) to understand what options are allowed.
188188

189+
### Adding Custom logic or Rendering:
190+
191+
Quicktype supports creating your own custom languages and rendering output, you can extend existing classes or create your own to be using by the `quicktype function`.<br/>
192+
Check out [this guide](./doc/CustomRenderer.md) for more info.
193+
189194
## Contributing
190195

191196
`quicktype` is [Open Source](LICENSE) and we love contributors! In fact, we have a [list of issues](https://github.com/quicktype/quicktype/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Ahelp-wanted) that are low-priority for us, but for which we'd happily accept contributions. Support for new target languages is also strongly desired. If you'd like to contribute, need help with anything at all, or would just like to talk things over, come [join us on Slack](http://slack.quicktype.io/).

doc/CustomRenderer.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Extending quicktype functionality with a Custom Renderer
2+
3+
## quicktype Interface
4+
5+
To customise your rendering output, you can extend existing quicktype classes and override existing methods to achieve the behaviour you want.
6+
7+
This process requires 3 main steps:
8+
9+
1. [Extending a `Renderer` Class](#creating-a-custom-renderer)
10+
2. [Wrapping your `Renderer` in a `TargetLanguage` Class](#creating-a-targetlanguage)
11+
3. [Using your new classes in the `quicktype` function](#using-your-custom-language)
12+
4. [Advanced Usage: Creating an entirely new Language](#creating-a-new-language)
13+
14+
## Creating a custom `Renderer`
15+
16+
Adding custom render logic for an existing language often involves extending a Renderer class and simply overriding or amending one of the `emit` methods:
17+
18+
```ts
19+
// MyCustomRenderer.ts
20+
import { CSharpRenderer } from "quicktype-core";
21+
22+
export class MyCustomRenderer extends CSharpRenderer {
23+
// Add your custom logic here, feel free to reference the source code for how existing methods work
24+
//
25+
// ex.
26+
protected superclassForType(t: Type): Sourcelike | undefined {
27+
// if the type is a class, it should extend `GameObject` when rendered in C#
28+
if (t instanceof ClassType) {
29+
return "GameObject";
30+
}
31+
return undefined;
32+
}
33+
// See: http://blog.quicktype.io/customizing-quicktype/ for more context
34+
}
35+
```
36+
37+
## Creating a `TargetLanguage`
38+
39+
If you just want to change the rendering logic for an existing language, you can just extend an exported Language class (`CSharpTargetLanguage` in this example) and override the `makeRenderer` method:
40+
41+
```ts
42+
// MyCustomLanguage.ts
43+
import { CSharpTargetLanguage } from "quicktype-core";
44+
45+
import { MyCustomRenderer } from "./MyCustomRenderer";
46+
47+
export class MyCustomLanguage extends CSharpTargetLanguage {
48+
// `makeRenderer` instantiates the Renderer class for the TargetLanguage
49+
protected makeRenderer(
50+
renderContext: RenderContext,
51+
untypedOptionValues: Record<string, unknown>
52+
): MyCustomRenderer {
53+
// use your new custom renderer class here
54+
return new MyCustomRenderer(this, renderContext, getOptionValues(cSharpOptions, untypedOptionValues));
55+
}
56+
}
57+
```
58+
59+
## Using your custom Language
60+
61+
```ts
62+
import { quicktype } from "quicktype-core";
63+
64+
import { MyCustomLanguage } from './MyCustomLanguage';
65+
66+
const lang = new MyCustomLanguage();
67+
68+
const lines = await quicktype({
69+
lang: lang, // use your new TargetLanguage in the `lang` field here
70+
...
71+
});
72+
73+
console.log(lines);
74+
```
75+
76+
## Creating a new Language
77+
78+
If none of the existing `quicktype` Language classes suit your needs, you can creating your own `TargetLanguge` and `Renderer` classes from scratch. If this satisfies your use cases for a language we don't currently support, please consider opening a PR with your new language and we'd love to take a look.
79+
80+
If you run into any issues, you can open a GitHub issue and we'll help you take a look.
81+
82+
### Creating a `TargetLanguage` from scratch
83+
84+
Instead of just extending an existing language, a new Language requires two additional steps:
85+
86+
- Defining the language config
87+
- Adding any language-specific options
88+
89+
```ts
90+
import { TargetLanguage, BooleanOption } from "quicktype-core";
91+
92+
// language config
93+
const brandNewLanguageConfig = {
94+
displayName: "Scratch", // these can be the same
95+
names: ["scratch"], // these can be the same
96+
extension: "sb" // the file extension that this language commonly has
97+
} as const;
98+
99+
// language options
100+
const brandNewLanguageOptions = {
101+
allowFoo: new BooleanOption(
102+
"allow-foo", // option name
103+
"Allows Foo", // description
104+
true // default value
105+
)
106+
// The default available Option classes are: StringOption, BooleanOption, EnumOption
107+
// Please visit the source code for more examples and usage
108+
};
109+
110+
class BrandNewLanguage extends TargetLanguage<typeof brandNewLanguageConfig> {
111+
public constructor() {
112+
super(brandNewLanguageConfig);
113+
}
114+
115+
public getOptions(): typeof brandNewLanguageOptions {
116+
return brandNewLanguageOptions;
117+
}
118+
119+
protected makeRenderer(
120+
renderContext: RenderContext,
121+
untypedOptionValues: Record<string, unknown>
122+
): BrandNewRenderer {
123+
return new BrandNewRenderer(this, renderContext, getOptionValues(brandNewLanguageOptions, untypedOptionValues));
124+
}
125+
}
126+
```
127+
128+
### Creating a `Renderer` from scratch
129+
130+
Creating a brand new `Renderer` class is very similar to extending an existing class:
131+
132+
```ts
133+
export class BrandNewRenderer extends ConvenienceRenderer {
134+
public constructor(targetLanguage: TargetLanguage, renderContext: RenderContext) {
135+
super(targetLanguage, renderContext);
136+
}
137+
138+
// Additional render methods go here
139+
// Please reference existing Renderer classes and open a GitHub issue if you need help
140+
}
141+
```
142+
143+
## Links
144+
145+
Blog post with an older example: http://blog.quicktype.io/customizing-quicktype/

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "quicktype",
3-
"version": "23.0.0",
3+
"version": "23.1.0",
44
"license": "Apache-2.0",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

packages/quicktype-core/src/Messages.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export type ErrorProperties =
8080
// TypeScript input
8181
| { kind: "TypeScriptCompilerError"; properties: { message: string } };
8282

83-
export type ErrorKinds = ErrorProperties extends { kind: infer K } ? K : never;
83+
export type ErrorKinds = ErrorProperties["kind"];
8484

8585
type ErrorMessages = { readonly [K in ErrorKinds]: string };
8686

@@ -165,7 +165,7 @@ const errorMessages: ErrorMessages = {
165165
TypeScriptCompilerError: "TypeScript error: ${message}"
166166
};
167167

168-
export type ErrorPropertiesForName<K> =
168+
export type ErrorPropertiesForKind<K extends ErrorKinds = ErrorKinds> =
169169
Extract<ErrorProperties, { kind: K }> extends { properties: infer P } ? P : never;
170170

171171
export class QuickTypeError extends Error {
@@ -179,31 +179,30 @@ export class QuickTypeError extends Error {
179179
}
180180
}
181181

182-
export function messageError<N extends ErrorKinds>(kind: N, properties: ErrorPropertiesForName<N>): never {
182+
export function messageError<Kind extends ErrorKinds>(kind: Kind, properties: ErrorPropertiesForKind<Kind>): never {
183183
const message = errorMessages[kind];
184184
let userMessage: string = message;
185-
const propertiesMap = properties as StringMap;
186-
187-
for (const name of Object.getOwnPropertyNames(propertiesMap)) {
188-
let value = propertiesMap[name];
189-
if (typeof value === "object" && typeof value.toString === "function") {
190-
value = value.toString();
191-
} else if (typeof value.message === "string") {
192-
value = value.message;
185+
186+
for (const [name, value] of Object.entries(properties as StringMap)) {
187+
let valueString = "";
188+
if (typeof value === "object" && typeof value?.toString === "function") {
189+
valueString = value.toString();
190+
} else if (typeof value?.message === "string") {
191+
valueString = value.message;
193192
} else if (typeof value !== "string") {
194-
value = JSON.stringify(value);
193+
valueString = JSON.stringify(value);
195194
}
196195

197-
userMessage = userMessage.replace("${" + name + "}", value);
196+
userMessage = userMessage.replace("${" + name + "}", valueString);
198197
}
199198

200-
throw new QuickTypeError(message, kind, userMessage, propertiesMap);
199+
throw new QuickTypeError(message, kind, userMessage, properties as StringMap);
201200
}
202201

203-
export function messageAssert<N extends ErrorKinds>(
202+
export function messageAssert<Kind extends ErrorKinds>(
204203
assertion: boolean,
205-
kind: N,
206-
properties: ErrorPropertiesForName<N>
204+
kind: Kind,
205+
properties: ErrorPropertiesForKind<Kind>
207206
): void {
208207
if (assertion) return;
209208
return messageError(kind, properties);

0 commit comments

Comments
 (0)