release: Quicktype v23.1.0 (#2610)
* feat: Add const typing for Language Names (#2554) * feat: feat: Strongly type Language Options (#2611)
This commit is contained in:
@ -1 +1 @@
|
||||
{ "tabWidth": 4, "printWidth": 120, "trailingComma": "none", "arrowParens": "avoid" }
|
||||
{ "tabWidth": 4, "printWidth": 120, "trailingComma": "none", "arrowParens": "avoid", "quoteProps": "consistent" }
|
||||
|
@ -186,6 +186,11 @@ main();
|
||||
|
||||
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.
|
||||
|
||||
### Adding Custom logic or Rendering:
|
||||
|
||||
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/>
|
||||
Check out [this guide](./doc/CustomRenderer.md) for more info.
|
||||
|
||||
## Contributing
|
||||
|
||||
`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/).
|
||||
|
145
doc/CustomRenderer.md
Normal file
145
doc/CustomRenderer.md
Normal file
@ -0,0 +1,145 @@
|
||||
# Extending quicktype functionality with a Custom Renderer
|
||||
|
||||
## quicktype Interface
|
||||
|
||||
To customise your rendering output, you can extend existing quicktype classes and override existing methods to achieve the behaviour you want.
|
||||
|
||||
This process requires 3 main steps:
|
||||
|
||||
1. [Extending a `Renderer` Class](#creating-a-custom-renderer)
|
||||
2. [Wrapping your `Renderer` in a `TargetLanguage` Class](#creating-a-targetlanguage)
|
||||
3. [Using your new classes in the `quicktype` function](#using-your-custom-language)
|
||||
4. [Advanced Usage: Creating an entirely new Language](#creating-a-new-language)
|
||||
|
||||
## Creating a custom `Renderer`
|
||||
|
||||
Adding custom render logic for an existing language often involves extending a Renderer class and simply overriding or amending one of the `emit` methods:
|
||||
|
||||
```ts
|
||||
// MyCustomRenderer.ts
|
||||
import { CSharpRenderer } from "quicktype-core";
|
||||
|
||||
export class MyCustomRenderer extends CSharpRenderer {
|
||||
// Add your custom logic here, feel free to reference the source code for how existing methods work
|
||||
//
|
||||
// ex.
|
||||
protected superclassForType(t: Type): Sourcelike | undefined {
|
||||
// if the type is a class, it should extend `GameObject` when rendered in C#
|
||||
if (t instanceof ClassType) {
|
||||
return "GameObject";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
// See: http://blog.quicktype.io/customizing-quicktype/ for more context
|
||||
}
|
||||
```
|
||||
|
||||
## Creating a `TargetLanguage`
|
||||
|
||||
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:
|
||||
|
||||
```ts
|
||||
// MyCustomLanguage.ts
|
||||
import { CSharpTargetLanguage } from "quicktype-core";
|
||||
|
||||
import { MyCustomRenderer } from "./MyCustomRenderer";
|
||||
|
||||
export class MyCustomLanguage extends CSharpTargetLanguage {
|
||||
// `makeRenderer` instantiates the Renderer class for the TargetLanguage
|
||||
protected makeRenderer(
|
||||
renderContext: RenderContext,
|
||||
untypedOptionValues: Record<string, unknown>
|
||||
): MyCustomRenderer {
|
||||
// use your new custom renderer class here
|
||||
return new MyCustomRenderer(this, renderContext, getOptionValues(cSharpOptions, untypedOptionValues));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using your custom Language
|
||||
|
||||
```ts
|
||||
import { quicktype } from "quicktype-core";
|
||||
|
||||
import { MyCustomLanguage } from './MyCustomLanguage';
|
||||
|
||||
const lang = new MyCustomLanguage();
|
||||
|
||||
const lines = await quicktype({
|
||||
lang: lang, // use your new TargetLanguage in the `lang` field here
|
||||
...
|
||||
});
|
||||
|
||||
console.log(lines);
|
||||
```
|
||||
|
||||
## Creating a new Language
|
||||
|
||||
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.
|
||||
|
||||
If you run into any issues, you can open a GitHub issue and we'll help you take a look.
|
||||
|
||||
### Creating a `TargetLanguage` from scratch
|
||||
|
||||
Instead of just extending an existing language, a new Language requires two additional steps:
|
||||
|
||||
- Defining the language config
|
||||
- Adding any language-specific options
|
||||
|
||||
```ts
|
||||
import { TargetLanguage, BooleanOption } from "quicktype-core";
|
||||
|
||||
// language config
|
||||
const brandNewLanguageConfig = {
|
||||
displayName: "Scratch", // these can be the same
|
||||
names: ["scratch"], // these can be the same
|
||||
extension: "sb" // the file extension that this language commonly has
|
||||
} as const;
|
||||
|
||||
// language options
|
||||
const brandNewLanguageOptions = {
|
||||
allowFoo: new BooleanOption(
|
||||
"allow-foo", // option name
|
||||
"Allows Foo", // description
|
||||
true // default value
|
||||
)
|
||||
// The default available Option classes are: StringOption, BooleanOption, EnumOption
|
||||
// Please visit the source code for more examples and usage
|
||||
};
|
||||
|
||||
class BrandNewLanguage extends TargetLanguage<typeof brandNewLanguageConfig> {
|
||||
public constructor() {
|
||||
super(brandNewLanguageConfig);
|
||||
}
|
||||
|
||||
public getOptions(): typeof brandNewLanguageOptions {
|
||||
return brandNewLanguageOptions;
|
||||
}
|
||||
|
||||
protected makeRenderer(
|
||||
renderContext: RenderContext,
|
||||
untypedOptionValues: Record<string, unknown>
|
||||
): BrandNewRenderer {
|
||||
return new BrandNewRenderer(this, renderContext, getOptionValues(brandNewLanguageOptions, untypedOptionValues));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Creating a `Renderer` from scratch
|
||||
|
||||
Creating a brand new `Renderer` class is very similar to extending an existing class:
|
||||
|
||||
```ts
|
||||
export class BrandNewRenderer extends ConvenienceRenderer {
|
||||
public constructor(targetLanguage: TargetLanguage, renderContext: RenderContext) {
|
||||
super(targetLanguage, renderContext);
|
||||
}
|
||||
|
||||
// Additional render methods go here
|
||||
// Please reference existing Renderer classes and open a GitHub issue if you need help
|
||||
}
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
Blog post with an older example: http://blog.quicktype.io/customizing-quicktype/
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "quicktype",
|
||||
"version": "23.0.0",
|
||||
"version": "23.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -80,7 +80,7 @@ export type ErrorProperties =
|
||||
// TypeScript input
|
||||
| { kind: "TypeScriptCompilerError"; properties: { message: string } };
|
||||
|
||||
export type ErrorKinds = ErrorProperties extends { kind: infer K } ? K : never;
|
||||
export type ErrorKinds = ErrorProperties["kind"];
|
||||
|
||||
type ErrorMessages = { readonly [K in ErrorKinds]: string };
|
||||
|
||||
@ -165,7 +165,7 @@ const errorMessages: ErrorMessages = {
|
||||
TypeScriptCompilerError: "TypeScript error: ${message}"
|
||||
};
|
||||
|
||||
export type ErrorPropertiesForName<K> =
|
||||
export type ErrorPropertiesForKind<K extends ErrorKinds = ErrorKinds> =
|
||||
Extract<ErrorProperties, { kind: K }> extends { properties: infer P } ? P : never;
|
||||
|
||||
export class QuickTypeError extends Error {
|
||||
@ -179,31 +179,30 @@ export class QuickTypeError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export function messageError<N extends ErrorKinds>(kind: N, properties: ErrorPropertiesForName<N>): never {
|
||||
export function messageError<Kind extends ErrorKinds>(kind: Kind, properties: ErrorPropertiesForKind<Kind>): never {
|
||||
const message = errorMessages[kind];
|
||||
let userMessage: string = message;
|
||||
const propertiesMap = properties as StringMap;
|
||||
|
||||
for (const name of Object.getOwnPropertyNames(propertiesMap)) {
|
||||
let value = propertiesMap[name];
|
||||
if (typeof value === "object" && typeof value.toString === "function") {
|
||||
value = value.toString();
|
||||
} else if (typeof value.message === "string") {
|
||||
value = value.message;
|
||||
for (const [name, value] of Object.entries(properties as StringMap)) {
|
||||
let valueString = "";
|
||||
if (typeof value === "object" && typeof value?.toString === "function") {
|
||||
valueString = value.toString();
|
||||
} else if (typeof value?.message === "string") {
|
||||
valueString = value.message;
|
||||
} else if (typeof value !== "string") {
|
||||
value = JSON.stringify(value);
|
||||
valueString = JSON.stringify(value);
|
||||
}
|
||||
|
||||
userMessage = userMessage.replace("${" + name + "}", value);
|
||||
userMessage = userMessage.replace("${" + name + "}", valueString);
|
||||
}
|
||||
|
||||
throw new QuickTypeError(message, kind, userMessage, propertiesMap);
|
||||
throw new QuickTypeError(message, kind, userMessage, properties as StringMap);
|
||||
}
|
||||
|
||||
export function messageAssert<N extends ErrorKinds>(
|
||||
export function messageAssert<Kind extends ErrorKinds>(
|
||||
assertion: boolean,
|
||||
kind: N,
|
||||
properties: ErrorPropertiesForName<N>
|
||||
kind: Kind,
|
||||
properties: ErrorPropertiesForKind<Kind>
|
||||
): void {
|
||||
if (assertion) return;
|
||||
return messageError(kind, properties);
|
||||
|
@ -1,193 +0,0 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
import { hasOwnProperty } from "collection-utils";
|
||||
|
||||
import { messageError } from "./Messages";
|
||||
import { assert } from "./support/Support";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "./types";
|
||||
|
||||
/**
|
||||
* Primary options show up in the web UI in the "Language" settings tab,
|
||||
* secondary options in "Other".
|
||||
*/
|
||||
export type OptionKind = "primary" | "secondary";
|
||||
|
||||
export interface OptionDefinition {
|
||||
alias?: string;
|
||||
defaultOption?: boolean;
|
||||
defaultValue?: FixMeOptionsAnyType;
|
||||
description: string;
|
||||
kind?: OptionKind;
|
||||
legalValues?: string[];
|
||||
multiple?: boolean;
|
||||
name: string;
|
||||
renderer?: boolean;
|
||||
type: StringConstructor | BooleanConstructor;
|
||||
typeLabel?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The superclass for target language options. You probably want to use one of its
|
||||
* subclasses, `BooleanOption`, `EnumOption`, or `StringOption`.
|
||||
*/
|
||||
export abstract class Option<T> {
|
||||
public readonly definition: OptionDefinition;
|
||||
|
||||
public constructor(definition: OptionDefinition) {
|
||||
definition.renderer = true;
|
||||
this.definition = definition;
|
||||
assert(definition.kind !== undefined, "Renderer option kind must be defined");
|
||||
}
|
||||
|
||||
public getValue(values: FixMeOptionsType): T {
|
||||
const value = values[this.definition.name];
|
||||
if (value === undefined) {
|
||||
return this.definition.defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public get cliDefinitions(): { actual: OptionDefinition[]; display: OptionDefinition[] } {
|
||||
return { actual: [this.definition], display: [this.definition] };
|
||||
}
|
||||
}
|
||||
|
||||
export type OptionValueType<O> = O extends Option<infer T> ? T : never;
|
||||
export type OptionValues<T> = { [P in keyof T]: OptionValueType<T[P]> };
|
||||
|
||||
export function getOptionValues<T extends { [name: string]: Option<FixMeOptionsAnyType> }>(
|
||||
options: T,
|
||||
untypedOptionValues: FixMeOptionsType
|
||||
): OptionValues<T> {
|
||||
const optionValues: FixMeOptionsType = {};
|
||||
for (const name of Object.getOwnPropertyNames(options)) {
|
||||
optionValues[name] = options[name].getValue(untypedOptionValues);
|
||||
}
|
||||
|
||||
return optionValues as OptionValues<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A target language option that allows setting a boolean flag.
|
||||
*/
|
||||
export class BooleanOption extends Option<boolean> {
|
||||
/**
|
||||
* @param name The shorthand name.
|
||||
* @param description Short-ish description of the option.
|
||||
* @param defaultValue The default value.
|
||||
* @param kind Whether it's a primary or secondary option.
|
||||
*/
|
||||
public constructor(name: string, description: string, defaultValue: boolean, kind: OptionKind = "primary") {
|
||||
super({
|
||||
name,
|
||||
kind,
|
||||
type: Boolean,
|
||||
description,
|
||||
defaultValue
|
||||
});
|
||||
}
|
||||
|
||||
public get cliDefinitions(): { actual: OptionDefinition[]; display: OptionDefinition[] } {
|
||||
const negated = Object.assign({}, this.definition, {
|
||||
name: `no-${this.definition.name}`,
|
||||
defaultValue: !this.definition.defaultValue
|
||||
});
|
||||
const display = Object.assign({}, this.definition, {
|
||||
name: `[no-]${this.definition.name}`,
|
||||
description: `${this.definition.description} (${this.definition.defaultValue ? "on" : "off"} by default)`
|
||||
});
|
||||
return {
|
||||
display: [display],
|
||||
actual: [this.definition, negated]
|
||||
};
|
||||
}
|
||||
|
||||
public getValue(values: FixMeOptionsType): boolean {
|
||||
let value = values[this.definition.name];
|
||||
if (value === undefined) {
|
||||
value = this.definition.defaultValue;
|
||||
}
|
||||
|
||||
let negated = values[`no-${this.definition.name}`];
|
||||
if (negated === undefined) {
|
||||
negated = !this.definition.defaultValue;
|
||||
}
|
||||
|
||||
if (value === "true") {
|
||||
value = true;
|
||||
} else if (value === "false") {
|
||||
value = false;
|
||||
}
|
||||
|
||||
if (this.definition.defaultValue) {
|
||||
return value && !negated;
|
||||
} else {
|
||||
return value || !negated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StringOption extends Option<string> {
|
||||
public constructor(
|
||||
name: string,
|
||||
description: string,
|
||||
typeLabel: string,
|
||||
defaultValue: string,
|
||||
kind: OptionKind = "primary"
|
||||
) {
|
||||
const definition = {
|
||||
name,
|
||||
kind,
|
||||
type: String,
|
||||
description,
|
||||
typeLabel,
|
||||
defaultValue
|
||||
};
|
||||
super(definition);
|
||||
}
|
||||
}
|
||||
|
||||
export class EnumOption<T> extends Option<T> {
|
||||
private readonly _values: { [name: string]: T };
|
||||
|
||||
public constructor(
|
||||
name: string,
|
||||
description: string,
|
||||
values: Array<[string, T]>,
|
||||
defaultValue: string | undefined = undefined,
|
||||
kind: OptionKind = "primary"
|
||||
) {
|
||||
if (defaultValue === undefined) {
|
||||
defaultValue = values[0][0];
|
||||
}
|
||||
|
||||
const definition = {
|
||||
name,
|
||||
kind,
|
||||
type: String,
|
||||
description,
|
||||
typeLabel: values.map(([n, _]) => n).join("|"),
|
||||
legalValues: values.map(([n, _]) => n),
|
||||
defaultValue
|
||||
};
|
||||
super(definition);
|
||||
|
||||
this._values = {};
|
||||
for (const [n, v] of values) {
|
||||
this._values[n] = v;
|
||||
}
|
||||
}
|
||||
|
||||
public getValue(values: FixMeOptionsType): T {
|
||||
let name: string = values[this.definition.name];
|
||||
if (name === undefined) {
|
||||
name = this.definition.defaultValue;
|
||||
}
|
||||
|
||||
if (!hasOwnProperty(this._values, name)) {
|
||||
return messageError("RendererUnknownOptionValue", { value: name, name: this.definition.name });
|
||||
}
|
||||
|
||||
return this._values[name];
|
||||
}
|
||||
}
|
178
packages/quicktype-core/src/RendererOptions/index.ts
Normal file
178
packages/quicktype-core/src/RendererOptions/index.ts
Normal file
@ -0,0 +1,178 @@
|
||||
import { messageError } from "../Messages";
|
||||
import { assert } from "../support/Support";
|
||||
import { type FixMeOptionsType, type NoInfer } from "../types";
|
||||
|
||||
import type { OptionDefinition, OptionKind, OptionValues } from "./types";
|
||||
|
||||
export * from "./types";
|
||||
|
||||
/**
|
||||
* The superclass for target language options. You probably want to use one of its
|
||||
* subclasses, `BooleanOption`, `EnumOption`, or `StringOption`.
|
||||
*/
|
||||
export abstract class Option<Name extends string, T> {
|
||||
public readonly definition: OptionDefinition<Name, T>;
|
||||
|
||||
public constructor(definition: OptionDefinition<Name, T>) {
|
||||
this.definition = definition;
|
||||
assert(definition.kind !== undefined, "Renderer option kind must be defined");
|
||||
}
|
||||
|
||||
public get name(): Name {
|
||||
return this.definition.name;
|
||||
}
|
||||
|
||||
public getValue(values: Record<string, unknown>): T {
|
||||
return (values[this.name] ?? this.definition.defaultValue) as T;
|
||||
}
|
||||
|
||||
public get cliDefinitions(): {
|
||||
actual: Array<OptionDefinition<Name, T>>;
|
||||
display: Array<OptionDefinition<Name, T>>;
|
||||
} {
|
||||
return { actual: [this.definition], display: [this.definition] };
|
||||
}
|
||||
}
|
||||
|
||||
export function getOptionValues<Name extends string, T, Options extends Record<string, Option<Name, T>>>(
|
||||
options: Options,
|
||||
untypedOptionValues: FixMeOptionsType
|
||||
): OptionValues<Options> {
|
||||
const optionValues: FixMeOptionsType = {};
|
||||
|
||||
for (const [key, option] of Object.entries(options)) {
|
||||
const value = option.getValue(untypedOptionValues);
|
||||
if (option instanceof EnumOption) {
|
||||
optionValues[key] = option.getEnumValue(value as string);
|
||||
} else {
|
||||
optionValues[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return optionValues as OptionValues<Options>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A target language option that allows setting a boolean flag.
|
||||
*/
|
||||
export class BooleanOption<Name extends string> extends Option<Name, boolean> {
|
||||
/**
|
||||
* @param name The shorthand name.
|
||||
* @param description Short-ish description of the option.
|
||||
* @param defaultValue The default value.
|
||||
* @param kind Whether it's a primary or secondary option.
|
||||
*/
|
||||
public constructor(name: Name, description: string, defaultValue: boolean, kind: OptionKind = "primary") {
|
||||
super({
|
||||
name,
|
||||
kind,
|
||||
type: Boolean,
|
||||
description,
|
||||
defaultValue
|
||||
});
|
||||
}
|
||||
|
||||
public get cliDefinitions(): {
|
||||
actual: Array<OptionDefinition<Name, boolean>>;
|
||||
display: Array<OptionDefinition<Name, boolean>>;
|
||||
} {
|
||||
const negated = Object.assign({}, this.definition, {
|
||||
name: `no-${this.name}`,
|
||||
defaultValue: !this.definition.defaultValue
|
||||
});
|
||||
const display = Object.assign({}, this.definition, {
|
||||
name: `[no-]${this.name}`,
|
||||
description: `${this.definition.description} (${this.definition.defaultValue ? "on" : "off"} by default)`
|
||||
});
|
||||
return {
|
||||
display: [display],
|
||||
actual: [this.definition, negated]
|
||||
};
|
||||
}
|
||||
|
||||
public getValue(values: Record<string, unknown>): boolean {
|
||||
let value = values[this.name];
|
||||
if (value === undefined) {
|
||||
value = this.definition.defaultValue;
|
||||
}
|
||||
|
||||
let negated = values[`no-${this.name}`];
|
||||
if (negated === undefined) {
|
||||
negated = !this.definition.defaultValue;
|
||||
}
|
||||
|
||||
if (value === "true") {
|
||||
value = true;
|
||||
} else if (value === "false") {
|
||||
value = false;
|
||||
}
|
||||
|
||||
if (this.definition.defaultValue) {
|
||||
return (value && !negated) as boolean;
|
||||
} else {
|
||||
return (value || !negated) as boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StringOption<Name extends string> extends Option<Name, string> {
|
||||
public constructor(
|
||||
name: Name,
|
||||
description: string,
|
||||
typeLabel: string,
|
||||
defaultValue: string,
|
||||
kind: OptionKind = "primary"
|
||||
) {
|
||||
const definition = {
|
||||
name,
|
||||
kind,
|
||||
type: String,
|
||||
description,
|
||||
typeLabel,
|
||||
defaultValue
|
||||
};
|
||||
super(definition);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: use const generics here
|
||||
export class EnumOption<
|
||||
Name extends string,
|
||||
EnumMap extends Record<string, unknown>,
|
||||
EnumKey extends Extract<keyof EnumMap, string> = Extract<keyof EnumMap, string>
|
||||
> extends Option<Name, EnumKey> {
|
||||
private readonly _values: EnumMap;
|
||||
|
||||
public constructor(
|
||||
name: Name,
|
||||
description: string,
|
||||
values: EnumMap,
|
||||
defaultValue: NoInfer<EnumKey>,
|
||||
kind: OptionKind = "primary"
|
||||
) {
|
||||
const definition = {
|
||||
name,
|
||||
kind,
|
||||
type: String,
|
||||
description,
|
||||
typeLabel: Object.keys(values).join("|"),
|
||||
defaultValue
|
||||
};
|
||||
super(definition);
|
||||
|
||||
this._values = values;
|
||||
}
|
||||
|
||||
public getEnumValue<Key extends EnumKey>(name: Key): EnumMap[Key] {
|
||||
if (!name) {
|
||||
const defaultKey = this.definition.defaultValue as NonNullable<Key>;
|
||||
return this._values[defaultKey];
|
||||
}
|
||||
|
||||
if (!(name in this._values)) {
|
||||
return messageError("RendererUnknownOptionValue", { value: name, name: this.name });
|
||||
}
|
||||
|
||||
return this._values[name];
|
||||
}
|
||||
}
|
33
packages/quicktype-core/src/RendererOptions/types.ts
Normal file
33
packages/quicktype-core/src/RendererOptions/types.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import type { EnumOption, Option } from "./index";
|
||||
|
||||
/**
|
||||
* Primary options show up in the web UI in the "Language" settings tab,
|
||||
* secondary options in "Other".
|
||||
*/
|
||||
export type OptionKind = "primary" | "secondary";
|
||||
|
||||
export interface OptionDefinition<Name extends string = string, T = unknown> {
|
||||
alias?: string;
|
||||
defaultOption?: boolean;
|
||||
defaultValue?: T;
|
||||
description: string;
|
||||
kind?: OptionKind;
|
||||
multiple?: boolean;
|
||||
name: Name;
|
||||
type: StringConstructor | BooleanConstructor;
|
||||
typeLabel?: string;
|
||||
}
|
||||
|
||||
export type OptionName<O> = O extends Option<infer Name, unknown> ? Name : never;
|
||||
export type OptionValue<O> =
|
||||
O extends EnumOption<string, infer EnumMap, infer EnumKey>
|
||||
? EnumMap[EnumKey]
|
||||
: O extends Option<string, infer Value>
|
||||
? Value
|
||||
: never;
|
||||
|
||||
export type OptionKey<O> = O extends EnumOption<string, Record<string, unknown>, infer EnumKey> ? EnumKey : O;
|
||||
|
||||
// FIXME: Merge these and use camelCase user-facing keys (v24)
|
||||
export type OptionMap<T> = { [K in keyof T as OptionName<T[K]>]: OptionKey<T[K]> }; // user-facing, keys are `name` property of Option, values are the available input type
|
||||
export type OptionValues<T> = { [K in keyof T]: OptionValue<T[K]> }; // internal, keys are keys of `_Options` object in each language file
|
@ -4,6 +4,8 @@ import { initTypeNames } from "./attributes/TypeNames";
|
||||
import { gatherNames } from "./GatherNames";
|
||||
import { InputData } from "./input/Inputs";
|
||||
import * as targetLanguages from "./language/All";
|
||||
import type { RendererOptions } from "./language/options.types";
|
||||
import type { LanguageName } from "./language/types";
|
||||
import { makeTransformations } from "./MakeTransformations";
|
||||
import { messageError } from "./Messages";
|
||||
import { combineClasses } from "./rewrites/CombineClasses";
|
||||
@ -22,7 +24,7 @@ import { type StringTypeMapping, TypeBuilder } from "./TypeBuilder";
|
||||
import { type TypeGraph, noneToAny, optionalToNullable, removeIndirectionIntersections } from "./TypeGraph";
|
||||
import { type FixMeOptionsType } from "./types";
|
||||
|
||||
export function getTargetLanguage(nameOrInstance: string | TargetLanguage): TargetLanguage {
|
||||
export function getTargetLanguage(nameOrInstance: LanguageName | TargetLanguage): TargetLanguage {
|
||||
if (typeof nameOrInstance === "object") {
|
||||
return nameOrInstance;
|
||||
}
|
||||
@ -35,10 +37,6 @@ export function getTargetLanguage(nameOrInstance: string | TargetLanguage): Targ
|
||||
return messageError("DriverUnknownOutputLanguage", { lang: nameOrInstance });
|
||||
}
|
||||
|
||||
export interface RendererOptions {
|
||||
[name: string]: string | boolean;
|
||||
}
|
||||
|
||||
export interface InferenceFlag {
|
||||
description: string;
|
||||
explanation: string;
|
||||
@ -122,7 +120,7 @@ export type InferenceFlags = { [F in InferenceFlagName]: boolean };
|
||||
* The options type for the main quicktype entry points,
|
||||
* `quicktypeMultiFile` and `quicktype`.
|
||||
*/
|
||||
export interface NonInferenceOptions {
|
||||
export interface NonInferenceOptions<Lang extends LanguageName = LanguageName> {
|
||||
/** Make all class property optional */
|
||||
allPropertiesOptional: boolean;
|
||||
/** Put class properties in alphabetical order, instead of in the order found in the JSON */
|
||||
@ -161,7 +159,7 @@ export interface NonInferenceOptions {
|
||||
* or a string specifying one of the names for quicktype's built-in target languages. For example,
|
||||
* both `cs` and `csharp` will generate C#.
|
||||
*/
|
||||
lang: string | TargetLanguage;
|
||||
lang: Lang | TargetLanguage;
|
||||
/** If given, output these comments at the beginning of the main output file */
|
||||
leadingComments?: Comment[];
|
||||
/** Don't render output. This is mainly useful for benchmarking. */
|
||||
@ -171,7 +169,7 @@ export interface NonInferenceOptions {
|
||||
*/
|
||||
outputFilename: string;
|
||||
/** Options for the target language's renderer */
|
||||
rendererOptions: RendererOptions;
|
||||
rendererOptions: RendererOptions<Lang>;
|
||||
}
|
||||
|
||||
export type Options = NonInferenceOptions & InferenceFlags;
|
||||
|
@ -10,27 +10,42 @@ import { defined } from "./support/Support";
|
||||
import { type Type } from "./Type";
|
||||
import { type StringTypeMapping } from "./TypeBuilder";
|
||||
import { type TypeGraph } from "./TypeGraph";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "./types";
|
||||
import { type FixMeOptionsType } from "./types";
|
||||
|
||||
export type MultiFileRenderResult = ReadonlyMap<string, SerializedRenderResult>;
|
||||
|
||||
export abstract class TargetLanguage {
|
||||
public constructor(
|
||||
public readonly displayName: string,
|
||||
public readonly names: string[],
|
||||
public readonly extension: string
|
||||
) {}
|
||||
export interface LanguageConfig {
|
||||
readonly displayName: string;
|
||||
readonly extension: string;
|
||||
readonly names: readonly string[];
|
||||
}
|
||||
|
||||
protected abstract getOptions(): Array<Option<FixMeOptionsAnyType>>;
|
||||
export abstract class TargetLanguage<Config extends LanguageConfig = LanguageConfig> {
|
||||
public readonly displayName: Config["displayName"];
|
||||
|
||||
public get optionDefinitions(): OptionDefinition[] {
|
||||
return this.getOptions().map(o => o.definition);
|
||||
public readonly names: Config["names"];
|
||||
|
||||
public readonly extension: Config["extension"];
|
||||
|
||||
public constructor({ displayName, names, extension }: Config) {
|
||||
this.displayName = displayName;
|
||||
this.names = names;
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
public get cliOptionDefinitions(): { actual: OptionDefinition[]; display: OptionDefinition[] } {
|
||||
let actual: OptionDefinition[] = [];
|
||||
let display: OptionDefinition[] = [];
|
||||
for (const { cliDefinitions } of this.getOptions()) {
|
||||
protected abstract getOptions(): Record<string, Option<string, unknown>>;
|
||||
|
||||
public get optionDefinitions(): Array<OptionDefinition<string, unknown>> {
|
||||
return Object.values(this.getOptions()).map(o => o.definition);
|
||||
}
|
||||
|
||||
public get cliOptionDefinitions(): {
|
||||
actual: Array<OptionDefinition<string, unknown>>;
|
||||
display: Array<OptionDefinition<string, unknown>>;
|
||||
} {
|
||||
let actual: Array<OptionDefinition<string, unknown>> = [];
|
||||
let display: Array<OptionDefinition<string, unknown>> = [];
|
||||
for (const { cliDefinitions } of Object.values(this.getOptions())) {
|
||||
actual = actual.concat(cliDefinitions.actual);
|
||||
display = display.concat(cliDefinitions.display);
|
||||
}
|
||||
@ -38,7 +53,7 @@ export abstract class TargetLanguage {
|
||||
return { actual, display };
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
public get name(): (typeof this.names)[0] {
|
||||
return defined(this.names[0]);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { EqualityMap, iterableFirst, setFilter, setSortBy, setUnion } from "collection-utils";
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { type StringTypes, stringTypesTypeAttributeKind } from "./attributes/StringTypes";
|
||||
import {
|
||||
type CombinationKind,
|
||||
type TypeAttributes,
|
||||
@ -7,7 +9,6 @@ import {
|
||||
emptyTypeAttributes
|
||||
} from "./attributes/TypeAttributes";
|
||||
import { assert, assertNever, defined, panic } from "./support/Support";
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import {
|
||||
ArrayType,
|
||||
type ClassProperty,
|
||||
@ -21,9 +22,6 @@ import {
|
||||
UnionType,
|
||||
isPrimitiveStringTypeKind
|
||||
} from "./Type";
|
||||
// String types should be imported last to avoid circular dependency issues.
|
||||
// eslint-disable-next-line import/order
|
||||
import { type StringTypes, stringTypesTypeAttributeKind } from "./attributes/StringTypes";
|
||||
|
||||
export function assertIsObject(t: Type): ObjectType {
|
||||
if (t instanceof ObjectType) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
export {
|
||||
type Options,
|
||||
type RendererOptions,
|
||||
getTargetLanguage,
|
||||
quicktypeMultiFile,
|
||||
quicktypeMultiFileSync,
|
||||
@ -21,7 +20,7 @@ export { Ref, type JSONSchemaType, type JSONSchemaAttributes } from "./input/JSO
|
||||
export type { RenderContext } from "./Renderer";
|
||||
export { Option, type OptionDefinition, getOptionValues, type OptionValues } from "./RendererOptions";
|
||||
export { TargetLanguage, type MultiFileRenderResult } from "./TargetLanguage";
|
||||
export { all as defaultTargetLanguages, languageNamed } from "./language/All";
|
||||
|
||||
export {
|
||||
type MultiWord,
|
||||
type Sourcelike,
|
||||
|
@ -8,6 +8,7 @@ import { type RunContext } from "../Run";
|
||||
import { defined, errorMessage, panic } from "../support/Support";
|
||||
import { type TargetLanguage } from "../TargetLanguage";
|
||||
import { type TypeBuilder } from "../TypeBuilder";
|
||||
import { type LanguageName } from "../types";
|
||||
|
||||
import { type CompressedJSON, CompressedJSONFromString, type Value } from "./CompressedJSON";
|
||||
import { TypeInference } from "./Inference";
|
||||
@ -151,7 +152,7 @@ export class JSONInput<T> implements Input<JSONSourceData<T>> {
|
||||
}
|
||||
|
||||
export function jsonInputForTargetLanguage(
|
||||
targetLanguage: string | TargetLanguage,
|
||||
targetLanguage: LanguageName | TargetLanguage,
|
||||
languages?: TargetLanguage[],
|
||||
handleJSONRefs = false
|
||||
): JSONInput<string> {
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { iterableFind } from "collection-utils";
|
||||
|
||||
import { type TargetLanguage } from "../TargetLanguage";
|
||||
|
||||
import { CJSONTargetLanguage } from "./CJSON";
|
||||
@ -25,11 +23,12 @@ import { RustTargetLanguage } from "./Rust";
|
||||
import { Scala3TargetLanguage } from "./Scala3";
|
||||
import { SmithyTargetLanguage } from "./Smithy4s";
|
||||
import { SwiftTargetLanguage } from "./Swift";
|
||||
import { type LanguageDisplayName, type LanguageName, type LanguageNameMap } from "./types";
|
||||
import { TypeScriptEffectSchemaTargetLanguage } from "./TypeScriptEffectSchema";
|
||||
import { FlowTargetLanguage, TypeScriptTargetLanguage } from "./TypeScriptFlow";
|
||||
import { TypeScriptZodTargetLanguage } from "./TypeScriptZod";
|
||||
|
||||
export const all: TargetLanguage[] = [
|
||||
export const all = [
|
||||
new CJSONTargetLanguage(),
|
||||
new CPlusPlusTargetLanguage(),
|
||||
new CrystalTargetLanguage(),
|
||||
@ -48,7 +47,7 @@ export const all: TargetLanguage[] = [
|
||||
new ObjectiveCTargetLanguage(),
|
||||
new PhpTargetLanguage(),
|
||||
new PikeTargetLanguage(),
|
||||
new PythonTargetLanguage("Python", ["python", "py"], "py"),
|
||||
new PythonTargetLanguage(),
|
||||
new RubyTargetLanguage(),
|
||||
new RustTargetLanguage(),
|
||||
new Scala3TargetLanguage(),
|
||||
@ -57,14 +56,34 @@ export const all: TargetLanguage[] = [
|
||||
new TypeScriptTargetLanguage(),
|
||||
new TypeScriptEffectSchemaTargetLanguage(),
|
||||
new TypeScriptZodTargetLanguage()
|
||||
];
|
||||
] as const;
|
||||
|
||||
export function languageNamed(name: string, targetLanguages?: TargetLanguage[]): TargetLanguage | undefined {
|
||||
if (targetLanguages === undefined) {
|
||||
targetLanguages = all;
|
||||
all satisfies readonly TargetLanguage[];
|
||||
|
||||
export function languageNamed<Name extends LanguageName>(
|
||||
name: Name,
|
||||
targetLanguages: readonly TargetLanguage[] = all
|
||||
): LanguageNameMap[Name] {
|
||||
const foundLanguage = targetLanguages.find(language => language.names.includes(name));
|
||||
if (!foundLanguage) {
|
||||
throw new Error(`Unknown language name: ${name}`);
|
||||
}
|
||||
|
||||
const maybeTargetLanguage = iterableFind(targetLanguages, l => l.names.includes(name) || l.displayName === name);
|
||||
if (maybeTargetLanguage !== undefined) return maybeTargetLanguage;
|
||||
return iterableFind(targetLanguages, l => l.extension === name);
|
||||
return foundLanguage as LanguageNameMap[Name];
|
||||
}
|
||||
|
||||
export function isLanguageName(maybeName: string): maybeName is LanguageName {
|
||||
if (all.some(lang => (lang.names as readonly string[]).includes(maybeName))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isLanguageDisplayName(maybeName: string): maybeName is LanguageDisplayName {
|
||||
if (all.some(lang => lang.displayName === maybeName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -22,42 +22,43 @@
|
||||
*/
|
||||
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { type NamingStyle } from "../../support/Strings";
|
||||
import { EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { CJSONRenderer } from "./CJSONRenderer";
|
||||
|
||||
/* Naming styles */
|
||||
const pascalValue: [string, NamingStyle] = ["pascal-case", "pascal"];
|
||||
const underscoreValue: [string, NamingStyle] = ["underscore-case", "underscore"];
|
||||
const camelValue: [string, NamingStyle] = ["camel-case", "camel"];
|
||||
const upperUnderscoreValue: [string, NamingStyle] = ["upper-underscore-case", "upper-underscore"];
|
||||
const pascalUpperAcronymsValue: [string, NamingStyle] = ["pascal-case-upper-acronyms", "pascal-upper-acronyms"];
|
||||
const camelUpperAcronymsValue: [string, NamingStyle] = ["camel-case-upper-acronyms", "camel-upper-acronyms"];
|
||||
const namingStyles = {
|
||||
"pascal-case": "pascal",
|
||||
"underscore-case": "underscore",
|
||||
"camel-case": "camel",
|
||||
"upper-underscore-case": "upper-underscore",
|
||||
"pascal-case-upper-acronyms": "pascal-upper-acronyms",
|
||||
"camel-case-upper-acronyms": "camel-upper-acronyms"
|
||||
} as const;
|
||||
|
||||
/* cJSON generator options */
|
||||
export const cJSONOptions = {
|
||||
typeSourceStyle: new EnumOption(
|
||||
"source-style",
|
||||
"Source code generation type, whether to generate single or multiple source files",
|
||||
[
|
||||
["single-source", true],
|
||||
["multi-source", false]
|
||||
],
|
||||
{
|
||||
"single-source": true,
|
||||
"multi-source": false
|
||||
} as const,
|
||||
"single-source",
|
||||
"secondary"
|
||||
),
|
||||
typeIntegerSize: new EnumOption(
|
||||
"integer-size",
|
||||
"Integer code generation type (int64_t by default)",
|
||||
[
|
||||
["int8_t", "int8_t"],
|
||||
["int16_t", "int16_t"],
|
||||
["int32_t", "int32_t"],
|
||||
["int64_t", "int64_t"]
|
||||
],
|
||||
{
|
||||
int8_t: "int8_t",
|
||||
int16_t: "int16_t",
|
||||
int32_t: "int32_t",
|
||||
int64_t: "int64_t"
|
||||
} as const,
|
||||
"int64_t",
|
||||
"secondary"
|
||||
),
|
||||
@ -70,76 +71,51 @@ export const cJSONOptions = {
|
||||
addTypedefAlias: new EnumOption(
|
||||
"typedef-alias",
|
||||
"Add typedef alias to unions, structs, and enums (no typedef by default)",
|
||||
[
|
||||
["no-typedef", false],
|
||||
["add-typedef", true]
|
||||
],
|
||||
{
|
||||
"no-typedef": false,
|
||||
"add-typedef": true
|
||||
} as const,
|
||||
"no-typedef",
|
||||
"secondary"
|
||||
),
|
||||
printStyle: new EnumOption(
|
||||
"print-style",
|
||||
"Which cJSON print should be used (formatted by default)",
|
||||
[
|
||||
["print-formatted", false],
|
||||
["print-unformatted", true]
|
||||
],
|
||||
{
|
||||
"print-formatted": false,
|
||||
"print-unformatted": true
|
||||
} as const,
|
||||
"print-formatted",
|
||||
"secondary"
|
||||
),
|
||||
typeNamingStyle: new EnumOption<NamingStyle>("type-style", "Naming style for types", [
|
||||
pascalValue,
|
||||
underscoreValue,
|
||||
camelValue,
|
||||
upperUnderscoreValue,
|
||||
pascalUpperAcronymsValue,
|
||||
camelUpperAcronymsValue
|
||||
]),
|
||||
memberNamingStyle: new EnumOption<NamingStyle>("member-style", "Naming style for members", [
|
||||
underscoreValue,
|
||||
pascalValue,
|
||||
camelValue,
|
||||
upperUnderscoreValue,
|
||||
pascalUpperAcronymsValue,
|
||||
camelUpperAcronymsValue
|
||||
]),
|
||||
enumeratorNamingStyle: new EnumOption<NamingStyle>("enumerator-style", "Naming style for enumerators", [
|
||||
upperUnderscoreValue,
|
||||
underscoreValue,
|
||||
pascalValue,
|
||||
camelValue,
|
||||
pascalUpperAcronymsValue,
|
||||
camelUpperAcronymsValue
|
||||
])
|
||||
typeNamingStyle: new EnumOption("type-style", "Naming style for types", namingStyles, "pascal-case"),
|
||||
memberNamingStyle: new EnumOption("member-style", "Naming style for members", namingStyles, "underscore-case"),
|
||||
enumeratorNamingStyle: new EnumOption(
|
||||
"enumerator-style",
|
||||
"Naming style for enumerators",
|
||||
namingStyles,
|
||||
"upper-underscore-case"
|
||||
)
|
||||
};
|
||||
|
||||
/* cJSON generator target language */
|
||||
export class CJSONTargetLanguage extends TargetLanguage {
|
||||
/**
|
||||
* Constructor
|
||||
* @param displayName: display name
|
||||
* @params names: names
|
||||
* @param extension: extension of files
|
||||
*/
|
||||
public constructor(displayName = "C (cJSON)", names: string[] = ["cjson", "cJSON"], extension = "h") {
|
||||
super(displayName, names, extension);
|
||||
export const cJSONLanguageConfig = {
|
||||
displayName: "C (cJSON)",
|
||||
names: ["cjson", "cJSON"],
|
||||
extension: "h"
|
||||
} as const;
|
||||
|
||||
export class CJSONTargetLanguage extends TargetLanguage<typeof cJSONLanguageConfig> {
|
||||
public constructor() {
|
||||
super(cJSONLanguageConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return cJSON generator options
|
||||
* @return cJSON generator options array
|
||||
*/
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
cJSONOptions.typeSourceStyle,
|
||||
cJSONOptions.typeIntegerSize,
|
||||
cJSONOptions.addTypedefAlias,
|
||||
cJSONOptions.printStyle,
|
||||
cJSONOptions.hashtableSize,
|
||||
cJSONOptions.typeNamingStyle,
|
||||
cJSONOptions.memberNamingStyle,
|
||||
cJSONOptions.enumeratorNamingStyle
|
||||
];
|
||||
public getOptions(): typeof cJSONOptions {
|
||||
return cJSONOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,118 +1,96 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { type NamingStyle } from "../../support/Strings";
|
||||
import { BooleanOption, EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { CPlusPlusRenderer } from "./CPlusPlusRenderer";
|
||||
|
||||
const pascalValue: [string, NamingStyle] = ["pascal-case", "pascal"];
|
||||
const underscoreValue: [string, NamingStyle] = ["underscore-case", "underscore"];
|
||||
const camelValue: [string, NamingStyle] = ["camel-case", "camel"];
|
||||
const upperUnderscoreValue: [string, NamingStyle] = ["upper-underscore-case", "upper-underscore"];
|
||||
const pascalUpperAcronymsValue: [string, NamingStyle] = ["pascal-case-upper-acronyms", "pascal-upper-acronyms"];
|
||||
const camelUpperAcronymsValue: [string, NamingStyle] = ["camel-case-upper-acronyms", "camel-upper-acronyms"];
|
||||
// FIXME: share with CJSON
|
||||
const namingStyles = {
|
||||
"pascal-case": "pascal",
|
||||
"underscore-case": "underscore",
|
||||
"camel-case": "camel",
|
||||
"upper-underscore-case": "upper-underscore",
|
||||
"pascal-case-upper-acronyms": "pascal-upper-acronyms",
|
||||
"camel-case-upper-acronyms": "camel-upper-acronyms"
|
||||
} as const;
|
||||
|
||||
export const cPlusPlusOptions = {
|
||||
typeSourceStyle: new EnumOption(
|
||||
"source-style",
|
||||
"Source code generation type, whether to generate single or multiple source files",
|
||||
[
|
||||
["single-source", true],
|
||||
["multi-source", false]
|
||||
],
|
||||
{
|
||||
"single-source": true,
|
||||
"multi-source": false
|
||||
} as const,
|
||||
"single-source",
|
||||
"secondary"
|
||||
),
|
||||
includeLocation: new EnumOption(
|
||||
"include-location",
|
||||
"Whether json.hpp is to be located globally or locally",
|
||||
[
|
||||
["local-include", true],
|
||||
["global-include", false]
|
||||
],
|
||||
{
|
||||
"local-include": true,
|
||||
"global-include": false
|
||||
} as const,
|
||||
"local-include",
|
||||
"secondary"
|
||||
),
|
||||
codeFormat: new EnumOption(
|
||||
"code-format",
|
||||
"Generate classes with getters/setters, instead of structs",
|
||||
[
|
||||
["with-struct", false],
|
||||
["with-getter-setter", true]
|
||||
],
|
||||
{
|
||||
"with-struct": false,
|
||||
"with-getter-setter": true
|
||||
} as const,
|
||||
"with-getter-setter"
|
||||
),
|
||||
wstring: new EnumOption(
|
||||
"wstring",
|
||||
"Store strings using Utf-16 std::wstring, rather than Utf-8 std::string",
|
||||
[
|
||||
["use-string", false],
|
||||
["use-wstring", true]
|
||||
],
|
||||
{
|
||||
"use-string": false,
|
||||
"use-wstring": true
|
||||
} as const,
|
||||
"use-string"
|
||||
),
|
||||
westConst: new EnumOption(
|
||||
"const-style",
|
||||
"Put const to the left/west (const T) or right/east (T const)",
|
||||
[
|
||||
["west-const", true],
|
||||
["east-const", false]
|
||||
],
|
||||
{
|
||||
"west-const": true,
|
||||
"east-const": false
|
||||
} as const,
|
||||
"west-const"
|
||||
),
|
||||
justTypes: new BooleanOption("just-types", "Plain types only", false),
|
||||
namespace: new StringOption("namespace", "Name of the generated namespace(s)", "NAME", "quicktype"),
|
||||
enumType: new StringOption("enum-type", "Type of enum class", "NAME", "int", "secondary"),
|
||||
typeNamingStyle: new EnumOption<NamingStyle>("type-style", "Naming style for types", [
|
||||
pascalValue,
|
||||
underscoreValue,
|
||||
camelValue,
|
||||
upperUnderscoreValue,
|
||||
pascalUpperAcronymsValue,
|
||||
camelUpperAcronymsValue
|
||||
]),
|
||||
memberNamingStyle: new EnumOption<NamingStyle>("member-style", "Naming style for members", [
|
||||
underscoreValue,
|
||||
pascalValue,
|
||||
camelValue,
|
||||
upperUnderscoreValue,
|
||||
pascalUpperAcronymsValue,
|
||||
camelUpperAcronymsValue
|
||||
]),
|
||||
enumeratorNamingStyle: new EnumOption<NamingStyle>("enumerator-style", "Naming style for enumerators", [
|
||||
upperUnderscoreValue,
|
||||
underscoreValue,
|
||||
pascalValue,
|
||||
camelValue,
|
||||
pascalUpperAcronymsValue,
|
||||
camelUpperAcronymsValue
|
||||
]),
|
||||
typeNamingStyle: new EnumOption("type-style", "Naming style for types", namingStyles, "pascal-case"),
|
||||
memberNamingStyle: new EnumOption("member-style", "Naming style for members", namingStyles, "underscore-case"),
|
||||
enumeratorNamingStyle: new EnumOption(
|
||||
"enumerator-style",
|
||||
"Naming style for enumerators",
|
||||
namingStyles,
|
||||
"upper-underscore-case"
|
||||
),
|
||||
boost: new BooleanOption("boost", "Require a dependency on boost. Without boost, C++17 is required", true),
|
||||
hideNullOptional: new BooleanOption("hide-null-optional", "Hide null value for optional field", false)
|
||||
};
|
||||
|
||||
export class CPlusPlusTargetLanguage extends TargetLanguage {
|
||||
public constructor(displayName = "C++", names: string[] = ["c++", "cpp", "cplusplus"], extension = "cpp") {
|
||||
super(displayName, names, extension);
|
||||
export const cPlusPlusLanguageConfig = {
|
||||
displayName: "C++",
|
||||
names: ["c++", "cpp", "cplusplus"],
|
||||
extension: "cpp"
|
||||
} as const;
|
||||
|
||||
export class CPlusPlusTargetLanguage extends TargetLanguage<typeof cPlusPlusLanguageConfig> {
|
||||
public constructor() {
|
||||
super(cPlusPlusLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
cPlusPlusOptions.justTypes,
|
||||
cPlusPlusOptions.namespace,
|
||||
cPlusPlusOptions.codeFormat,
|
||||
cPlusPlusOptions.wstring,
|
||||
cPlusPlusOptions.westConst,
|
||||
cPlusPlusOptions.typeSourceStyle,
|
||||
cPlusPlusOptions.includeLocation,
|
||||
cPlusPlusOptions.typeNamingStyle,
|
||||
cPlusPlusOptions.memberNamingStyle,
|
||||
cPlusPlusOptions.enumeratorNamingStyle,
|
||||
cPlusPlusOptions.enumType,
|
||||
cPlusPlusOptions.boost,
|
||||
cPlusPlusOptions.hideNullOptional
|
||||
];
|
||||
public getOptions(): typeof cPlusPlusOptions {
|
||||
return cPlusPlusOptions;
|
||||
}
|
||||
|
||||
public get supportsUnionsWithBothNumberTypes(): boolean {
|
||||
|
@ -1,22 +1,16 @@
|
||||
import { type ConvenienceRenderer } from "../../ConvenienceRenderer";
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { assertNever } from "../../support/Support";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind, type Type } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { NewtonsoftCSharpRenderer } from "./NewtonSoftCSharpRenderer";
|
||||
import { SystemTextJsonCSharpRenderer } from "./SystemTextJsonCSharpRenderer";
|
||||
import { needTransformerForType } from "./utils";
|
||||
|
||||
export enum Framework {
|
||||
Newtonsoft = "Newtonsoft",
|
||||
SystemTextJson = "SystemTextJson"
|
||||
}
|
||||
|
||||
export type Version = 5 | 6;
|
||||
export interface OutputFeatures {
|
||||
attributes: boolean;
|
||||
helpers: boolean;
|
||||
@ -28,103 +22,102 @@ export const cSharpOptions = {
|
||||
framework: new EnumOption(
|
||||
"framework",
|
||||
"Serialization framework",
|
||||
[
|
||||
["NewtonSoft", Framework.Newtonsoft],
|
||||
["SystemTextJson", Framework.SystemTextJson]
|
||||
],
|
||||
{
|
||||
NewtonSoft: "NewtonSoft",
|
||||
SystemTextJson: "SystemTextJson"
|
||||
} as const,
|
||||
"NewtonSoft"
|
||||
),
|
||||
useList: new EnumOption("array-type", "Use T[] or List<T>", [
|
||||
["array", false],
|
||||
["list", true]
|
||||
]),
|
||||
useList: new EnumOption(
|
||||
"array-type",
|
||||
"Use T[] or List<T>",
|
||||
{
|
||||
array: false,
|
||||
list: true
|
||||
},
|
||||
"array"
|
||||
),
|
||||
dense: new EnumOption(
|
||||
"density",
|
||||
"Property density",
|
||||
[
|
||||
["normal", false],
|
||||
["dense", true]
|
||||
],
|
||||
{
|
||||
normal: false,
|
||||
dense: true
|
||||
} as const,
|
||||
"normal",
|
||||
"secondary"
|
||||
),
|
||||
// FIXME: Do this via a configurable named eventually.
|
||||
namespace: new StringOption("namespace", "Generated namespace", "NAME", "QuickType"),
|
||||
version: new EnumOption<Version>(
|
||||
version: new EnumOption(
|
||||
"csharp-version",
|
||||
"C# version",
|
||||
[
|
||||
["5", 5],
|
||||
["6", 6]
|
||||
],
|
||||
{
|
||||
"5": 5,
|
||||
"6": 6
|
||||
} as const,
|
||||
"6",
|
||||
"secondary"
|
||||
),
|
||||
virtual: new BooleanOption("virtual", "Generate virtual properties", false),
|
||||
typeForAny: new EnumOption<CSharpTypeForAny>(
|
||||
typeForAny: new EnumOption(
|
||||
"any-type",
|
||||
'Type to use for "any"',
|
||||
[
|
||||
["object", "object"],
|
||||
["dynamic", "dynamic"]
|
||||
],
|
||||
{
|
||||
object: "object",
|
||||
dynamic: "dynamic"
|
||||
} as const,
|
||||
"object",
|
||||
"secondary"
|
||||
),
|
||||
useDecimal: new EnumOption(
|
||||
"number-type",
|
||||
"Type to use for numbers",
|
||||
[
|
||||
["double", false],
|
||||
["decimal", true]
|
||||
],
|
||||
{
|
||||
double: false,
|
||||
decimal: true
|
||||
} as const,
|
||||
"double",
|
||||
"secondary"
|
||||
),
|
||||
features: new EnumOption("features", "Output features", [
|
||||
["complete", { namespaces: true, helpers: true, attributes: true }],
|
||||
["attributes-only", { namespaces: true, helpers: false, attributes: true }],
|
||||
["just-types-and-namespace", { namespaces: true, helpers: false, attributes: false }],
|
||||
["just-types", { namespaces: true, helpers: false, attributes: false }]
|
||||
]),
|
||||
features: new EnumOption(
|
||||
"features",
|
||||
"Output features",
|
||||
{
|
||||
"complete": { namespaces: true, helpers: true, attributes: true },
|
||||
"attributes-only": { namespaces: true, helpers: false, attributes: true },
|
||||
"just-types-and-namespace": { namespaces: true, helpers: false, attributes: false },
|
||||
"just-types": { namespaces: true, helpers: false, attributes: false }
|
||||
} as const,
|
||||
"complete"
|
||||
),
|
||||
baseclass: new EnumOption(
|
||||
"base-class",
|
||||
"Base class",
|
||||
[
|
||||
["EntityData", "EntityData"],
|
||||
["Object", undefined]
|
||||
],
|
||||
{
|
||||
EntityData: "EntityData",
|
||||
Object: undefined
|
||||
} as const,
|
||||
"Object",
|
||||
"secondary"
|
||||
),
|
||||
checkRequired: new BooleanOption("check-required", "Fail if required properties are missing", false),
|
||||
keepPropertyName: new BooleanOption("keep-property-name", "Keep original field name generate", false)
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const newtonsoftCSharpOptions = Object.assign({}, cSharpOptions, {});
|
||||
|
||||
export const systemTextJsonCSharpOptions = Object.assign({}, cSharpOptions, {});
|
||||
|
||||
export class CSharpTargetLanguage extends TargetLanguage {
|
||||
export const cSharpLanguageConfig = { displayName: "C#", names: ["cs", "csharp"], extension: "cs" } as const;
|
||||
|
||||
export class CSharpTargetLanguage extends TargetLanguage<typeof cSharpLanguageConfig> {
|
||||
public constructor() {
|
||||
super("C#", ["cs", "csharp"], "cs");
|
||||
super(cSharpLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
cSharpOptions.framework,
|
||||
cSharpOptions.namespace,
|
||||
cSharpOptions.version,
|
||||
cSharpOptions.dense,
|
||||
cSharpOptions.useList,
|
||||
cSharpOptions.useDecimal,
|
||||
cSharpOptions.typeForAny,
|
||||
cSharpOptions.virtual,
|
||||
cSharpOptions.features,
|
||||
cSharpOptions.baseclass,
|
||||
cSharpOptions.checkRequired,
|
||||
cSharpOptions.keepPropertyName
|
||||
];
|
||||
public getOptions(): typeof cSharpOptions {
|
||||
return cSharpOptions;
|
||||
}
|
||||
|
||||
public get stringTypeMapping(): StringTypeMapping {
|
||||
@ -156,13 +149,13 @@ export class CSharpTargetLanguage extends TargetLanguage {
|
||||
const options = getOptionValues(cSharpOptions, untypedOptionValues);
|
||||
|
||||
switch (options.framework) {
|
||||
case Framework.Newtonsoft:
|
||||
case "NewtonSoft":
|
||||
return new NewtonsoftCSharpRenderer(
|
||||
this,
|
||||
renderContext,
|
||||
getOptionValues(newtonsoftCSharpOptions, untypedOptionValues)
|
||||
);
|
||||
case Framework.SystemTextJson:
|
||||
case "SystemTextJson":
|
||||
return new SystemTextJsonCSharpRenderer(
|
||||
this,
|
||||
renderContext,
|
||||
|
@ -1,24 +1,28 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { type Option } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType } from "../../types";
|
||||
|
||||
import { CrystalRenderer } from "./CrystalRenderer";
|
||||
|
||||
export class CrystalTargetLanguage extends TargetLanguage {
|
||||
protected makeRenderer(renderContext: RenderContext): CrystalRenderer {
|
||||
return new CrystalRenderer(this, renderContext);
|
||||
export const crystalLanguageConfig = {
|
||||
displayName: "Crystal",
|
||||
names: ["crystal", "cr", "crystallang"],
|
||||
extension: "cr"
|
||||
} as const;
|
||||
|
||||
export class CrystalTargetLanguage extends TargetLanguage<typeof crystalLanguageConfig> {
|
||||
public constructor() {
|
||||
super(crystalLanguageConfig);
|
||||
}
|
||||
|
||||
public constructor() {
|
||||
super("Crystal", ["crystal", "cr", "crystallang"], "cr");
|
||||
protected makeRenderer(renderContext: RenderContext): CrystalRenderer {
|
||||
return new CrystalRenderer(this, renderContext);
|
||||
}
|
||||
|
||||
protected get defaultIndentation(): string {
|
||||
return " ";
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [];
|
||||
public getOptions(): {} {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { DartRenderer } from "./DartRenderer";
|
||||
|
||||
@ -31,25 +31,15 @@ export const dartOptions = {
|
||||
partName: new StringOption("part-name", "Use this name in `part` directive", "NAME", "", "secondary")
|
||||
};
|
||||
|
||||
export class DartTargetLanguage extends TargetLanguage {
|
||||
export const dartLanguageConfig = { displayName: "Dart", names: ["dart"], extension: "dart" } as const;
|
||||
|
||||
export class DartTargetLanguage extends TargetLanguage<typeof dartLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Dart", ["dart"], "dart");
|
||||
super(dartLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
dartOptions.nullSafety,
|
||||
dartOptions.justTypes,
|
||||
dartOptions.codersInClass,
|
||||
dartOptions.methodNamesWithMap,
|
||||
dartOptions.requiredProperties,
|
||||
dartOptions.finalProperties,
|
||||
dartOptions.generateCopyWith,
|
||||
dartOptions.useFreezed,
|
||||
dartOptions.useHive,
|
||||
dartOptions.useJsonAnnotation,
|
||||
dartOptions.partName
|
||||
];
|
||||
public getOptions(): typeof dartOptions {
|
||||
return dartOptions;
|
||||
}
|
||||
|
||||
public get supportsUnionsWithBothNumberTypes(): boolean {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { ElixirRenderer } from "./ElixirRenderer";
|
||||
|
||||
@ -10,13 +10,19 @@ export const elixirOptions = {
|
||||
namespace: new StringOption("namespace", "Specify a module namespace", "NAME", "")
|
||||
};
|
||||
|
||||
export class ElixirTargetLanguage extends TargetLanguage {
|
||||
export const elixirLanguageConfig = {
|
||||
displayName: "Elixir",
|
||||
names: ["elixir"],
|
||||
extension: "ex"
|
||||
} as const;
|
||||
|
||||
export class ElixirTargetLanguage extends TargetLanguage<typeof elixirLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Elixir", ["elixir"], "ex");
|
||||
super(elixirLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [elixirOptions.justTypes, elixirOptions.namespace];
|
||||
public getOptions(): typeof elixirOptions {
|
||||
return elixirOptions;
|
||||
}
|
||||
|
||||
public get supportsOptionalClassProperties(): boolean {
|
||||
|
@ -1,27 +1,38 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { ElmRenderer } from "./ElmRenderer";
|
||||
|
||||
export const elmOptions = {
|
||||
justTypes: new BooleanOption("just-types", "Plain types only", false),
|
||||
useList: new EnumOption("array-type", "Use Array or List", [
|
||||
["array", false],
|
||||
["list", true]
|
||||
]),
|
||||
useList: new EnumOption(
|
||||
"array-type",
|
||||
"Use Array or List",
|
||||
{
|
||||
array: false,
|
||||
list: true
|
||||
} as const,
|
||||
"array"
|
||||
),
|
||||
// FIXME: Do this via a configurable named eventually.
|
||||
moduleName: new StringOption("module", "Generated module name", "NAME", "QuickType")
|
||||
};
|
||||
|
||||
export class ElmTargetLanguage extends TargetLanguage {
|
||||
export const elmLanguageConfig = {
|
||||
displayName: "Elm",
|
||||
names: ["elm"],
|
||||
extension: "elm"
|
||||
} as const;
|
||||
|
||||
export class ElmTargetLanguage extends TargetLanguage<typeof elmLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Elm", ["elm"], "elm");
|
||||
super(elmLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [elmOptions.justTypes, elmOptions.moduleName, elmOptions.useList];
|
||||
public getOptions(): typeof elmOptions {
|
||||
return elmOptions;
|
||||
}
|
||||
|
||||
public get supportsOptionalClassProperties(): boolean {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { GoRenderer } from "./GolangRenderer";
|
||||
|
||||
@ -20,20 +20,19 @@ export const goOptions = {
|
||||
)
|
||||
};
|
||||
|
||||
export class GoTargetLanguage extends TargetLanguage {
|
||||
const golangLanguageConfig = {
|
||||
displayName: "Go",
|
||||
names: ["go", "golang"],
|
||||
extension: "go"
|
||||
} as const;
|
||||
|
||||
export class GoTargetLanguage extends TargetLanguage<typeof golangLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Go", ["go", "golang"], "go");
|
||||
super(golangLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
goOptions.justTypes,
|
||||
goOptions.justTypesAndPackage,
|
||||
goOptions.packageName,
|
||||
goOptions.multiFileOutput,
|
||||
goOptions.fieldTags,
|
||||
goOptions.omitEmpty
|
||||
];
|
||||
public getOptions(): typeof goOptions {
|
||||
return goOptions;
|
||||
}
|
||||
|
||||
public get supportsUnionsWithBothNumberTypes(): boolean {
|
||||
|
@ -1,26 +1,37 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { HaskellRenderer } from "./HaskellRenderer";
|
||||
|
||||
export const haskellOptions = {
|
||||
justTypes: new BooleanOption("just-types", "Plain types only", false),
|
||||
useList: new EnumOption("array-type", "Use Array or List", [
|
||||
["array", false],
|
||||
["list", true]
|
||||
]),
|
||||
useList: new EnumOption(
|
||||
"array-type",
|
||||
"Use Array or List",
|
||||
{
|
||||
array: false,
|
||||
list: true
|
||||
} as const,
|
||||
"array"
|
||||
),
|
||||
moduleName: new StringOption("module", "Generated module name", "NAME", "QuickType")
|
||||
};
|
||||
|
||||
export class HaskellTargetLanguage extends TargetLanguage {
|
||||
export const haskellLanguageConfig = {
|
||||
displayName: "Haskell",
|
||||
names: ["haskell"],
|
||||
extension: "haskell"
|
||||
} as const;
|
||||
|
||||
export class HaskellTargetLanguage extends TargetLanguage<typeof haskellLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Haskell", ["haskell"], "haskell");
|
||||
super(haskellLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [haskellOptions.justTypes, haskellOptions.moduleName, haskellOptions.useList];
|
||||
public getOptions(): typeof haskellOptions {
|
||||
return haskellOptions;
|
||||
}
|
||||
|
||||
public get supportsOptionalClassProperties(): boolean {
|
||||
|
@ -1,18 +1,23 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { type Option } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type StringTypeMapping, getNoStringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { JSONSchemaRenderer } from "./JSONSchemaRenderer";
|
||||
|
||||
export class JSONSchemaTargetLanguage extends TargetLanguage {
|
||||
export const JSONSchemaLanguageConfig = {
|
||||
displayName: "JSON Schema",
|
||||
names: ["schema", "json-schema"],
|
||||
extension: "schema"
|
||||
} as const;
|
||||
|
||||
export class JSONSchemaTargetLanguage extends TargetLanguage<typeof JSONSchemaLanguageConfig> {
|
||||
public constructor() {
|
||||
super("JSON Schema", ["schema", "json-schema"], "schema");
|
||||
super(JSONSchemaLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [];
|
||||
public getOptions(): {} {
|
||||
return {};
|
||||
}
|
||||
|
||||
public get stringTypeMapping(): StringTypeMapping {
|
||||
|
@ -1,1523 +0,0 @@
|
||||
import { type PrimitiveStringTypeKind, type StringTypeMapping, type TransformedStringTypeKind } from "..";
|
||||
import { anyTypeIssueAnnotation, nullTypeIssueAnnotation } from "../Annotation";
|
||||
import { ConvenienceRenderer, type ForbiddenWordsInfo } from "../ConvenienceRenderer";
|
||||
import { DependencyName, type Name, type Namer, funPrefixNamer } from "../Naming";
|
||||
import { type RenderContext } from "../Renderer";
|
||||
import {
|
||||
BooleanOption,
|
||||
EnumOption,
|
||||
type Option,
|
||||
type OptionValues,
|
||||
StringOption,
|
||||
getOptionValues
|
||||
} from "../RendererOptions";
|
||||
import { type Sourcelike, maybeAnnotated } from "../Source";
|
||||
import { AcronymStyleOptions, acronymOption, acronymStyle } from "../support/Acronyms";
|
||||
import {
|
||||
allLowerWordStyle,
|
||||
allUpperWordStyle,
|
||||
capitalize,
|
||||
combineWords,
|
||||
escapeNonPrintableMapper,
|
||||
firstUpperWordStyle,
|
||||
isAscii,
|
||||
isDigit,
|
||||
isLetter,
|
||||
splitIntoWords,
|
||||
standardUnicodeHexEscape,
|
||||
utf16ConcatMap,
|
||||
utf16LegalizeCharacters
|
||||
} from "../support/Strings";
|
||||
import { assert, assertNever, defined, panic } from "../support/Support";
|
||||
import { TargetLanguage } from "../TargetLanguage";
|
||||
import {
|
||||
ArrayType,
|
||||
type ClassProperty,
|
||||
ClassType,
|
||||
EnumType,
|
||||
MapType,
|
||||
type Type,
|
||||
type TypeKind,
|
||||
UnionType
|
||||
} from "../Type";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../types";
|
||||
import { directlyReachableSingleNamedType, matchType, nullableFromUnion, removeNullFromUnion } from "../TypeUtils";
|
||||
|
||||
export const javaOptions = {
|
||||
useList: new EnumOption(
|
||||
"array-type",
|
||||
"Use T[] or List<T>",
|
||||
[
|
||||
["array", false],
|
||||
["list", true]
|
||||
],
|
||||
"array"
|
||||
),
|
||||
justTypes: new BooleanOption("just-types", "Plain types only", false),
|
||||
dateTimeProvider: new EnumOption(
|
||||
"datetime-provider",
|
||||
"Date time provider type",
|
||||
[
|
||||
["java8", "java8"],
|
||||
["legacy", "legacy"]
|
||||
],
|
||||
"java8"
|
||||
),
|
||||
acronymStyle: acronymOption(AcronymStyleOptions.Pascal),
|
||||
// FIXME: Do this via a configurable named eventually.
|
||||
packageName: new StringOption("package", "Generated package name", "NAME", "io.quicktype"),
|
||||
lombok: new BooleanOption("lombok", "Use lombok", false, "primary"),
|
||||
lombokCopyAnnotations: new BooleanOption("lombok-copy-annotations", "Copy accessor annotations", true, "secondary")
|
||||
};
|
||||
|
||||
export class JavaTargetLanguage extends TargetLanguage {
|
||||
public constructor() {
|
||||
super("Java", ["java"], "java");
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
javaOptions.useList,
|
||||
javaOptions.justTypes,
|
||||
javaOptions.dateTimeProvider,
|
||||
javaOptions.acronymStyle,
|
||||
javaOptions.packageName,
|
||||
javaOptions.lombok,
|
||||
javaOptions.lombokCopyAnnotations
|
||||
];
|
||||
}
|
||||
|
||||
public get supportsUnionsWithBothNumberTypes(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected makeRenderer(renderContext: RenderContext, untypedOptionValues: FixMeOptionsType): JavaRenderer {
|
||||
const options = getOptionValues(javaOptions, untypedOptionValues);
|
||||
if (options.justTypes) {
|
||||
return new JavaRenderer(this, renderContext, options);
|
||||
}
|
||||
|
||||
return new JacksonRenderer(this, renderContext, options);
|
||||
}
|
||||
|
||||
public get stringTypeMapping(): StringTypeMapping {
|
||||
const mapping: Map<TransformedStringTypeKind, PrimitiveStringTypeKind> = new Map();
|
||||
mapping.set("date", "date");
|
||||
mapping.set("time", "time");
|
||||
mapping.set("date-time", "date-time");
|
||||
mapping.set("uuid", "uuid");
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
|
||||
const javaKeywords = [
|
||||
"_", // as of release 9, '_' is a keyword, and may not be used as an identifier
|
||||
"Object",
|
||||
"Class",
|
||||
"System",
|
||||
"Long",
|
||||
"Double",
|
||||
"Boolean",
|
||||
"String",
|
||||
"List",
|
||||
"Map",
|
||||
"UUID",
|
||||
"Exception",
|
||||
"IOException",
|
||||
"Override",
|
||||
"abstract",
|
||||
"continue",
|
||||
"for",
|
||||
"new",
|
||||
"switch",
|
||||
"assert",
|
||||
"default",
|
||||
"goto",
|
||||
"package",
|
||||
"synchronized",
|
||||
"boolean",
|
||||
"do",
|
||||
"if",
|
||||
"private",
|
||||
"this",
|
||||
"break",
|
||||
"double",
|
||||
"implements",
|
||||
"protected",
|
||||
"throw",
|
||||
"byte",
|
||||
"else",
|
||||
"import",
|
||||
"public",
|
||||
"throws",
|
||||
"case",
|
||||
"enum",
|
||||
"instanceof",
|
||||
"return",
|
||||
"transient",
|
||||
"catch",
|
||||
"extends",
|
||||
"int",
|
||||
"short",
|
||||
"try",
|
||||
"char",
|
||||
"final",
|
||||
"interface",
|
||||
"static",
|
||||
"void",
|
||||
"class",
|
||||
"finally",
|
||||
"long",
|
||||
"strictfp",
|
||||
"volatile",
|
||||
"const",
|
||||
"float",
|
||||
"native",
|
||||
"super",
|
||||
"while",
|
||||
"null",
|
||||
"false",
|
||||
"true"
|
||||
];
|
||||
|
||||
export const stringEscape = utf16ConcatMap(escapeNonPrintableMapper(isAscii, standardUnicodeHexEscape));
|
||||
|
||||
function isStartCharacter(codePoint: number): boolean {
|
||||
if (codePoint === 0x5f) return true; // underscore
|
||||
return isAscii(codePoint) && isLetter(codePoint);
|
||||
}
|
||||
|
||||
function isPartCharacter(codePoint: number): boolean {
|
||||
return isStartCharacter(codePoint) || (isAscii(codePoint) && isDigit(codePoint));
|
||||
}
|
||||
|
||||
const legalizeName = utf16LegalizeCharacters(isPartCharacter);
|
||||
|
||||
export function javaNameStyle(
|
||||
startWithUpper: boolean,
|
||||
upperUnderscore: boolean,
|
||||
original: string,
|
||||
acronymsStyle: (s: string) => string = allUpperWordStyle
|
||||
): string {
|
||||
const words = splitIntoWords(original);
|
||||
return combineWords(
|
||||
words,
|
||||
legalizeName,
|
||||
upperUnderscore ? allUpperWordStyle : startWithUpper ? firstUpperWordStyle : allLowerWordStyle,
|
||||
upperUnderscore ? allUpperWordStyle : firstUpperWordStyle,
|
||||
upperUnderscore || startWithUpper ? allUpperWordStyle : allLowerWordStyle,
|
||||
acronymsStyle,
|
||||
upperUnderscore ? "_" : "",
|
||||
isStartCharacter
|
||||
);
|
||||
}
|
||||
|
||||
abstract class JavaDateTimeProvider {
|
||||
public constructor(
|
||||
protected readonly _renderer: JavaRenderer,
|
||||
protected readonly _className: string
|
||||
) {}
|
||||
|
||||
public abstract keywords: string[];
|
||||
|
||||
public abstract dateTimeImports: string[];
|
||||
|
||||
public abstract dateImports: string[];
|
||||
|
||||
public abstract timeImports: string[];
|
||||
|
||||
public abstract converterImports: string[];
|
||||
|
||||
public abstract dateTimeType: string;
|
||||
|
||||
public abstract dateType: string;
|
||||
|
||||
public abstract timeType: string;
|
||||
|
||||
public abstract dateTimeJacksonAnnotations: string[];
|
||||
|
||||
public abstract dateJacksonAnnotations: string[];
|
||||
|
||||
public abstract timeJacksonAnnotations: string[];
|
||||
|
||||
public abstract emitDateTimeConverters(): void;
|
||||
|
||||
public shouldEmitDateTimeConverter = true;
|
||||
|
||||
public shouldEmitTimeConverter = true;
|
||||
|
||||
public shouldEmitDateConverter = true;
|
||||
|
||||
public abstract convertStringToDateTime(variable: Sourcelike): Sourcelike;
|
||||
public abstract convertStringToTime(variable: Sourcelike): Sourcelike;
|
||||
public abstract convertStringToDate(variable: Sourcelike): Sourcelike;
|
||||
|
||||
public abstract convertDateTimeToString(variable: Sourcelike): Sourcelike;
|
||||
public abstract convertTimeToString(variable: Sourcelike): Sourcelike;
|
||||
public abstract convertDateToString(variable: Sourcelike): Sourcelike;
|
||||
}
|
||||
|
||||
class Java8DateTimeProvider extends JavaDateTimeProvider {
|
||||
public keywords = [
|
||||
"LocalDate",
|
||||
"OffsetDateTime",
|
||||
"OffsetTime",
|
||||
"ZoneOffset",
|
||||
"ZonedDateTime",
|
||||
"DateTimeFormatter",
|
||||
"DateTimeFormatterBuilder",
|
||||
"ChronoField"
|
||||
];
|
||||
|
||||
public dateTimeImports: string[] = ["java.time.OffsetDateTime"];
|
||||
|
||||
public dateImports: string[] = ["java.time.LocalDate"];
|
||||
|
||||
public timeImports: string[] = ["java.time.OffsetTime"];
|
||||
|
||||
public converterImports: string[] = [
|
||||
"java.time.LocalDate",
|
||||
"java.time.OffsetDateTime",
|
||||
"java.time.OffsetTime",
|
||||
"java.time.ZoneOffset",
|
||||
"java.time.ZonedDateTime",
|
||||
"java.time.format.DateTimeFormatter",
|
||||
"java.time.format.DateTimeFormatterBuilder",
|
||||
"java.time.temporal.ChronoField"
|
||||
];
|
||||
|
||||
public dateTimeType = "OffsetDateTime";
|
||||
|
||||
public dateType = "LocalDate";
|
||||
|
||||
public timeType = "OffsetTime";
|
||||
|
||||
public dateTimeJacksonAnnotations: string[] = [];
|
||||
|
||||
public dateJacksonAnnotations: string[] = [];
|
||||
|
||||
public timeJacksonAnnotations: string[] = [];
|
||||
|
||||
public emitDateTimeConverters(): void {
|
||||
this._renderer.ensureBlankLine();
|
||||
this._renderer.emitLine(
|
||||
"private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()"
|
||||
);
|
||||
this._renderer.indent(() =>
|
||||
this._renderer.indent(() => {
|
||||
this._renderer.emitLine(".appendOptional(DateTimeFormatter.ISO_DATE_TIME)");
|
||||
this._renderer.emitLine(".appendOptional(DateTimeFormatter.ISO_OFFSET_DATE_TIME)");
|
||||
this._renderer.emitLine(".appendOptional(DateTimeFormatter.ISO_INSTANT)");
|
||||
this._renderer.emitLine('.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SX"))');
|
||||
this._renderer.emitLine('.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssX"))');
|
||||
this._renderer.emitLine('.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))');
|
||||
this._renderer.emitLine(".toFormatter()");
|
||||
this._renderer.emitLine(".withZone(ZoneOffset.UTC);");
|
||||
})
|
||||
);
|
||||
this._renderer.ensureBlankLine();
|
||||
this._renderer.emitBlock("public static OffsetDateTime parseDateTimeString(String str)", () => {
|
||||
this._renderer.emitLine(
|
||||
"return ZonedDateTime.from(Converter.DATE_TIME_FORMATTER.parse(str)).toOffsetDateTime();"
|
||||
);
|
||||
});
|
||||
|
||||
this._renderer.ensureBlankLine();
|
||||
this._renderer.emitLine(
|
||||
"private static final DateTimeFormatter TIME_FORMATTER = new DateTimeFormatterBuilder()"
|
||||
);
|
||||
this._renderer.indent(() =>
|
||||
this._renderer.indent(() => {
|
||||
this._renderer.emitLine(".appendOptional(DateTimeFormatter.ISO_TIME)");
|
||||
this._renderer.emitLine(".appendOptional(DateTimeFormatter.ISO_OFFSET_TIME)");
|
||||
this._renderer.emitLine(".parseDefaulting(ChronoField.YEAR, 2020)");
|
||||
this._renderer.emitLine(".parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)");
|
||||
this._renderer.emitLine(".parseDefaulting(ChronoField.DAY_OF_MONTH, 1)");
|
||||
this._renderer.emitLine(".toFormatter()");
|
||||
this._renderer.emitLine(".withZone(ZoneOffset.UTC);");
|
||||
})
|
||||
);
|
||||
this._renderer.ensureBlankLine();
|
||||
this._renderer.emitBlock("public static OffsetTime parseTimeString(String str)", () => {
|
||||
this._renderer.emitLine(
|
||||
"return ZonedDateTime.from(Converter.TIME_FORMATTER.parse(str)).toOffsetDateTime().toOffsetTime();"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public convertStringToDateTime(variable: Sourcelike): Sourcelike {
|
||||
return [this._className, ".parseDateTimeString(", variable, ")"];
|
||||
}
|
||||
|
||||
public convertStringToTime(variable: Sourcelike): Sourcelike {
|
||||
return [this._className, ".parseTimeString(", variable, ")"];
|
||||
}
|
||||
|
||||
public convertStringToDate(variable: Sourcelike): Sourcelike {
|
||||
return ["LocalDate.parse(", variable, ")"];
|
||||
}
|
||||
|
||||
public convertDateTimeToString(variable: Sourcelike): Sourcelike {
|
||||
return [variable, ".format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)"];
|
||||
}
|
||||
|
||||
public convertTimeToString(variable: Sourcelike): Sourcelike {
|
||||
return [variable, ".format(java.time.format.DateTimeFormatter.ISO_OFFSET_TIME)"];
|
||||
}
|
||||
|
||||
public convertDateToString(variable: Sourcelike): Sourcelike {
|
||||
return [variable, ".format(java.time.format.DateTimeFormatter.ISO_DATE)"];
|
||||
}
|
||||
}
|
||||
|
||||
class JavaLegacyDateTimeProvider extends JavaDateTimeProvider {
|
||||
public keywords = ["SimpleDateFormat", "Date"];
|
||||
|
||||
public dateTimeImports: string[] = ["java.util.Date"];
|
||||
|
||||
public dateImports: string[] = ["java.util.Date"];
|
||||
|
||||
public timeImports: string[] = ["java.util.Date"];
|
||||
|
||||
public converterImports: string[] = ["java.util.Date", "java.text.SimpleDateFormat"];
|
||||
|
||||
public dateTimeType = "Date";
|
||||
|
||||
public dateType = "Date";
|
||||
|
||||
public timeType = "Date";
|
||||
|
||||
public dateTimeJacksonAnnotations: string[] = [
|
||||
'@JsonFormat(pattern = "yyyy-MM-dd\'T\'HH:mm:ssX", timezone = "UTC")'
|
||||
];
|
||||
|
||||
public dateJacksonAnnotations: string[] = ['@JsonFormat(pattern = "yyyy-MM-dd")'];
|
||||
|
||||
public timeJacksonAnnotations: string[] = ['@JsonFormat(pattern = "HH:mm:ssX", timezone = "UTC")'];
|
||||
|
||||
public shouldEmitTimeConverter = false;
|
||||
|
||||
public shouldEmitDateConverter = false;
|
||||
|
||||
public emitDateTimeConverters(): void {
|
||||
this._renderer.ensureBlankLine();
|
||||
this._renderer.emitLine("private static final String[] DATE_TIME_FORMATS = {");
|
||||
this._renderer.indent(() =>
|
||||
this._renderer.indent(() => {
|
||||
this._renderer.emitLine("\"yyyy-MM-dd'T'HH:mm:ss.SX\",");
|
||||
this._renderer.emitLine("\"yyyy-MM-dd'T'HH:mm:ss.S\",");
|
||||
this._renderer.emitLine("\"yyyy-MM-dd'T'HH:mm:ssX\",");
|
||||
this._renderer.emitLine("\"yyyy-MM-dd'T'HH:mm:ss\",");
|
||||
this._renderer.emitLine('"yyyy-MM-dd HH:mm:ss.SX",');
|
||||
this._renderer.emitLine('"yyyy-MM-dd HH:mm:ss.S",');
|
||||
this._renderer.emitLine('"yyyy-MM-dd HH:mm:ssX",');
|
||||
this._renderer.emitLine('"yyyy-MM-dd HH:mm:ss",');
|
||||
this._renderer.emitLine('"HH:mm:ss.SZ",');
|
||||
this._renderer.emitLine('"HH:mm:ss.S",');
|
||||
this._renderer.emitLine('"HH:mm:ssZ",');
|
||||
this._renderer.emitLine('"HH:mm:ss",');
|
||||
this._renderer.emitLine('"yyyy-MM-dd",');
|
||||
})
|
||||
);
|
||||
this._renderer.emitLine("};");
|
||||
this._renderer.ensureBlankLine();
|
||||
this._renderer.emitBlock("public static Date parseAllDateTimeString(String str)", () => {
|
||||
this._renderer.emitBlock("for (String format : DATE_TIME_FORMATS)", () => {
|
||||
this._renderer.emitIgnoredTryCatchBlock(() => {
|
||||
this._renderer.emitLine("return new SimpleDateFormat(format).parse(str);");
|
||||
});
|
||||
});
|
||||
this._renderer.emitLine("return null;");
|
||||
});
|
||||
|
||||
this._renderer.ensureBlankLine();
|
||||
this._renderer.emitBlock("public static String serializeDateTime(Date datetime)", () => {
|
||||
this._renderer.emitLine("return new SimpleDateFormat(\"yyyy-MM-dd'T'hh:mm:ssZ\").format(datetime);");
|
||||
});
|
||||
|
||||
this._renderer.ensureBlankLine();
|
||||
this._renderer.emitBlock("public static String serializeDate(Date datetime)", () => {
|
||||
this._renderer.emitLine('return new SimpleDateFormat("yyyy-MM-dd").format(datetime);');
|
||||
});
|
||||
|
||||
this._renderer.ensureBlankLine();
|
||||
this._renderer.emitBlock("public static String serializeTime(Date datetime)", () => {
|
||||
this._renderer.emitLine('return new SimpleDateFormat("hh:mm:ssZ").format(datetime);');
|
||||
});
|
||||
}
|
||||
|
||||
public convertStringToDateTime(variable: Sourcelike): Sourcelike {
|
||||
return [this._className, ".parseAllDateTimeString(", variable, ")"];
|
||||
}
|
||||
|
||||
public convertStringToTime(variable: Sourcelike): Sourcelike {
|
||||
return [this._className, ".parseAllDateTimeString(", variable, ")"];
|
||||
}
|
||||
|
||||
public convertStringToDate(variable: Sourcelike): Sourcelike {
|
||||
return [this._className, ".parseAllDateTimeString(", variable, ")"];
|
||||
}
|
||||
|
||||
public convertDateTimeToString(variable: Sourcelike): Sourcelike {
|
||||
return [this._className, ".serializeDateTime(", variable, ")"];
|
||||
}
|
||||
|
||||
public convertTimeToString(variable: Sourcelike): Sourcelike {
|
||||
return [this._className, ".serializeTime(", variable, ")"];
|
||||
}
|
||||
|
||||
public convertDateToString(variable: Sourcelike): Sourcelike {
|
||||
return [this._className, ".serializeDate(", variable, ")"];
|
||||
}
|
||||
}
|
||||
|
||||
export class JavaRenderer extends ConvenienceRenderer {
|
||||
private _currentFilename: string | undefined;
|
||||
|
||||
private readonly _gettersAndSettersForPropertyName = new Map<Name, [Name, Name]>();
|
||||
|
||||
private _haveEmittedLeadingComments = false;
|
||||
|
||||
protected readonly _dateTimeProvider: JavaDateTimeProvider;
|
||||
|
||||
protected readonly _converterClassname: string = "Converter";
|
||||
|
||||
protected readonly _converterKeywords: string[] = [];
|
||||
|
||||
public constructor(
|
||||
targetLanguage: TargetLanguage,
|
||||
renderContext: RenderContext,
|
||||
protected readonly _options: OptionValues<typeof javaOptions>
|
||||
) {
|
||||
super(targetLanguage, renderContext);
|
||||
|
||||
switch (_options.dateTimeProvider) {
|
||||
default:
|
||||
case "java8":
|
||||
this._dateTimeProvider = new Java8DateTimeProvider(this, this._converterClassname);
|
||||
break;
|
||||
case "legacy":
|
||||
this._dateTimeProvider = new JavaLegacyDateTimeProvider(this, this._converterClassname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected forbiddenNamesForGlobalNamespace(): string[] {
|
||||
const keywords = [
|
||||
...javaKeywords,
|
||||
...this._converterKeywords,
|
||||
this._converterClassname,
|
||||
...this._dateTimeProvider.keywords
|
||||
];
|
||||
return keywords;
|
||||
}
|
||||
|
||||
protected forbiddenForObjectProperties(_c: ClassType, _className: Name): ForbiddenWordsInfo {
|
||||
return { names: [], includeGlobalForbidden: true };
|
||||
}
|
||||
|
||||
protected makeNamedTypeNamer(): Namer {
|
||||
return this.getNameStyling("typeNamingFunction");
|
||||
}
|
||||
|
||||
protected namerForObjectProperty(): Namer {
|
||||
return this.getNameStyling("propertyNamingFunction");
|
||||
}
|
||||
|
||||
protected makeUnionMemberNamer(): Namer {
|
||||
return this.getNameStyling("propertyNamingFunction");
|
||||
}
|
||||
|
||||
protected makeEnumCaseNamer(): Namer {
|
||||
return this.getNameStyling("enumCaseNamingFunction");
|
||||
}
|
||||
|
||||
protected unionNeedsName(u: UnionType): boolean {
|
||||
return nullableFromUnion(u) === null;
|
||||
}
|
||||
|
||||
protected namedTypeToNameForTopLevel(type: Type): Type | undefined {
|
||||
// If the top-level type doesn't contain any classes or unions
|
||||
// we have to define a class just for the `FromJson` method, in
|
||||
// emitFromJsonForTopLevel.
|
||||
return directlyReachableSingleNamedType(type);
|
||||
}
|
||||
|
||||
protected makeNamesForPropertyGetterAndSetter(
|
||||
_c: ClassType,
|
||||
_className: Name,
|
||||
_p: ClassProperty,
|
||||
_jsonName: string,
|
||||
name: Name
|
||||
): [Name, Name] {
|
||||
const getterName = new DependencyName(
|
||||
this.getNameStyling("propertyNamingFunction"),
|
||||
name.order,
|
||||
lookup => `get_${lookup(name)}`
|
||||
);
|
||||
const setterName = new DependencyName(
|
||||
this.getNameStyling("propertyNamingFunction"),
|
||||
name.order,
|
||||
lookup => `set_${lookup(name)}`
|
||||
);
|
||||
return [getterName, setterName];
|
||||
}
|
||||
|
||||
protected makePropertyDependencyNames(
|
||||
c: ClassType,
|
||||
className: Name,
|
||||
p: ClassProperty,
|
||||
jsonName: string,
|
||||
name: Name
|
||||
): Name[] {
|
||||
const getterAndSetterNames = this.makeNamesForPropertyGetterAndSetter(c, className, p, jsonName, name);
|
||||
this._gettersAndSettersForPropertyName.set(name, getterAndSetterNames);
|
||||
return getterAndSetterNames;
|
||||
}
|
||||
|
||||
private getNameStyling(convention: string): Namer {
|
||||
const styling: { [key: string]: Namer } = {
|
||||
typeNamingFunction: funPrefixNamer("types", n =>
|
||||
javaNameStyle(true, false, n, acronymStyle(this._options.acronymStyle))
|
||||
),
|
||||
propertyNamingFunction: funPrefixNamer("properties", n =>
|
||||
javaNameStyle(false, false, n, acronymStyle(this._options.acronymStyle))
|
||||
),
|
||||
enumCaseNamingFunction: funPrefixNamer("enum-cases", n =>
|
||||
javaNameStyle(true, true, n, acronymStyle(this._options.acronymStyle))
|
||||
)
|
||||
};
|
||||
return styling[convention];
|
||||
}
|
||||
|
||||
protected fieldOrMethodName(methodName: string, topLevelName: Name): Sourcelike {
|
||||
if (this.topLevels.size === 1) {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
return [topLevelName, capitalize(methodName)];
|
||||
}
|
||||
|
||||
protected methodName(prefix: string, suffix: string, topLevelName: Name): Sourcelike {
|
||||
if (this.topLevels.size === 1) {
|
||||
return [prefix, suffix];
|
||||
}
|
||||
|
||||
return [prefix, topLevelName, suffix];
|
||||
}
|
||||
|
||||
protected decoderName(topLevelName: Name): Sourcelike {
|
||||
return this.fieldOrMethodName("fromJsonString", topLevelName);
|
||||
}
|
||||
|
||||
protected encoderName(topLevelName: Name): Sourcelike {
|
||||
return this.fieldOrMethodName("toJsonString", topLevelName);
|
||||
}
|
||||
|
||||
protected readerGetterName(topLevelName: Name): Sourcelike {
|
||||
return this.methodName("get", "ObjectReader", topLevelName);
|
||||
}
|
||||
|
||||
protected writerGetterName(topLevelName: Name): Sourcelike {
|
||||
return this.methodName("get", "ObjectWriter", topLevelName);
|
||||
}
|
||||
|
||||
protected startFile(basename: Sourcelike): void {
|
||||
assert(this._currentFilename === undefined, "Previous file wasn't finished");
|
||||
// FIXME: The filenames should actually be Sourcelikes, too
|
||||
this._currentFilename = `${this.sourcelikeToString(basename)}.java`;
|
||||
// FIXME: Why is this necessary?
|
||||
this.ensureBlankLine();
|
||||
if (!this._haveEmittedLeadingComments && this.leadingComments !== undefined) {
|
||||
this.emitComments(this.leadingComments);
|
||||
this.ensureBlankLine();
|
||||
this._haveEmittedLeadingComments = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected finishFile(): void {
|
||||
super.finishFile(defined(this._currentFilename));
|
||||
this._currentFilename = undefined;
|
||||
}
|
||||
|
||||
protected emitPackageAndImports(imports: string[]): void {
|
||||
this.emitLine("package ", this._options.packageName, ";");
|
||||
this.ensureBlankLine();
|
||||
for (const pkg of imports) {
|
||||
this.emitLine("import ", pkg, ";");
|
||||
}
|
||||
}
|
||||
|
||||
protected emitFileHeader(fileName: Sourcelike, imports: string[]): void {
|
||||
this.startFile(fileName);
|
||||
this.emitPackageAndImports(imports);
|
||||
this.ensureBlankLine();
|
||||
}
|
||||
|
||||
public emitDescriptionBlock(lines: Sourcelike[]): void {
|
||||
this.emitCommentLines(lines, { lineStart: " * ", beforeComment: "/**", afterComment: " */" });
|
||||
}
|
||||
|
||||
public emitBlock(line: Sourcelike, f: () => void): void {
|
||||
this.emitLine(line, " {");
|
||||
this.indent(f);
|
||||
this.emitLine("}");
|
||||
}
|
||||
|
||||
public emitTryCatch(main: () => void, handler: () => void, exception = "Exception"): void {
|
||||
this.emitLine("try {");
|
||||
this.indent(main);
|
||||
this.emitLine("} catch (", exception, " ex) {");
|
||||
this.indent(handler);
|
||||
this.emitLine("}");
|
||||
}
|
||||
|
||||
public emitIgnoredTryCatchBlock(f: () => void): void {
|
||||
this.emitTryCatch(f, () => this.emitLine("// Ignored"));
|
||||
}
|
||||
|
||||
protected javaType(reference: boolean, t: Type, withIssues = false): Sourcelike {
|
||||
return matchType<Sourcelike>(
|
||||
t,
|
||||
_anyType => maybeAnnotated(withIssues, anyTypeIssueAnnotation, "Object"),
|
||||
_nullType => maybeAnnotated(withIssues, nullTypeIssueAnnotation, "Object"),
|
||||
_boolType => (reference ? "Boolean" : "boolean"),
|
||||
_integerType => (reference ? "Long" : "long"),
|
||||
_doubleType => (reference ? "Double" : "double"),
|
||||
_stringType => "String",
|
||||
arrayType => {
|
||||
if (this._options.useList) {
|
||||
return ["List<", this.javaType(true, arrayType.items, withIssues), ">"];
|
||||
} else {
|
||||
return [this.javaType(false, arrayType.items, withIssues), "[]"];
|
||||
}
|
||||
},
|
||||
classType => this.nameForNamedType(classType),
|
||||
mapType => ["Map<String, ", this.javaType(true, mapType.values, withIssues), ">"],
|
||||
enumType => this.nameForNamedType(enumType),
|
||||
unionType => {
|
||||
const nullable = nullableFromUnion(unionType);
|
||||
if (nullable !== null) return this.javaType(true, nullable, withIssues);
|
||||
return this.nameForNamedType(unionType);
|
||||
},
|
||||
transformedStringType => {
|
||||
if (transformedStringType.kind === "time") {
|
||||
return this._dateTimeProvider.timeType;
|
||||
}
|
||||
|
||||
if (transformedStringType.kind === "date") {
|
||||
return this._dateTimeProvider.dateType;
|
||||
}
|
||||
|
||||
if (transformedStringType.kind === "date-time") {
|
||||
return this._dateTimeProvider.dateTimeType;
|
||||
}
|
||||
|
||||
if (transformedStringType.kind === "uuid") {
|
||||
return "UUID";
|
||||
}
|
||||
|
||||
return "String";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected javaImport(t: Type): string[] {
|
||||
return matchType<string[]>(
|
||||
t,
|
||||
_anyType => [],
|
||||
_nullType => [],
|
||||
_boolType => [],
|
||||
_integerType => [],
|
||||
_doubleType => [],
|
||||
_stringType => [],
|
||||
arrayType => {
|
||||
if (this._options.useList) {
|
||||
return [...this.javaImport(arrayType.items), "java.util.List"];
|
||||
} else {
|
||||
return [...this.javaImport(arrayType.items)];
|
||||
}
|
||||
},
|
||||
_classType => [],
|
||||
mapType => [...this.javaImport(mapType.values), "java.util.Map"],
|
||||
_enumType => [],
|
||||
unionType => {
|
||||
const imports: string[] = [];
|
||||
unionType.members.forEach(type => this.javaImport(type).forEach(imp => imports.push(imp)));
|
||||
return imports;
|
||||
},
|
||||
transformedStringType => {
|
||||
if (transformedStringType.kind === "time") {
|
||||
return this._dateTimeProvider.timeImports;
|
||||
}
|
||||
|
||||
if (transformedStringType.kind === "date") {
|
||||
return this._dateTimeProvider.dateImports;
|
||||
}
|
||||
|
||||
if (transformedStringType.kind === "date-time") {
|
||||
return this._dateTimeProvider.dateTimeImports;
|
||||
}
|
||||
|
||||
if (transformedStringType.kind === "uuid") {
|
||||
return ["java.util.UUID"];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected javaTypeWithoutGenerics(reference: boolean, t: Type): Sourcelike {
|
||||
if (t instanceof ArrayType) {
|
||||
if (this._options.useList) {
|
||||
return ["List"];
|
||||
} else {
|
||||
return [this.javaTypeWithoutGenerics(false, t.items), "[]"];
|
||||
}
|
||||
} else if (t instanceof MapType) {
|
||||
return "Map";
|
||||
} else if (t instanceof UnionType) {
|
||||
const nullable = nullableFromUnion(t);
|
||||
if (nullable !== null) return this.javaTypeWithoutGenerics(true, nullable);
|
||||
return this.nameForNamedType(t);
|
||||
} else {
|
||||
return this.javaType(reference, t);
|
||||
}
|
||||
}
|
||||
|
||||
protected emitClassAttributes(_c: ClassType, _className: Name): void {
|
||||
if (this._options.lombok) {
|
||||
this.emitLine("@lombok.Data");
|
||||
}
|
||||
}
|
||||
|
||||
protected annotationsForAccessor(
|
||||
_c: ClassType,
|
||||
_className: Name,
|
||||
_propertyName: Name,
|
||||
_jsonName: string,
|
||||
_p: ClassProperty,
|
||||
_isSetter: boolean
|
||||
): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected importsForType(t: ClassType | UnionType | EnumType): string[] {
|
||||
if (t instanceof ClassType) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (t instanceof UnionType) {
|
||||
return ["java.io.IOException"];
|
||||
}
|
||||
|
||||
if (t instanceof EnumType) {
|
||||
return ["java.io.IOException"];
|
||||
}
|
||||
|
||||
return assertNever(t);
|
||||
}
|
||||
|
||||
protected importsForClass(c: ClassType): string[] {
|
||||
const imports: string[] = [];
|
||||
this.forEachClassProperty(c, "none", (_name, _jsonName, p) => {
|
||||
this.javaImport(p.type).forEach(imp => imports.push(imp));
|
||||
});
|
||||
imports.sort();
|
||||
return [...new Set(imports)];
|
||||
}
|
||||
|
||||
protected importsForUnionMembers(u: UnionType): string[] {
|
||||
const imports: string[] = [];
|
||||
const [, nonNulls] = removeNullFromUnion(u);
|
||||
this.forEachUnionMember(u, nonNulls, "none", null, (_fieldName, t) => {
|
||||
this.javaImport(t).forEach(imp => imports.push(imp));
|
||||
});
|
||||
imports.sort();
|
||||
return [...new Set(imports)];
|
||||
}
|
||||
|
||||
protected emitClassDefinition(c: ClassType, className: Name): void {
|
||||
let imports = [...this.importsForType(c), ...this.importsForClass(c)];
|
||||
|
||||
this.emitFileHeader(className, imports);
|
||||
this.emitDescription(this.descriptionForType(c));
|
||||
this.emitClassAttributes(c, className);
|
||||
this.emitBlock(["public class ", className], () => {
|
||||
this.forEachClassProperty(c, "none", (name, jsonName, p) => {
|
||||
if (this._options.lombok && this._options.lombokCopyAnnotations) {
|
||||
const getter = this.annotationsForAccessor(c, className, name, jsonName, p, false);
|
||||
const setter = this.annotationsForAccessor(c, className, name, jsonName, p, true);
|
||||
if (getter.length !== 0) {
|
||||
this.emitLine("@lombok.Getter(onMethod_ = {" + getter.join(", ") + "})");
|
||||
}
|
||||
|
||||
if (setter.length !== 0) {
|
||||
this.emitLine("@lombok.Setter(onMethod_ = {" + setter.join(", ") + "})");
|
||||
}
|
||||
}
|
||||
|
||||
this.emitLine("private ", this.javaType(false, p.type, true), " ", name, ";");
|
||||
});
|
||||
if (!this._options.lombok) {
|
||||
this.forEachClassProperty(c, "leading-and-interposing", (name, jsonName, p) => {
|
||||
this.emitDescription(this.descriptionForClassProperty(c, jsonName));
|
||||
const [getterName, setterName] = defined(this._gettersAndSettersForPropertyName.get(name));
|
||||
const rendered = this.javaType(false, p.type);
|
||||
this.annotationsForAccessor(c, className, name, jsonName, p, false).forEach(annotation =>
|
||||
this.emitLine(annotation)
|
||||
);
|
||||
this.emitLine("public ", rendered, " ", getterName, "() { return ", name, "; }");
|
||||
this.annotationsForAccessor(c, className, name, jsonName, p, true).forEach(annotation =>
|
||||
this.emitLine(annotation)
|
||||
);
|
||||
this.emitLine("public void ", setterName, "(", rendered, " value) { this.", name, " = value; }");
|
||||
});
|
||||
}
|
||||
});
|
||||
this.finishFile();
|
||||
}
|
||||
|
||||
protected unionField(u: UnionType, t: Type, withIssues = false): { fieldName: Sourcelike; fieldType: Sourcelike } {
|
||||
const fieldType = this.javaType(true, t, withIssues);
|
||||
// FIXME: "Value" should be part of the name.
|
||||
const fieldName = [this.nameForUnionMember(u, t), "Value"];
|
||||
return { fieldType, fieldName };
|
||||
}
|
||||
|
||||
protected emitUnionAttributes(_u: UnionType, _unionName: Name): void {
|
||||
// empty
|
||||
}
|
||||
|
||||
protected emitUnionSerializer(_u: UnionType, _unionName: Name): void {
|
||||
// empty
|
||||
}
|
||||
|
||||
protected emitUnionDefinition(u: UnionType, unionName: Name): void {
|
||||
const imports = [...this.importsForType(u), ...this.importsForUnionMembers(u)];
|
||||
|
||||
this.emitFileHeader(unionName, imports);
|
||||
this.emitDescription(this.descriptionForType(u));
|
||||
const [, nonNulls] = removeNullFromUnion(u);
|
||||
|
||||
this.emitUnionAttributes(u, unionName);
|
||||
this.emitBlock(["public class ", unionName], () => {
|
||||
for (const t of nonNulls) {
|
||||
const { fieldType, fieldName } = this.unionField(u, t, true);
|
||||
this.emitLine("public ", fieldType, " ", fieldName, ";");
|
||||
}
|
||||
|
||||
this.emitUnionSerializer(u, unionName);
|
||||
});
|
||||
this.finishFile();
|
||||
}
|
||||
|
||||
protected emitEnumSerializationAttributes(_e: EnumType): void {
|
||||
// Empty
|
||||
}
|
||||
|
||||
protected emitEnumDeserializationAttributes(_e: EnumType): void {
|
||||
// Empty
|
||||
}
|
||||
|
||||
protected emitEnumDefinition(e: EnumType, enumName: Name): void {
|
||||
this.emitFileHeader(enumName, this.importsForType(e));
|
||||
this.emitDescription(this.descriptionForType(e));
|
||||
const caseNames: Sourcelike[] = [];
|
||||
this.forEachEnumCase(e, "none", name => {
|
||||
if (caseNames.length > 0) caseNames.push(", ");
|
||||
caseNames.push(name);
|
||||
});
|
||||
caseNames.push(";");
|
||||
this.emitBlock(["public enum ", enumName], () => {
|
||||
this.emitLine(caseNames);
|
||||
this.ensureBlankLine();
|
||||
|
||||
this.emitEnumSerializationAttributes(e);
|
||||
this.emitBlock("public String toValue()", () => {
|
||||
this.emitLine("switch (this) {");
|
||||
this.indent(() => {
|
||||
this.forEachEnumCase(e, "none", (name, jsonName) => {
|
||||
this.emitLine("case ", name, ': return "', stringEscape(jsonName), '";');
|
||||
});
|
||||
});
|
||||
this.emitLine("}");
|
||||
this.emitLine("return null;");
|
||||
});
|
||||
this.ensureBlankLine();
|
||||
|
||||
this.emitEnumDeserializationAttributes(e);
|
||||
this.emitBlock(["public static ", enumName, " forValue(String value) throws IOException"], () => {
|
||||
this.forEachEnumCase(e, "none", (name, jsonName) => {
|
||||
this.emitLine('if (value.equals("', stringEscape(jsonName), '")) return ', name, ";");
|
||||
});
|
||||
this.emitLine('throw new IOException("Cannot deserialize ', enumName, '");');
|
||||
});
|
||||
});
|
||||
this.finishFile();
|
||||
}
|
||||
|
||||
protected emitSourceStructure(): void {
|
||||
this.forEachNamedType(
|
||||
"leading-and-interposing",
|
||||
(c: ClassType, n: Name) => this.emitClassDefinition(c, n),
|
||||
(e, n) => this.emitEnumDefinition(e, n),
|
||||
(u, n) => this.emitUnionDefinition(u, n)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class JacksonRenderer extends JavaRenderer {
|
||||
public constructor(
|
||||
targetLanguage: TargetLanguage,
|
||||
renderContext: RenderContext,
|
||||
options: OptionValues<typeof javaOptions>
|
||||
) {
|
||||
super(targetLanguage, renderContext, options);
|
||||
}
|
||||
|
||||
protected readonly _converterKeywords: string[] = [
|
||||
"JsonProperty",
|
||||
"JsonDeserialize",
|
||||
"JsonDeserializer",
|
||||
"JsonSerialize",
|
||||
"JsonSerializer",
|
||||
"JsonParser",
|
||||
"JsonProcessingException",
|
||||
"DeserializationContext",
|
||||
"SerializerProvider"
|
||||
];
|
||||
|
||||
protected emitClassAttributes(c: ClassType, _className: Name): void {
|
||||
if (c.getProperties().size === 0)
|
||||
this.emitLine("@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.NONE)");
|
||||
|
||||
super.emitClassAttributes(c, _className);
|
||||
}
|
||||
|
||||
protected annotationsForAccessor(
|
||||
_c: ClassType,
|
||||
_className: Name,
|
||||
_propertyName: Name,
|
||||
jsonName: string,
|
||||
p: ClassProperty,
|
||||
_isSetter: boolean
|
||||
): string[] {
|
||||
const superAnnotations = super.annotationsForAccessor(_c, _className, _propertyName, jsonName, p, _isSetter);
|
||||
|
||||
const annotations: string[] = ['@JsonProperty("' + stringEscape(jsonName) + '")'];
|
||||
|
||||
switch (p.type.kind) {
|
||||
case "date-time":
|
||||
this._dateTimeProvider.dateTimeJacksonAnnotations.forEach(annotation => annotations.push(annotation));
|
||||
break;
|
||||
case "date":
|
||||
this._dateTimeProvider.dateJacksonAnnotations.forEach(annotation => annotations.push(annotation));
|
||||
break;
|
||||
case "time":
|
||||
this._dateTimeProvider.timeJacksonAnnotations.forEach(annotation => annotations.push(annotation));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return [...superAnnotations, ...annotations];
|
||||
}
|
||||
|
||||
protected importsForType(t: ClassType | UnionType | EnumType): string[] {
|
||||
if (t instanceof ClassType) {
|
||||
const imports = super.importsForType(t);
|
||||
imports.push("com.fasterxml.jackson.annotation.*");
|
||||
return imports;
|
||||
}
|
||||
|
||||
if (t instanceof UnionType) {
|
||||
const imports = super.importsForType(t);
|
||||
imports.push(
|
||||
"java.io.IOException",
|
||||
"com.fasterxml.jackson.core.*",
|
||||
"com.fasterxml.jackson.databind.*",
|
||||
"com.fasterxml.jackson.databind.annotation.*"
|
||||
);
|
||||
if (this._options.useList) {
|
||||
imports.push("com.fasterxml.jackson.core.type.*");
|
||||
}
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
if (t instanceof EnumType) {
|
||||
const imports = super.importsForType(t);
|
||||
imports.push("com.fasterxml.jackson.annotation.*");
|
||||
return imports;
|
||||
}
|
||||
|
||||
return assertNever(t);
|
||||
}
|
||||
|
||||
protected emitUnionAttributes(_u: UnionType, unionName: Name): void {
|
||||
this.emitLine("@JsonDeserialize(using = ", unionName, ".Deserializer.class)");
|
||||
this.emitLine("@JsonSerialize(using = ", unionName, ".Serializer.class)");
|
||||
}
|
||||
|
||||
protected emitUnionSerializer(u: UnionType, unionName: Name): void {
|
||||
const stringBasedObjects: TypeKind[] = ["uuid", "time", "date", "date-time"];
|
||||
|
||||
const tokenCase = (tokenType: string): void => {
|
||||
this.emitLine("case ", tokenType, ":");
|
||||
};
|
||||
|
||||
const emitNullDeserializer = (): void => {
|
||||
this.indent(() => {
|
||||
tokenCase("VALUE_NULL");
|
||||
this.indent(() => this.emitLine("break;"));
|
||||
});
|
||||
};
|
||||
|
||||
const emitDeserializerCodeForStringObjects = (
|
||||
fieldName: Sourcelike,
|
||||
kind: TypeKind,
|
||||
parseFrom: string
|
||||
): void => {
|
||||
switch (kind) {
|
||||
case "date":
|
||||
this.emitLine(
|
||||
"value.",
|
||||
fieldName,
|
||||
" = ",
|
||||
this._dateTimeProvider.convertStringToDate(parseFrom),
|
||||
";"
|
||||
);
|
||||
|
||||
break;
|
||||
case "time":
|
||||
this.emitLine(
|
||||
"value.",
|
||||
fieldName,
|
||||
" = ",
|
||||
this._dateTimeProvider.convertStringToTime(parseFrom),
|
||||
";"
|
||||
);
|
||||
|
||||
break;
|
||||
case "date-time":
|
||||
this.emitLine(
|
||||
"value.",
|
||||
fieldName,
|
||||
" = ",
|
||||
this._dateTimeProvider.convertStringToDateTime(parseFrom),
|
||||
";"
|
||||
);
|
||||
break;
|
||||
case "uuid":
|
||||
this.emitLine("value.", fieldName, " = UUID.fromString(", parseFrom, ");");
|
||||
|
||||
break;
|
||||
default:
|
||||
return panic("Requested type isnt an object!");
|
||||
}
|
||||
};
|
||||
|
||||
const emitDeserializeType = (t: Type, variableFieldName = ""): void => {
|
||||
const { fieldName } = this.unionField(u, t);
|
||||
const rendered = this.javaTypeWithoutGenerics(true, t);
|
||||
if (this._options.useList && t instanceof ArrayType) {
|
||||
this.emitLine(
|
||||
"value.",
|
||||
fieldName,
|
||||
" = jsonParser.readValueAs(new TypeReference<",
|
||||
rendered,
|
||||
">() {});"
|
||||
);
|
||||
} else if (stringBasedObjects.some(stringBasedTypeKind => t.kind === stringBasedTypeKind)) {
|
||||
emitDeserializerCodeForStringObjects(fieldName, t.kind, variableFieldName);
|
||||
} else if (t.kind === "string") {
|
||||
this.emitLine("value.", fieldName, " = ", variableFieldName, ";");
|
||||
} else if (t.kind === "enum") {
|
||||
const { fieldType } = this.unionField(u, t, true);
|
||||
this.emitLine("value.", fieldName, " = ", fieldType, ".forValue(", variableFieldName, ");");
|
||||
} else {
|
||||
this.emitLine("value.", fieldName, " = jsonParser.readValueAs(", rendered, ".class);");
|
||||
}
|
||||
};
|
||||
|
||||
const emitDeserializer = (tokenTypes: string[], kind: TypeKind): void => {
|
||||
const t = u.findMember(kind);
|
||||
if (t === undefined) return;
|
||||
|
||||
this.indent(() => {
|
||||
for (const tokenType of tokenTypes) {
|
||||
tokenCase(tokenType);
|
||||
}
|
||||
|
||||
this.indent(() => {
|
||||
emitDeserializeType(t);
|
||||
this.emitLine("break;");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const emitStringDeserializer = (): void => {
|
||||
const enumType = u.findMember("enum");
|
||||
const stringType = u.findMember("string");
|
||||
|
||||
if (
|
||||
stringBasedObjects.every(kind => u.findMember(kind) === undefined) &&
|
||||
stringType === undefined &&
|
||||
enumType === undefined
|
||||
)
|
||||
return;
|
||||
|
||||
this.indent(() => {
|
||||
tokenCase("VALUE_STRING");
|
||||
|
||||
this.indent(() => {
|
||||
const fromVariable = "string";
|
||||
this.emitLine("String " + fromVariable + " = jsonParser.readValueAs(String.class);");
|
||||
|
||||
stringBasedObjects.forEach(kind => {
|
||||
const type = u.findMember(kind);
|
||||
if (type !== undefined) {
|
||||
this.emitIgnoredTryCatchBlock(() => {
|
||||
emitDeserializeType(type, fromVariable);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (enumType !== undefined) {
|
||||
this.emitIgnoredTryCatchBlock(() => {
|
||||
emitDeserializeType(enumType, fromVariable);
|
||||
});
|
||||
}
|
||||
|
||||
// String should be the last one if exists, because it cannot fail, unlike the parsers.
|
||||
if (stringType !== undefined) {
|
||||
emitDeserializeType(stringType, fromVariable);
|
||||
}
|
||||
|
||||
this.emitLine("break;");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const emitNumberDeserializer = (): void => {
|
||||
const integerType = u.findMember("integer");
|
||||
const doubleType = u.findMember("double");
|
||||
if (doubleType === undefined && integerType === undefined) return;
|
||||
|
||||
this.indent(() => {
|
||||
tokenCase("VALUE_NUMBER_INT");
|
||||
if (integerType !== undefined) {
|
||||
this.indent(() => {
|
||||
emitDeserializeType(integerType);
|
||||
this.emitLine("break;");
|
||||
});
|
||||
}
|
||||
|
||||
if (doubleType !== undefined) {
|
||||
tokenCase("VALUE_NUMBER_FLOAT");
|
||||
this.indent(() => {
|
||||
emitDeserializeType(doubleType);
|
||||
this.emitLine("break;");
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const customObjectSerializer: TypeKind[] = ["time", "date", "date-time"];
|
||||
|
||||
const serializerCodeForType = (type: Type, fieldName: Sourcelike): Sourcelike => {
|
||||
switch (type.kind) {
|
||||
case "date":
|
||||
return this._dateTimeProvider.convertDateToString(fieldName);
|
||||
case "time":
|
||||
return this._dateTimeProvider.convertTimeToString(fieldName);
|
||||
case "date-time":
|
||||
return this._dateTimeProvider.convertDateTimeToString(fieldName);
|
||||
default:
|
||||
return panic("Requested type doesn't have custom serializer code!");
|
||||
}
|
||||
};
|
||||
|
||||
const emitSerializeType = (t: Type): void => {
|
||||
let { fieldName } = this.unionField(u, t, true);
|
||||
|
||||
this.emitBlock(["if (obj.", fieldName, " != null)"], () => {
|
||||
if (customObjectSerializer.some(customSerializerType => t.kind === customSerializerType)) {
|
||||
this.emitLine("jsonGenerator.writeObject(", serializerCodeForType(t, ["obj.", fieldName]), ");");
|
||||
} else {
|
||||
this.emitLine("jsonGenerator.writeObject(obj.", fieldName, ");");
|
||||
}
|
||||
|
||||
this.emitLine("return;");
|
||||
});
|
||||
};
|
||||
|
||||
const [maybeNull, nonNulls] = removeNullFromUnion(u);
|
||||
|
||||
this.ensureBlankLine();
|
||||
this.emitBlock(["static class Deserializer extends JsonDeserializer<", unionName, ">"], () => {
|
||||
this.emitLine("@Override");
|
||||
this.emitBlock(
|
||||
[
|
||||
"public ",
|
||||
unionName,
|
||||
" deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException"
|
||||
],
|
||||
() => {
|
||||
this.emitLine(unionName, " value = new ", unionName, "();");
|
||||
this.emitLine("switch (jsonParser.currentToken()) {");
|
||||
if (maybeNull !== null) emitNullDeserializer();
|
||||
emitNumberDeserializer();
|
||||
emitDeserializer(["VALUE_TRUE", "VALUE_FALSE"], "bool");
|
||||
emitStringDeserializer();
|
||||
emitDeserializer(["START_ARRAY"], "array");
|
||||
emitDeserializer(["START_OBJECT"], "class");
|
||||
emitDeserializer(["START_OBJECT"], "map");
|
||||
this.indent(() =>
|
||||
this.emitLine('default: throw new IOException("Cannot deserialize ', unionName, '");')
|
||||
);
|
||||
this.emitLine("}");
|
||||
this.emitLine("return value;");
|
||||
}
|
||||
);
|
||||
});
|
||||
this.ensureBlankLine();
|
||||
this.emitBlock(["static class Serializer extends JsonSerializer<", unionName, ">"], () => {
|
||||
this.emitLine("@Override");
|
||||
this.emitBlock(
|
||||
[
|
||||
"public void serialize(",
|
||||
unionName,
|
||||
" obj, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException"
|
||||
],
|
||||
() => {
|
||||
for (const t of nonNulls) {
|
||||
emitSerializeType(t);
|
||||
}
|
||||
|
||||
if (maybeNull !== null) {
|
||||
this.emitLine("jsonGenerator.writeNull();");
|
||||
} else {
|
||||
this.emitLine('throw new IOException("', unionName, ' must not be null");');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
protected emitEnumSerializationAttributes(_e: EnumType): void {
|
||||
this.emitLine("@JsonValue");
|
||||
}
|
||||
|
||||
protected emitEnumDeserializationAttributes(_e: EnumType): void {
|
||||
this.emitLine("@JsonCreator");
|
||||
}
|
||||
|
||||
protected emitOffsetDateTimeConverterModule(): void {
|
||||
this.emitLine("SimpleModule module = new SimpleModule();");
|
||||
|
||||
if (this._dateTimeProvider.shouldEmitDateTimeConverter) {
|
||||
this.emitLine(
|
||||
"module.addDeserializer(",
|
||||
this._dateTimeProvider.dateTimeType,
|
||||
".class, new JsonDeserializer<",
|
||||
this._dateTimeProvider.dateTimeType,
|
||||
">() {"
|
||||
);
|
||||
this.indent(() => {
|
||||
this.emitLine("@Override");
|
||||
this.emitBlock(
|
||||
[
|
||||
"public ",
|
||||
this._dateTimeProvider.dateTimeType,
|
||||
" deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) ",
|
||||
"throws IOException, JsonProcessingException"
|
||||
],
|
||||
() => {
|
||||
this.emitLine("String value = jsonParser.getText();");
|
||||
this.emitLine("return ", this._dateTimeProvider.convertStringToDateTime("value"), ";");
|
||||
}
|
||||
);
|
||||
});
|
||||
this.emitLine("});");
|
||||
}
|
||||
|
||||
if (!this._dateTimeProvider.shouldEmitTimeConverter) {
|
||||
this.emitLine(
|
||||
"module.addDeserializer(",
|
||||
this._dateTimeProvider.timeType,
|
||||
".class, new JsonDeserializer<",
|
||||
this._dateTimeProvider.timeType,
|
||||
">() {"
|
||||
);
|
||||
this.indent(() => {
|
||||
this.emitLine("@Override");
|
||||
this.emitBlock(
|
||||
[
|
||||
"public ",
|
||||
this._dateTimeProvider.timeType,
|
||||
" deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) ",
|
||||
"throws IOException, JsonProcessingException"
|
||||
],
|
||||
() => {
|
||||
this.emitLine("String value = jsonParser.getText();");
|
||||
this.emitLine("return ", this._dateTimeProvider.convertStringToTime("value"), ";");
|
||||
}
|
||||
);
|
||||
});
|
||||
this.emitLine("});");
|
||||
}
|
||||
|
||||
if (!this._dateTimeProvider.shouldEmitDateConverter) {
|
||||
this.emitLine(
|
||||
"module.addDeserializer(",
|
||||
this._dateTimeProvider.dateType,
|
||||
".class, new JsonDeserializer<",
|
||||
this._dateTimeProvider.dateType,
|
||||
">() {"
|
||||
);
|
||||
this.indent(() => {
|
||||
this.emitLine("@Override");
|
||||
this.emitBlock(
|
||||
[
|
||||
"public ",
|
||||
this._dateTimeProvider.dateType,
|
||||
" deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) ",
|
||||
"throws IOException, JsonProcessingException"
|
||||
],
|
||||
() => {
|
||||
this.emitLine("String value = jsonParser.getText();");
|
||||
this.emitLine("return ", this._dateTimeProvider.convertStringToDate("value"), ";");
|
||||
}
|
||||
);
|
||||
});
|
||||
this.emitLine("});");
|
||||
}
|
||||
|
||||
this.emitLine("mapper.registerModule(module);");
|
||||
}
|
||||
|
||||
protected emitConverterClass(): void {
|
||||
this.startFile(this._converterClassname);
|
||||
this.emitCommentLines([
|
||||
"To use this code, add the following Maven dependency to your project:",
|
||||
"",
|
||||
this._options.lombok ? " org.projectlombok : lombok : 1.18.2" : "",
|
||||
" com.fasterxml.jackson.core : jackson-databind : 2.9.0",
|
||||
this._options.dateTimeProvider === "java8"
|
||||
? " com.fasterxml.jackson.datatype : jackson-datatype-jsr310 : 2.9.0"
|
||||
: "",
|
||||
"",
|
||||
"Import this package:",
|
||||
""
|
||||
]);
|
||||
this.emitLine("// import ", this._options.packageName, ".Converter;");
|
||||
this.emitMultiline(`//
|
||||
// Then you can deserialize a JSON string with
|
||||
//`);
|
||||
this.forEachTopLevel("none", (t, name) => {
|
||||
this.emitLine(
|
||||
"// ",
|
||||
this.javaType(false, t),
|
||||
" data = Converter.",
|
||||
this.decoderName(name),
|
||||
"(jsonString);"
|
||||
);
|
||||
});
|
||||
this.ensureBlankLine();
|
||||
const imports = [
|
||||
"java.io.IOException",
|
||||
"com.fasterxml.jackson.databind.*",
|
||||
"com.fasterxml.jackson.databind.module.SimpleModule",
|
||||
"com.fasterxml.jackson.core.JsonParser",
|
||||
"com.fasterxml.jackson.core.JsonProcessingException",
|
||||
"java.util.*"
|
||||
].concat(this._dateTimeProvider.converterImports);
|
||||
this.emitPackageAndImports(imports);
|
||||
this.ensureBlankLine();
|
||||
this.emitBlock(["public class Converter"], () => {
|
||||
this.emitLine("// Date-time helpers");
|
||||
this._dateTimeProvider.emitDateTimeConverters();
|
||||
|
||||
this.emitLine("// Serialize/deserialize helpers");
|
||||
this.forEachTopLevel("leading-and-interposing", (topLevelType, topLevelName) => {
|
||||
const topLevelTypeRendered = this.javaType(false, topLevelType);
|
||||
this.emitBlock(
|
||||
[
|
||||
"public static ",
|
||||
topLevelTypeRendered,
|
||||
" ",
|
||||
this.decoderName(topLevelName),
|
||||
"(String json) throws IOException"
|
||||
],
|
||||
() => {
|
||||
this.emitLine("return ", this.readerGetterName(topLevelName), "().readValue(json);");
|
||||
}
|
||||
);
|
||||
this.ensureBlankLine();
|
||||
this.emitBlock(
|
||||
[
|
||||
"public static String ",
|
||||
this.encoderName(topLevelName),
|
||||
"(",
|
||||
topLevelTypeRendered,
|
||||
" obj) throws JsonProcessingException"
|
||||
],
|
||||
() => {
|
||||
this.emitLine("return ", this.writerGetterName(topLevelName), "().writeValueAsString(obj);");
|
||||
}
|
||||
);
|
||||
});
|
||||
this.forEachTopLevel("leading-and-interposing", (topLevelType, topLevelName) => {
|
||||
const readerName = this.fieldOrMethodName("reader", topLevelName);
|
||||
const writerName = this.fieldOrMethodName("writer", topLevelName);
|
||||
this.emitLine("private static ObjectReader ", readerName, ";");
|
||||
this.emitLine("private static ObjectWriter ", writerName, ";");
|
||||
this.ensureBlankLine();
|
||||
this.emitBlock(
|
||||
["private static void ", this.methodName("instantiate", "Mapper", topLevelName), "()"],
|
||||
() => {
|
||||
const renderedForClass = this.javaTypeWithoutGenerics(false, topLevelType);
|
||||
this.emitLine("ObjectMapper mapper = new ObjectMapper();");
|
||||
this.emitLine("mapper.findAndRegisterModules();");
|
||||
this.emitLine("mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);");
|
||||
this.emitLine("mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);");
|
||||
this.emitOffsetDateTimeConverterModule();
|
||||
this.emitLine(readerName, " = mapper.readerFor(", renderedForClass, ".class);");
|
||||
this.emitLine(writerName, " = mapper.writerFor(", renderedForClass, ".class);");
|
||||
}
|
||||
);
|
||||
this.ensureBlankLine();
|
||||
this.emitBlock(["private static ObjectReader ", this.readerGetterName(topLevelName), "()"], () => {
|
||||
this.emitLine(
|
||||
"if (",
|
||||
readerName,
|
||||
" == null) ",
|
||||
this.methodName("instantiate", "Mapper", topLevelName),
|
||||
"();"
|
||||
);
|
||||
this.emitLine("return ", readerName, ";");
|
||||
});
|
||||
this.ensureBlankLine();
|
||||
this.emitBlock(["private static ObjectWriter ", this.writerGetterName(topLevelName), "()"], () => {
|
||||
this.emitLine(
|
||||
"if (",
|
||||
writerName,
|
||||
" == null) ",
|
||||
this.methodName("instantiate", "Mapper", topLevelName),
|
||||
"();"
|
||||
);
|
||||
this.emitLine("return ", writerName, ";");
|
||||
});
|
||||
});
|
||||
});
|
||||
this.finishFile();
|
||||
}
|
||||
|
||||
protected emitSourceStructure(): void {
|
||||
this.emitConverterClass();
|
||||
super.emitSourceStructure();
|
||||
}
|
||||
}
|
@ -1,32 +1,21 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { AcronymStyleOptions, acronymOption } from "../../support/Acronyms";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { JacksonRenderer } from "./JavaJacksonRenderer";
|
||||
import { JavaRenderer } from "./JavaRenderer";
|
||||
|
||||
export const javaOptions = {
|
||||
useList: new EnumOption(
|
||||
"array-type",
|
||||
"Use T[] or List<T>",
|
||||
[
|
||||
["array", false],
|
||||
["list", true]
|
||||
],
|
||||
"array"
|
||||
),
|
||||
useList: new EnumOption("array-type", "Use T[] or List<T>", { array: false, list: true } as const, "array"),
|
||||
justTypes: new BooleanOption("just-types", "Plain types only", false),
|
||||
dateTimeProvider: new EnumOption(
|
||||
"datetime-provider",
|
||||
"Date time provider type",
|
||||
[
|
||||
["java8", "java8"],
|
||||
["legacy", "legacy"]
|
||||
],
|
||||
{ java8: "java8", legacy: "legacy" } as const,
|
||||
"java8"
|
||||
),
|
||||
acronymStyle: acronymOption(AcronymStyleOptions.Pascal),
|
||||
@ -36,21 +25,19 @@ export const javaOptions = {
|
||||
lombokCopyAnnotations: new BooleanOption("lombok-copy-annotations", "Copy accessor annotations", true, "secondary")
|
||||
};
|
||||
|
||||
export class JavaTargetLanguage extends TargetLanguage {
|
||||
export const javaLanguageConfig = {
|
||||
displayName: "Java",
|
||||
names: ["java"],
|
||||
extension: "java"
|
||||
} as const;
|
||||
|
||||
export class JavaTargetLanguage extends TargetLanguage<typeof javaLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Java", ["java"], "java");
|
||||
super(javaLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
javaOptions.useList,
|
||||
javaOptions.justTypes,
|
||||
javaOptions.dateTimeProvider,
|
||||
javaOptions.acronymStyle,
|
||||
javaOptions.packageName,
|
||||
javaOptions.lombok,
|
||||
javaOptions.lombokCopyAnnotations
|
||||
];
|
||||
public getOptions(): typeof javaOptions {
|
||||
return javaOptions;
|
||||
}
|
||||
|
||||
public get supportsUnionsWithBothNumberTypes(): boolean {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, getOptionValues } from "../../RendererOptions";
|
||||
import { AcronymStyleOptions, acronymOption } from "../../support/Acronyms";
|
||||
import { convertersOption } from "../../support/Converters";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { JavaScriptRenderer } from "./JavaScriptRenderer";
|
||||
|
||||
@ -19,31 +19,31 @@ export const javaScriptOptions = {
|
||||
"secondary"
|
||||
),
|
||||
converters: convertersOption(),
|
||||
rawType: new EnumOption<"json" | "any">(
|
||||
rawType: new EnumOption(
|
||||
"raw-type",
|
||||
"Type of raw input (json by default)",
|
||||
[
|
||||
["json", "json"],
|
||||
["any", "any"]
|
||||
],
|
||||
{
|
||||
json: "json",
|
||||
any: "any"
|
||||
} as const,
|
||||
"json",
|
||||
"secondary"
|
||||
)
|
||||
};
|
||||
|
||||
export class JavaScriptTargetLanguage extends TargetLanguage {
|
||||
public constructor(displayName = "JavaScript", names: string[] = ["javascript", "js", "jsx"], extension = "js") {
|
||||
super(displayName, names, extension);
|
||||
export const javaScriptLanguageConfig = {
|
||||
displayName: "JavaScript",
|
||||
names: ["javascript", "js", "jsx"],
|
||||
extension: "js"
|
||||
} as const;
|
||||
|
||||
export class JavaScriptTargetLanguage extends TargetLanguage<typeof javaScriptLanguageConfig> {
|
||||
public constructor() {
|
||||
super(javaScriptLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
javaScriptOptions.runtimeTypecheck,
|
||||
javaScriptOptions.runtimeTypecheckIgnoreUnknownProperties,
|
||||
javaScriptOptions.acronymStyle,
|
||||
javaScriptOptions.converters,
|
||||
javaScriptOptions.rawType
|
||||
];
|
||||
public getOptions(): typeof javaScriptOptions {
|
||||
return javaScriptOptions;
|
||||
}
|
||||
|
||||
public get stringTypeMapping(): StringTypeMapping {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { EnumOption, type Option, getOptionValues } from "../../RendererOptions";
|
||||
import { EnumOption, getOptionValues } from "../../RendererOptions";
|
||||
import { AcronymStyleOptions, acronymOption } from "../../support/Acronyms";
|
||||
import { convertersOption } from "../../support/Converters";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { JavaScriptPropTypesRenderer } from "./JavaScriptPropTypesRenderer";
|
||||
|
||||
@ -13,25 +13,27 @@ export const javaScriptPropTypesOptions = {
|
||||
moduleSystem: new EnumOption(
|
||||
"module-system",
|
||||
"Which module system to use",
|
||||
[
|
||||
["common-js", false],
|
||||
["es6", true]
|
||||
],
|
||||
{
|
||||
"common-js": false,
|
||||
"es6": true
|
||||
} as const,
|
||||
"es6"
|
||||
)
|
||||
};
|
||||
|
||||
export class JavaScriptPropTypesTargetLanguage extends TargetLanguage {
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [javaScriptPropTypesOptions.acronymStyle, javaScriptPropTypesOptions.converters];
|
||||
export const javaScriptPropTypesLanguageConfig = {
|
||||
displayName: "JavaScript PropTypes",
|
||||
names: ["javascript-prop-types"],
|
||||
extension: "js"
|
||||
} as const;
|
||||
|
||||
export class JavaScriptPropTypesTargetLanguage extends TargetLanguage<typeof javaScriptPropTypesLanguageConfig> {
|
||||
public constructor() {
|
||||
super(javaScriptPropTypesLanguageConfig);
|
||||
}
|
||||
|
||||
public constructor(
|
||||
displayName = "JavaScript PropTypes",
|
||||
names: string[] = ["javascript-prop-types"],
|
||||
extension = "js"
|
||||
) {
|
||||
super(displayName, names, extension);
|
||||
public getOptions(): typeof javaScriptPropTypesOptions {
|
||||
return javaScriptPropTypesOptions;
|
||||
}
|
||||
|
||||
protected makeRenderer(
|
||||
|
@ -1,46 +1,45 @@
|
||||
import { type ConvenienceRenderer } from "../../ConvenienceRenderer";
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { AcronymStyleOptions, acronymOption } from "../../support/Acronyms";
|
||||
import { assertNever } from "../../support/Support";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { KotlinJacksonRenderer } from "./KotlinJacksonRenderer";
|
||||
import { KotlinKlaxonRenderer } from "./KotlinKlaxonRenderer";
|
||||
import { KotlinRenderer } from "./KotlinRenderer";
|
||||
import { KotlinXRenderer } from "./KotlinXRenderer";
|
||||
|
||||
export enum Framework {
|
||||
None = "None",
|
||||
Jackson = "Jackson",
|
||||
Klaxon = "Klaxon",
|
||||
KotlinX = "KotlinX"
|
||||
}
|
||||
|
||||
export const kotlinOptions = {
|
||||
framework: new EnumOption(
|
||||
"framework",
|
||||
"Serialization framework",
|
||||
[
|
||||
["just-types", Framework.None],
|
||||
["jackson", Framework.Jackson],
|
||||
["klaxon", Framework.Klaxon],
|
||||
["kotlinx", Framework.KotlinX]
|
||||
],
|
||||
{
|
||||
"just-types": "None",
|
||||
"jackson": "Jackson",
|
||||
"klaxon": "Klaxon",
|
||||
"kotlinx": "KotlinX"
|
||||
} as const,
|
||||
"klaxon"
|
||||
),
|
||||
acronymStyle: acronymOption(AcronymStyleOptions.Pascal),
|
||||
packageName: new StringOption("package", "Package", "PACKAGE", "quicktype")
|
||||
};
|
||||
|
||||
export class KotlinTargetLanguage extends TargetLanguage {
|
||||
export const kotlinLanguageConfig = {
|
||||
displayName: "Kotlin",
|
||||
names: ["kotlin"],
|
||||
extension: "kt"
|
||||
} as const;
|
||||
|
||||
export class KotlinTargetLanguage extends TargetLanguage<typeof kotlinLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Kotlin", ["kotlin"], "kt");
|
||||
super(kotlinLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [kotlinOptions.framework, kotlinOptions.acronymStyle, kotlinOptions.packageName];
|
||||
public getOptions(): typeof kotlinOptions {
|
||||
return kotlinOptions;
|
||||
}
|
||||
|
||||
public get supportsOptionalClassProperties(): boolean {
|
||||
@ -55,13 +54,13 @@ export class KotlinTargetLanguage extends TargetLanguage {
|
||||
const options = getOptionValues(kotlinOptions, untypedOptionValues);
|
||||
|
||||
switch (options.framework) {
|
||||
case Framework.None:
|
||||
case "None":
|
||||
return new KotlinRenderer(this, renderContext, options);
|
||||
case Framework.Jackson:
|
||||
case "Jackson":
|
||||
return new KotlinJacksonRenderer(this, renderContext, options);
|
||||
case Framework.Klaxon:
|
||||
case "Klaxon":
|
||||
return new KotlinKlaxonRenderer(this, renderContext, options);
|
||||
case Framework.KotlinX:
|
||||
case "KotlinX":
|
||||
return new KotlinXRenderer(this, renderContext, options);
|
||||
default:
|
||||
return assertNever(options.framework);
|
||||
|
@ -12,7 +12,7 @@ import { ArrayType, type ClassProperty, ClassType, EnumType, MapType, Type, Unio
|
||||
import { isAnyOrNull, matchType, nullableFromUnion } from "../../TypeUtils";
|
||||
|
||||
import { forbiddenPropertyNames, keywords } from "./constants";
|
||||
import { type MemoryAttribute, type objectiveCOptions } from "./language";
|
||||
import { type objectiveCOptions } from "./language";
|
||||
import {
|
||||
DEFAULT_CLASS_PREFIX,
|
||||
forbiddenForEnumCases,
|
||||
@ -22,6 +22,8 @@ import {
|
||||
typeNameStyle
|
||||
} from "./utils";
|
||||
|
||||
type MemoryAttribute = "assign" | "strong" | "copy";
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
export class ObjectiveCRenderer extends ConvenienceRenderer {
|
||||
|
@ -1,42 +1,41 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { ObjectiveCRenderer } from "./ObjectiveCRenderer";
|
||||
import { DEFAULT_CLASS_PREFIX } from "./utils";
|
||||
|
||||
export type MemoryAttribute = "assign" | "strong" | "copy";
|
||||
export interface OutputFeatures {
|
||||
implementation: boolean;
|
||||
interface: boolean;
|
||||
}
|
||||
|
||||
export const objectiveCOptions = {
|
||||
features: new EnumOption("features", "Interface and implementation", [
|
||||
["all", { interface: true, implementation: true }],
|
||||
["interface", { interface: true, implementation: false }],
|
||||
["implementation", { interface: false, implementation: true }]
|
||||
]),
|
||||
features: new EnumOption(
|
||||
"features",
|
||||
"Interface and implementation",
|
||||
{
|
||||
all: { interface: true, implementation: true },
|
||||
interface: { interface: true, implementation: false },
|
||||
implementation: { interface: false, implementation: true }
|
||||
} as const,
|
||||
"all"
|
||||
),
|
||||
justTypes: new BooleanOption("just-types", "Plain types only", false),
|
||||
marshallingFunctions: new BooleanOption("functions", "C-style functions", false),
|
||||
classPrefix: new StringOption("class-prefix", "Class prefix", "PREFIX", DEFAULT_CLASS_PREFIX),
|
||||
extraComments: new BooleanOption("extra-comments", "Extra comments", false)
|
||||
};
|
||||
|
||||
export class ObjectiveCTargetLanguage extends TargetLanguage {
|
||||
export const objectiveCLanguageConfig = {
|
||||
displayName: "Objective-C",
|
||||
names: ["objc", "objective-c", "objectivec"],
|
||||
extension: "m"
|
||||
} as const;
|
||||
|
||||
export class ObjectiveCTargetLanguage extends TargetLanguage<typeof objectiveCLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Objective-C", ["objc", "objective-c", "objectivec"], "m");
|
||||
super(objectiveCLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
objectiveCOptions.justTypes,
|
||||
objectiveCOptions.classPrefix,
|
||||
objectiveCOptions.features,
|
||||
objectiveCOptions.extraComments,
|
||||
objectiveCOptions.marshallingFunctions
|
||||
];
|
||||
public getOptions(): typeof objectiveCOptions {
|
||||
return objectiveCOptions;
|
||||
}
|
||||
|
||||
protected makeRenderer(renderContext: RenderContext, untypedOptionValues: FixMeOptionsType): ObjectiveCRenderer {
|
||||
|
@ -1,12 +1,10 @@
|
||||
import * as _ from "lodash";
|
||||
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, type Option, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, getOptionValues } from "../../RendererOptions";
|
||||
import { AcronymStyleOptions, acronymOption } from "../../support/Acronyms";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { PhpRenderer } from "./PhpRenderer";
|
||||
|
||||
@ -17,13 +15,20 @@ export const phpOptions = {
|
||||
withClosing: new BooleanOption("with-closing", "PHP Closing Tag", false),
|
||||
acronymStyle: acronymOption(AcronymStyleOptions.Pascal)
|
||||
};
|
||||
export class PhpTargetLanguage extends TargetLanguage {
|
||||
|
||||
export const phpLanguageConfig = {
|
||||
displayName: "PHP",
|
||||
names: ["php"],
|
||||
extension: "php"
|
||||
} as const;
|
||||
|
||||
export class PhpTargetLanguage extends TargetLanguage<typeof phpLanguageConfig> {
|
||||
public constructor() {
|
||||
super("PHP", ["php"], "php");
|
||||
super(phpLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return _.values(phpOptions);
|
||||
public getOptions(): typeof phpOptions {
|
||||
return phpOptions;
|
||||
}
|
||||
|
||||
public get supportsUnionsWithBothNumberTypes(): boolean {
|
||||
|
@ -1,19 +1,23 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { type Option } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType } from "../../types";
|
||||
|
||||
import { PikeRenderer } from "./PikeRenderer";
|
||||
|
||||
export const pikeOptions = {};
|
||||
|
||||
export class PikeTargetLanguage extends TargetLanguage {
|
||||
export const pikeLanguageConfig = {
|
||||
displayName: "Pike",
|
||||
names: ["pike", "pikelang"],
|
||||
extension: "pmod"
|
||||
} as const;
|
||||
|
||||
export class PikeTargetLanguage extends TargetLanguage<typeof pikeLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Pike", ["pike", "pikelang"], "pmod");
|
||||
super(pikeLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [];
|
||||
public getOptions(): {} {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected makeRenderer(renderContext: RenderContext): PikeRenderer {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { iterableSome } from "collection-utils";
|
||||
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind, type Type, UnionType } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { JSONPythonRenderer } from "./JSONPythonRenderer";
|
||||
import { PythonRenderer } from "./PythonRenderer";
|
||||
@ -16,14 +16,14 @@ export interface PythonFeatures {
|
||||
}
|
||||
|
||||
export const pythonOptions = {
|
||||
features: new EnumOption<PythonFeatures>(
|
||||
features: new EnumOption(
|
||||
"python-version",
|
||||
"Python version",
|
||||
[
|
||||
["3.5", { typeHints: false, dataClasses: false }],
|
||||
["3.6", { typeHints: true, dataClasses: false }],
|
||||
["3.7", { typeHints: true, dataClasses: true }]
|
||||
],
|
||||
{
|
||||
"3.5": { typeHints: false, dataClasses: false },
|
||||
"3.6": { typeHints: true, dataClasses: false },
|
||||
"3.7": { typeHints: true, dataClasses: true }
|
||||
},
|
||||
"3.6"
|
||||
),
|
||||
justTypes: new BooleanOption("just-types", "Classes only", false),
|
||||
@ -31,14 +31,15 @@ export const pythonOptions = {
|
||||
pydanticBaseModel: new BooleanOption("pydantic-base-model", "Uses pydantic BaseModel", false)
|
||||
};
|
||||
|
||||
export class PythonTargetLanguage extends TargetLanguage {
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
pythonOptions.features,
|
||||
pythonOptions.justTypes,
|
||||
pythonOptions.nicePropertyNames,
|
||||
pythonOptions.pydanticBaseModel
|
||||
];
|
||||
export const pythonLanguageConfig = { displayName: "Python", names: ["python", "py"], extension: "py" } as const;
|
||||
|
||||
export class PythonTargetLanguage extends TargetLanguage<typeof pythonLanguageConfig> {
|
||||
public constructor() {
|
||||
super(pythonLanguageConfig);
|
||||
}
|
||||
|
||||
public getOptions(): typeof pythonOptions {
|
||||
return pythonOptions;
|
||||
}
|
||||
|
||||
public get stringTypeMapping(): StringTypeMapping {
|
||||
|
@ -1,28 +1,39 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { RubyRenderer } from "./RubyRenderer";
|
||||
import { Strictness } from "./utils";
|
||||
|
||||
export const rubyOptions = {
|
||||
justTypes: new BooleanOption("just-types", "Plain types only", false),
|
||||
strictness: new EnumOption("strictness", "Type strictness", [
|
||||
["strict", Strictness.Strict],
|
||||
["coercible", Strictness.Coercible],
|
||||
["none", Strictness.None]
|
||||
]),
|
||||
strictness: new EnumOption(
|
||||
"strictness",
|
||||
"Type strictness",
|
||||
{
|
||||
strict: Strictness.Strict,
|
||||
coercible: Strictness.Coercible,
|
||||
none: Strictness.None
|
||||
} as const,
|
||||
"strict"
|
||||
),
|
||||
namespace: new StringOption("namespace", "Specify a wrapping Namespace", "NAME", "", "secondary")
|
||||
};
|
||||
|
||||
export class RubyTargetLanguage extends TargetLanguage {
|
||||
export const rubyLanguageConfig = {
|
||||
displayName: "Ruby",
|
||||
names: ["ruby"],
|
||||
extension: "rb"
|
||||
} as const;
|
||||
|
||||
export class RubyTargetLanguage extends TargetLanguage<typeof rubyLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Ruby", ["ruby"], "rb");
|
||||
super(rubyLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [rubyOptions.justTypes, rubyOptions.strictness, rubyOptions.namespace];
|
||||
public getOptions(): typeof rubyOptions {
|
||||
return rubyOptions;
|
||||
}
|
||||
|
||||
public get supportsOptionalClassProperties(): boolean {
|
||||
|
@ -1,48 +1,55 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { RustRenderer } from "./RustRenderer";
|
||||
import { Density, Visibility } from "./utils";
|
||||
|
||||
export const rustOptions = {
|
||||
density: new EnumOption("density", "Density", [
|
||||
["normal", Density.Normal],
|
||||
["dense", Density.Dense]
|
||||
]),
|
||||
visibility: new EnumOption("visibility", "Field visibility", [
|
||||
["private", Visibility.Private],
|
||||
["crate", Visibility.Crate],
|
||||
["public", Visibility.Public]
|
||||
]),
|
||||
density: new EnumOption(
|
||||
"density",
|
||||
"Density",
|
||||
{
|
||||
normal: Density.Normal,
|
||||
dense: Density.Dense
|
||||
} as const,
|
||||
"normal"
|
||||
),
|
||||
visibility: new EnumOption(
|
||||
"visibility",
|
||||
"Field visibility",
|
||||
{
|
||||
private: Visibility.Private,
|
||||
crate: Visibility.Crate,
|
||||
public: Visibility.Public
|
||||
} as const,
|
||||
"private"
|
||||
),
|
||||
deriveDebug: new BooleanOption("derive-debug", "Derive Debug impl", false),
|
||||
deriveClone: new BooleanOption("derive-clone", "Derive Clone impl", false),
|
||||
derivePartialEq: new BooleanOption("derive-partial-eq", "Derive PartialEq impl", false),
|
||||
skipSerializingNone: new BooleanOption("skip-serializing-none", "Skip serializing empty Option fields", false),
|
||||
edition2018: new BooleanOption("edition-2018", "Edition 2018", true),
|
||||
leadingComments: new BooleanOption("leading-comments", "Leading Comments", true)
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const rustLanguageConfig = {
|
||||
displayName: "Rust",
|
||||
names: ["rust", "rs", "rustlang"],
|
||||
extension: "rs"
|
||||
} as const;
|
||||
|
||||
export class RustTargetLanguage extends TargetLanguage<typeof rustLanguageConfig> {
|
||||
public constructor() {
|
||||
super(rustLanguageConfig);
|
||||
}
|
||||
|
||||
public getOptions(): typeof rustOptions {
|
||||
return rustOptions;
|
||||
}
|
||||
|
||||
export class RustTargetLanguage extends TargetLanguage {
|
||||
protected makeRenderer(renderContext: RenderContext, untypedOptionValues: FixMeOptionsType): RustRenderer {
|
||||
return new RustRenderer(this, renderContext, getOptionValues(rustOptions, untypedOptionValues));
|
||||
}
|
||||
|
||||
public constructor() {
|
||||
super("Rust", ["rust", "rs", "rustlang"], "rs");
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
rustOptions.density,
|
||||
rustOptions.visibility,
|
||||
rustOptions.deriveDebug,
|
||||
rustOptions.deriveClone,
|
||||
rustOptions.derivePartialEq,
|
||||
rustOptions.edition2018,
|
||||
rustOptions.leadingComments,
|
||||
rustOptions.skipSerializingNone
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +1,41 @@
|
||||
import { type ConvenienceRenderer } from "../../ConvenienceRenderer";
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { assertNever } from "../../support/Support";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { CirceRenderer } from "./CirceRenderer";
|
||||
import { Scala3Renderer } from "./Scala3Renderer";
|
||||
import { UpickleRenderer } from "./UpickleRenderer";
|
||||
|
||||
export enum Framework {
|
||||
None = "None",
|
||||
Upickle = "Upickle",
|
||||
Circe = "Circe"
|
||||
}
|
||||
|
||||
export const scala3Options = {
|
||||
framework: new EnumOption(
|
||||
"framework",
|
||||
"Serialization framework",
|
||||
[
|
||||
["just-types", Framework.None],
|
||||
["circe", Framework.Circe],
|
||||
["upickle", Framework.Upickle]
|
||||
],
|
||||
undefined
|
||||
{
|
||||
"just-types": "None",
|
||||
"circe": "Circe",
|
||||
"upickle": "Upickle"
|
||||
} as const,
|
||||
"just-types"
|
||||
),
|
||||
packageName: new StringOption("package", "Package", "PACKAGE", "quicktype")
|
||||
};
|
||||
|
||||
export class Scala3TargetLanguage extends TargetLanguage {
|
||||
export const scala3LanguageConfig = {
|
||||
displayName: "Scala3",
|
||||
names: ["scala3"],
|
||||
extension: "scala"
|
||||
} as const;
|
||||
|
||||
export class Scala3TargetLanguage extends TargetLanguage<typeof scala3LanguageConfig> {
|
||||
public constructor() {
|
||||
super("Scala3", ["scala3"], "scala");
|
||||
super(scala3LanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [scala3Options.framework, scala3Options.packageName];
|
||||
public getOptions(): typeof scala3Options {
|
||||
return scala3Options;
|
||||
}
|
||||
|
||||
public get supportsOptionalClassProperties(): boolean {
|
||||
@ -50,11 +50,11 @@ export class Scala3TargetLanguage extends TargetLanguage {
|
||||
const options = getOptionValues(scala3Options, untypedOptionValues);
|
||||
|
||||
switch (options.framework) {
|
||||
case Framework.None:
|
||||
case "None":
|
||||
return new Scala3Renderer(this, renderContext, options);
|
||||
case Framework.Upickle:
|
||||
case "Upickle":
|
||||
return new UpickleRenderer(this, renderContext, options);
|
||||
case Framework.Circe:
|
||||
case "Circe":
|
||||
return new CirceRenderer(this, renderContext, options);
|
||||
default:
|
||||
return assertNever(options.framework);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { type ConvenienceRenderer } from "../../ConvenienceRenderer";
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { assertNever } from "../../support/Support";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { Smithy4sRenderer } from "./Smithy4sRenderer";
|
||||
|
||||
@ -12,17 +12,29 @@ export enum Framework {
|
||||
}
|
||||
|
||||
export const smithyOptions = {
|
||||
framework: new EnumOption("framework", "Serialization framework", [["just-types", Framework.None]], undefined),
|
||||
// FIXME: why does this exist
|
||||
framework: new EnumOption(
|
||||
"framework",
|
||||
"Serialization framework",
|
||||
{ "just-types": Framework.None } as const,
|
||||
"just-types"
|
||||
),
|
||||
packageName: new StringOption("package", "Package", "PACKAGE", "quicktype")
|
||||
};
|
||||
|
||||
export class SmithyTargetLanguage extends TargetLanguage {
|
||||
export const smithyLanguageConfig = {
|
||||
displayName: "Smithy",
|
||||
names: ["smithy4a"],
|
||||
extension: "smithy"
|
||||
} as const;
|
||||
|
||||
export class SmithyTargetLanguage extends TargetLanguage<typeof smithyLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Smithy", ["Smithy"], "smithy");
|
||||
super(smithyLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [smithyOptions.framework, smithyOptions.packageName];
|
||||
public getOptions(): typeof smithyOptions {
|
||||
return smithyOptions;
|
||||
}
|
||||
|
||||
public get supportsOptionalClassProperties(): boolean {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { type DateTimeRecognizer } from "../../DateTime";
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, EnumOption, type Option, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, EnumOption, StringOption, getOptionValues } from "../../RendererOptions";
|
||||
import { AcronymStyleOptions, acronymOption } from "../../support/Acronyms";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { SwiftRenderer } from "./SwiftRenderer";
|
||||
import { SwiftDateTimeRecognizer } from "./utils";
|
||||
@ -23,19 +23,24 @@ export const swiftOptions = {
|
||||
),
|
||||
alamofire: new BooleanOption("alamofire", "Alamofire extensions", false),
|
||||
namedTypePrefix: new StringOption("type-prefix", "Prefix for type names", "PREFIX", "", "secondary"),
|
||||
useClasses: new EnumOption("struct-or-class", "Structs or classes", [
|
||||
["struct", false],
|
||||
["class", true]
|
||||
]),
|
||||
useClasses: new EnumOption(
|
||||
"struct-or-class",
|
||||
"Structs or classes",
|
||||
{
|
||||
struct: false,
|
||||
class: true
|
||||
} as const,
|
||||
"struct"
|
||||
),
|
||||
mutableProperties: new BooleanOption("mutable-properties", "Use var instead of let for object properties", false),
|
||||
acronymStyle: acronymOption(AcronymStyleOptions.Pascal),
|
||||
dense: new EnumOption(
|
||||
"density",
|
||||
"Code density",
|
||||
[
|
||||
["dense", true],
|
||||
["normal", false]
|
||||
],
|
||||
{
|
||||
dense: true,
|
||||
normal: false
|
||||
} as const,
|
||||
"dense",
|
||||
"secondary"
|
||||
),
|
||||
@ -56,52 +61,39 @@ export const swiftOptions = {
|
||||
accessLevel: new EnumOption(
|
||||
"access-level",
|
||||
"Access level",
|
||||
[
|
||||
["internal", "internal"],
|
||||
["public", "public"]
|
||||
],
|
||||
{
|
||||
internal: "internal",
|
||||
public: "public"
|
||||
} as const,
|
||||
"internal",
|
||||
"secondary"
|
||||
),
|
||||
protocol: new EnumOption(
|
||||
"protocol",
|
||||
"Make types implement protocol",
|
||||
[
|
||||
["none", { equatable: false, hashable: false }],
|
||||
["equatable", { equatable: true, hashable: false }],
|
||||
["hashable", { equatable: false, hashable: true }]
|
||||
],
|
||||
{
|
||||
none: { equatable: false, hashable: false },
|
||||
equatable: { equatable: true, hashable: false },
|
||||
hashable: { equatable: false, hashable: true }
|
||||
} as const,
|
||||
"none",
|
||||
"secondary"
|
||||
)
|
||||
};
|
||||
|
||||
export class SwiftTargetLanguage extends TargetLanguage {
|
||||
export const swiftLanguageConfig = {
|
||||
displayName: "Swift",
|
||||
names: ["swift", "swift4"],
|
||||
extension: "swift"
|
||||
} as const;
|
||||
|
||||
export class SwiftTargetLanguage extends TargetLanguage<typeof swiftLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Swift", ["swift", "swift4"], "swift");
|
||||
super(swiftLanguageConfig);
|
||||
}
|
||||
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
swiftOptions.justTypes,
|
||||
swiftOptions.useClasses,
|
||||
swiftOptions.dense,
|
||||
swiftOptions.convenienceInitializers,
|
||||
swiftOptions.explicitCodingKeys,
|
||||
swiftOptions.codingKeysProtocol,
|
||||
swiftOptions.accessLevel,
|
||||
swiftOptions.alamofire,
|
||||
swiftOptions.linux,
|
||||
swiftOptions.namedTypePrefix,
|
||||
swiftOptions.protocol,
|
||||
swiftOptions.acronymStyle,
|
||||
swiftOptions.objcSupport,
|
||||
swiftOptions.optionalEnums,
|
||||
swiftOptions.sendable,
|
||||
swiftOptions.swift5Support,
|
||||
swiftOptions.multiFileOutput,
|
||||
swiftOptions.mutableProperties
|
||||
];
|
||||
public getOptions(): typeof swiftOptions {
|
||||
return swiftOptions;
|
||||
}
|
||||
|
||||
public get stringTypeMapping(): StringTypeMapping {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, type Option, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { TypeScriptEffectSchemaRenderer } from "./TypeScriptEffectSchemaRenderer";
|
||||
|
||||
@ -9,17 +9,19 @@ export const typeScriptEffectSchemaOptions = {
|
||||
justSchema: new BooleanOption("just-schema", "Schema only", false)
|
||||
};
|
||||
|
||||
export class TypeScriptEffectSchemaTargetLanguage extends TargetLanguage {
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [];
|
||||
export const typeScriptEffectSchemaLanguageConfig = {
|
||||
displayName: "TypeScript Effect Schema",
|
||||
names: ["typescript-effect-schema"],
|
||||
extension: "ts"
|
||||
} as const;
|
||||
|
||||
export class TypeScriptEffectSchemaTargetLanguage extends TargetLanguage<typeof typeScriptEffectSchemaLanguageConfig> {
|
||||
public constructor() {
|
||||
super(typeScriptEffectSchemaLanguageConfig);
|
||||
}
|
||||
|
||||
public constructor(
|
||||
displayName: string = "TypeScript Effect Schema",
|
||||
names: string[] = ["typescript-effect-schema"],
|
||||
extension: string = "ts"
|
||||
) {
|
||||
super(displayName, names, extension);
|
||||
public getOptions(): {} {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected makeRenderer(
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, type Option, getOptionValues } from "../../RendererOptions";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type JavaScriptRenderer, JavaScriptTargetLanguage, javaScriptOptions } from "../JavaScript";
|
||||
import { BooleanOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
import { javaScriptOptions } from "../JavaScript";
|
||||
|
||||
import { FlowRenderer } from "./FlowRenderer";
|
||||
import { TypeScriptRenderer } from "./TypeScriptRenderer";
|
||||
@ -20,46 +23,71 @@ export const tsFlowOptions = Object.assign({}, javaScriptOptions, {
|
||||
readonly: new BooleanOption("readonly", "Use readonly type members", false)
|
||||
});
|
||||
|
||||
export abstract class TypeScriptFlowBaseTargetLanguage extends JavaScriptTargetLanguage {
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [
|
||||
tsFlowOptions.justTypes,
|
||||
tsFlowOptions.nicePropertyNames,
|
||||
tsFlowOptions.declareUnions,
|
||||
tsFlowOptions.runtimeTypecheck,
|
||||
tsFlowOptions.runtimeTypecheckIgnoreUnknownProperties,
|
||||
tsFlowOptions.acronymStyle,
|
||||
tsFlowOptions.converters,
|
||||
tsFlowOptions.rawType,
|
||||
tsFlowOptions.preferUnions,
|
||||
tsFlowOptions.preferTypes,
|
||||
tsFlowOptions.preferConstValues,
|
||||
tsFlowOptions.readonly
|
||||
];
|
||||
export const typeScriptLanguageConfig = {
|
||||
displayName: "TypeScript",
|
||||
names: ["typescript", "ts", "tsx"],
|
||||
extension: "ts"
|
||||
} as const;
|
||||
|
||||
export class TypeScriptTargetLanguage extends TargetLanguage<typeof typeScriptLanguageConfig> {
|
||||
public constructor() {
|
||||
super(typeScriptLanguageConfig);
|
||||
}
|
||||
|
||||
public getOptions(): typeof tsFlowOptions {
|
||||
return tsFlowOptions;
|
||||
}
|
||||
|
||||
public get stringTypeMapping(): StringTypeMapping {
|
||||
const mapping: Map<TransformedStringTypeKind, PrimitiveStringTypeKind> = new Map();
|
||||
const dateTimeType = "date-time";
|
||||
mapping.set("date", dateTimeType);
|
||||
mapping.set("date-time", dateTimeType);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
public get supportsOptionalClassProperties(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract makeRenderer(
|
||||
renderContext: RenderContext,
|
||||
untypedOptionValues: FixMeOptionsType
|
||||
): JavaScriptRenderer;
|
||||
}
|
||||
|
||||
export class TypeScriptTargetLanguage extends TypeScriptFlowBaseTargetLanguage {
|
||||
public constructor() {
|
||||
super("TypeScript", ["typescript", "ts", "tsx"], "ts");
|
||||
public get supportsFullObjectType(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected makeRenderer(renderContext: RenderContext, untypedOptionValues: FixMeOptionsType): TypeScriptRenderer {
|
||||
return new TypeScriptRenderer(this, renderContext, getOptionValues(tsFlowOptions, untypedOptionValues));
|
||||
}
|
||||
}
|
||||
export class FlowTargetLanguage extends TypeScriptFlowBaseTargetLanguage {
|
||||
|
||||
export const flowLanguageConfig = {
|
||||
displayName: "Flow",
|
||||
names: ["flow"],
|
||||
extension: "js"
|
||||
} as const;
|
||||
|
||||
export class FlowTargetLanguage extends TargetLanguage<typeof flowLanguageConfig> {
|
||||
public constructor() {
|
||||
super("Flow", ["flow"], "js");
|
||||
super(flowLanguageConfig);
|
||||
}
|
||||
|
||||
public getOptions(): typeof tsFlowOptions {
|
||||
return tsFlowOptions;
|
||||
}
|
||||
|
||||
public get stringTypeMapping(): StringTypeMapping {
|
||||
const mapping: Map<TransformedStringTypeKind, PrimitiveStringTypeKind> = new Map();
|
||||
const dateTimeType = "date-time";
|
||||
mapping.set("date", dateTimeType);
|
||||
mapping.set("date-time", dateTimeType);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
public get supportsOptionalClassProperties(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get supportsFullObjectType(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected makeRenderer(renderContext: RenderContext, untypedOptionValues: FixMeOptionsType): FlowRenderer {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { type RenderContext } from "../../Renderer";
|
||||
import { BooleanOption, type Option, getOptionValues } from "../../RendererOptions";
|
||||
import { BooleanOption, getOptionValues } from "../../RendererOptions";
|
||||
import { TargetLanguage } from "../../TargetLanguage";
|
||||
import { type PrimitiveStringTypeKind, type TransformedStringTypeKind } from "../../Type";
|
||||
import { type StringTypeMapping } from "../../TypeBuilder";
|
||||
import { type FixMeOptionsAnyType, type FixMeOptionsType } from "../../types";
|
||||
import { type FixMeOptionsType } from "../../types";
|
||||
|
||||
import { TypeScriptZodRenderer } from "./TypeScriptZodRenderer";
|
||||
|
||||
@ -11,17 +11,19 @@ export const typeScriptZodOptions = {
|
||||
justSchema: new BooleanOption("just-schema", "Schema only", false)
|
||||
};
|
||||
|
||||
export class TypeScriptZodTargetLanguage extends TargetLanguage {
|
||||
protected getOptions(): Array<Option<FixMeOptionsAnyType>> {
|
||||
return [];
|
||||
export const typeScriptZodLanguageConfig = {
|
||||
displayName: "TypeScript Zod",
|
||||
names: ["typescript-zod"],
|
||||
extension: "ts"
|
||||
} as const;
|
||||
|
||||
export class TypeScriptZodTargetLanguage extends TargetLanguage<typeof typeScriptZodLanguageConfig> {
|
||||
public constructor() {
|
||||
super(typeScriptZodLanguageConfig);
|
||||
}
|
||||
|
||||
public constructor(
|
||||
displayName: string = "TypeScript Zod",
|
||||
names: string[] = ["typescript-zod"],
|
||||
extension: string = "ts"
|
||||
) {
|
||||
super(displayName, names, extension);
|
||||
public getOptions(): {} {
|
||||
return {};
|
||||
}
|
||||
|
||||
public get stringTypeMapping(): StringTypeMapping {
|
||||
|
@ -24,3 +24,7 @@ export * from "./Swift";
|
||||
export * from "./TypeScriptFlow";
|
||||
export * from "./TypeScriptEffectSchema";
|
||||
export * from "./TypeScriptZod";
|
||||
|
||||
export { all as defaultTargetLanguages, languageNamed, isLanguageName } from "./All";
|
||||
export type { LanguageName, LanguageDisplayName } from "./types";
|
||||
export type { RendererOptions } from "./options.types";
|
||||
|
15
packages/quicktype-core/src/language/options.types.ts
Normal file
15
packages/quicktype-core/src/language/options.types.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { OptionMap } from "../RendererOptions";
|
||||
|
||||
import type { LanguageName, LanguageNameMap } from "./types";
|
||||
|
||||
export type LanguageRawOptionMap = Readonly<{
|
||||
[Lang in keyof LanguageNameMap]: ReturnType<LanguageNameMap[Lang]["getOptions"]>;
|
||||
}>;
|
||||
|
||||
export type LanguageOptionMap = Readonly<{
|
||||
[Lang in keyof LanguageRawOptionMap]: OptionMap<LanguageRawOptionMap[Lang]>;
|
||||
}>;
|
||||
|
||||
export type RendererOptions<Lang extends LanguageName = LanguageName, Options = LanguageOptionMap[Lang]> = {
|
||||
-readonly [K in keyof Options]: Options[K];
|
||||
};
|
15
packages/quicktype-core/src/language/types.ts
Normal file
15
packages/quicktype-core/src/language/types.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { type TargetLanguage } from "../TargetLanguage";
|
||||
|
||||
import { type all } from "./All";
|
||||
|
||||
type AllLanguages = (typeof all)[number];
|
||||
|
||||
export type LanguageDisplayName<Language extends TargetLanguage = AllLanguages> = Language["displayName"];
|
||||
export type LanguageName<Language extends TargetLanguage = AllLanguages> = Language["names"][number];
|
||||
|
||||
export type LanguageDisplayNameMap = {
|
||||
[Language in AllLanguages as LanguageDisplayName<Language>]: Language;
|
||||
};
|
||||
export type LanguageNameMap = {
|
||||
[Language in AllLanguages as LanguageName<Language>]: Language;
|
||||
};
|
@ -9,16 +9,16 @@ export enum AcronymStyleOptions {
|
||||
Pascal = "pascal"
|
||||
}
|
||||
|
||||
export const acronymOption = function (defaultOption: AcronymStyleOptions): EnumOption<AcronymStyleOptions> {
|
||||
export const acronymOption = function (defaultOption: AcronymStyleOptions) {
|
||||
return new EnumOption(
|
||||
"acronym-style",
|
||||
"Acronym naming style",
|
||||
[
|
||||
[AcronymStyleOptions.Original, AcronymStyleOptions.Original],
|
||||
[AcronymStyleOptions.Pascal, AcronymStyleOptions.Pascal],
|
||||
[AcronymStyleOptions.Camel, AcronymStyleOptions.Camel],
|
||||
[AcronymStyleOptions.Lower, AcronymStyleOptions.Lower]
|
||||
],
|
||||
{
|
||||
[AcronymStyleOptions.Original]: AcronymStyleOptions.Original,
|
||||
[AcronymStyleOptions.Pascal]: AcronymStyleOptions.Pascal,
|
||||
[AcronymStyleOptions.Camel]: AcronymStyleOptions.Camel,
|
||||
[AcronymStyleOptions.Lower]: AcronymStyleOptions.Lower
|
||||
} as const,
|
||||
defaultOption,
|
||||
"secondary"
|
||||
);
|
||||
|
@ -5,14 +5,14 @@ export enum ConvertersOptions {
|
||||
TopLevel = "top-level"
|
||||
}
|
||||
|
||||
export function convertersOption(): EnumOption<ConvertersOptions> {
|
||||
export function convertersOption() {
|
||||
return new EnumOption(
|
||||
"converters",
|
||||
"Which converters to generate (top-level by default)",
|
||||
[
|
||||
[ConvertersOptions.TopLevel, ConvertersOptions.TopLevel],
|
||||
[ConvertersOptions.AllObjects, ConvertersOptions.AllObjects]
|
||||
],
|
||||
{
|
||||
[ConvertersOptions.TopLevel]: ConvertersOptions.TopLevel,
|
||||
[ConvertersOptions.AllObjects]: ConvertersOptions.AllObjects
|
||||
} as const,
|
||||
ConvertersOptions.TopLevel,
|
||||
"secondary"
|
||||
);
|
||||
|
@ -1,6 +1,9 @@
|
||||
// FIXME: remove these when options are strongly types
|
||||
export * from "./language/types";
|
||||
export * from "./language/options.types";
|
||||
|
||||
// FIXME: remove these when options are strongly typed
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export type FixMeOptionsType = Record<string, any>;
|
||||
|
||||
export type FixMeOptionsAnyType = any;
|
||||
// FIXME: Remove this post TS5.4
|
||||
export type NoInfer<T> = [T][T extends any ? 0 : never];
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
type TargetLanguage,
|
||||
defaultTargetLanguages,
|
||||
inferenceFlagNames,
|
||||
isLanguageName,
|
||||
jsonInputForTargetLanguage,
|
||||
languageNamed,
|
||||
quicktype
|
||||
@ -64,17 +65,17 @@ async function pickTargetLanguage(): Promise<TargetLanguagePick> {
|
||||
const languageChoices = defaultTargetLanguages.map(l => l.displayName).sort();
|
||||
let chosenName = await vscode.window.showQuickPick(languageChoices);
|
||||
const cancelled = chosenName === undefined;
|
||||
if (chosenName === undefined) {
|
||||
chosenName = "typescript";
|
||||
if (chosenName === undefined || !isLanguageName(chosenName)) {
|
||||
return { cancelled, lang: languageNamed("typescript") };
|
||||
}
|
||||
|
||||
// @ts-expect-error languageNamed is not strongly typed yet
|
||||
return { cancelled, lang: languageNamed(chosenName) };
|
||||
}
|
||||
|
||||
async function getTargetLanguage(editor: vscode.TextEditor): Promise<TargetLanguagePick> {
|
||||
const documentLanguage = editor.document.languageId;
|
||||
const currentLanguage = languageNamed(documentLanguage);
|
||||
const languageName = isLanguageName(documentLanguage) ? documentLanguage : "typescript";
|
||||
const currentLanguage = languageNamed(languageName);
|
||||
if (currentLanguage !== undefined) {
|
||||
return {
|
||||
cancelled: false,
|
||||
@ -102,10 +103,10 @@ async function runQuicktype(
|
||||
if (justTypes) {
|
||||
// FIXME: The target language should have a property to return these options.
|
||||
if (lang.name === "csharp") {
|
||||
rendererOptions.features = "just-types";
|
||||
(rendererOptions as RendererOptions<"csharp">).features = "just-types";
|
||||
} else if (lang.name === "kotlin") {
|
||||
rendererOptions.framework = "just-types";
|
||||
} else {
|
||||
(rendererOptions as RendererOptions<"kotlin">).framework = "just-types";
|
||||
} else if ("just-types" in rendererOptions) {
|
||||
rendererOptions["just-types"] = "true";
|
||||
}
|
||||
}
|
||||
@ -202,7 +203,7 @@ async function pasteAsTypes(editor: vscode.TextEditor, kind: InputKind, justType
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error FIXME: resolve this after above ^
|
||||
// @ts-expect-error
|
||||
const text = result.lines.join("\n");
|
||||
const selection = editor.selection;
|
||||
return await editor.edit(builder => {
|
||||
@ -364,11 +365,11 @@ function deduceTargetLanguage(): TargetLanguage {
|
||||
|
||||
const sorted = Array.from(counts).sort(([_na, ca], [_nb, cb]) => cb - ca);
|
||||
for (const [name] of sorted) {
|
||||
const lang = languageNamed(name);
|
||||
if (lang !== undefined) return lang;
|
||||
if (isLanguageName(name)) {
|
||||
return languageNamed(name);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error languageNamed is not yet strongly typed
|
||||
return languageNamed("typescript");
|
||||
}
|
||||
|
||||
@ -481,7 +482,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
||||
);
|
||||
|
||||
const maybeName = extensionContext.workspaceState.get<string>(lastTargetLanguageUsedKey);
|
||||
if (typeof maybeName === "string") {
|
||||
if (typeof maybeName === "string" && isLanguageName(maybeName)) {
|
||||
explicitlySetTargetLanguage = languageNamed(maybeName);
|
||||
}
|
||||
}
|
||||
|
57
src/index.ts
57
src/index.ts
@ -20,6 +20,7 @@ import {
|
||||
JSONInput,
|
||||
JSONSchemaInput,
|
||||
type JSONSourceData,
|
||||
type LanguageName,
|
||||
type OptionDefinition,
|
||||
type Options,
|
||||
type RendererOptions,
|
||||
@ -34,6 +35,7 @@ import {
|
||||
getTargetLanguage,
|
||||
inferenceFlagNames,
|
||||
inferenceFlags,
|
||||
isLanguageName,
|
||||
languageNamed,
|
||||
messageAssert,
|
||||
messageError,
|
||||
@ -59,7 +61,7 @@ const packageJSON = require("../package.json");
|
||||
|
||||
const wordWrap: (s: string) => string = _wordwrap(90);
|
||||
|
||||
export interface CLIOptions {
|
||||
export interface CLIOptions<Lang extends LanguageName = LanguageName> {
|
||||
// We use this to access the inference flags
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[option: string]: any;
|
||||
@ -73,13 +75,13 @@ export interface CLIOptions {
|
||||
help: boolean;
|
||||
httpHeader?: string[];
|
||||
httpMethod?: string;
|
||||
lang: string;
|
||||
lang: Lang;
|
||||
|
||||
noRender: boolean;
|
||||
out?: string;
|
||||
quiet: boolean;
|
||||
|
||||
rendererOptions: RendererOptions;
|
||||
rendererOptions: RendererOptions<Lang>;
|
||||
|
||||
src: string[];
|
||||
srcLang: string;
|
||||
@ -222,7 +224,7 @@ async function samplesFromDirectory(dataDir: string, httpHeaders?: string[]): Pr
|
||||
return sources;
|
||||
}
|
||||
|
||||
function inferLang(options: Partial<CLIOptions>, defaultLanguage: string): string {
|
||||
function inferLang(options: Partial<CLIOptions>, defaultLanguage: LanguageName): string | LanguageName {
|
||||
// Output file extension determines the language if language is undefined
|
||||
if (options.out !== undefined) {
|
||||
let extension = path.extname(options.out);
|
||||
@ -272,19 +274,19 @@ function inferCLIOptions(opts: Partial<CLIOptions>, targetLanguage: TargetLangua
|
||||
language = targetLanguage;
|
||||
} else {
|
||||
const languageName = opts.lang ?? inferLang(opts, defaultDefaultTargetLanguageName);
|
||||
const maybeLanguage = languageNamed(languageName);
|
||||
if (maybeLanguage === undefined) {
|
||||
|
||||
if (isLanguageName(languageName)) {
|
||||
language = languageNamed(languageName);
|
||||
} else {
|
||||
return messageError("DriverUnknownOutputLanguage", { lang: languageName });
|
||||
}
|
||||
|
||||
language = maybeLanguage;
|
||||
}
|
||||
|
||||
const options: CLIOptions = {
|
||||
src: opts.src ?? [],
|
||||
srcUrls: opts.srcUrls,
|
||||
srcLang: srcLang,
|
||||
lang: language.displayName,
|
||||
lang: language.name as LanguageName,
|
||||
topLevel: opts.topLevel ?? inferTopLevel(opts),
|
||||
noRender: !!opts.noRender,
|
||||
alphabetizeProperties: !!opts.alphabetizeProperties,
|
||||
@ -311,7 +313,7 @@ function inferCLIOptions(opts: Partial<CLIOptions>, targetLanguage: TargetLangua
|
||||
return options;
|
||||
}
|
||||
|
||||
function makeLangTypeLabel(targetLanguages: TargetLanguage[]): string {
|
||||
function makeLangTypeLabel(targetLanguages: readonly TargetLanguage[]): string {
|
||||
assert(targetLanguages.length > 0, "Must have at least one target language");
|
||||
return targetLanguages.map(r => _.minBy(r.names, s => s.length)).join("|");
|
||||
}
|
||||
@ -331,7 +333,7 @@ function dashedFromCamelCase(name: string): string {
|
||||
.join("-");
|
||||
}
|
||||
|
||||
function makeOptionDefinitions(targetLanguages: TargetLanguage[]): OptionDefinition[] {
|
||||
function makeOptionDefinitions(targetLanguages: readonly TargetLanguage[]): OptionDefinition[] {
|
||||
const beforeLang: OptionDefinition[] = [
|
||||
{
|
||||
name: "out",
|
||||
@ -513,7 +515,7 @@ const tableOptionsForOptions: TableOptions = {
|
||||
]
|
||||
};
|
||||
|
||||
function makeSectionsBeforeRenderers(targetLanguages: TargetLanguage[]): UsageSection[] {
|
||||
function makeSectionsBeforeRenderers(targetLanguages: readonly TargetLanguage[]): UsageSection[] {
|
||||
const langDisplayNames = targetLanguages.map(r => r.displayName).join(", ");
|
||||
|
||||
return [
|
||||
@ -583,7 +585,8 @@ export function parseCLIOptions(argv: string[], targetLanguage?: TargetLanguage)
|
||||
// twice. This is the first parse to get the renderer:
|
||||
const incompleteOptions = inferCLIOptions(parseOptions(optionDefinitions, argv, true), targetLanguage);
|
||||
if (targetLanguage === undefined) {
|
||||
targetLanguage = getTargetLanguage(incompleteOptions.lang);
|
||||
const languageName = isLanguageName(incompleteOptions.lang) ? incompleteOptions.lang : "typescript";
|
||||
targetLanguage = getTargetLanguage(languageName);
|
||||
}
|
||||
|
||||
const rendererOptionDefinitions = targetLanguage.cliOptionDefinitions.actual;
|
||||
@ -615,14 +618,16 @@ function parseOptions(definitions: OptionDefinition[], argv: string[], partial:
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const options: { [key: string]: any; rendererOptions: RendererOptions } = { rendererOptions: {} };
|
||||
for (const o of definitions) {
|
||||
if (!hasOwnProperty(opts, o.name)) continue;
|
||||
const v = opts[o.name] as string;
|
||||
if (o.renderer !== undefined) options.rendererOptions[o.name] = v;
|
||||
else {
|
||||
const k = _.lowerFirst(o.name.split("-").map(_.upperFirst).join(""));
|
||||
const options: { [key: string]: unknown; rendererOptions: RendererOptions } = { rendererOptions: {} };
|
||||
for (const optionDefinition of definitions) {
|
||||
if (!hasOwnProperty(opts, optionDefinition.name)) {
|
||||
continue;
|
||||
}
|
||||
const v = opts[optionDefinition.name] as string;
|
||||
if (optionDefinition.name in options.rendererOptions) {
|
||||
(options.rendererOptions as Record<typeof optionDefinition.name, unknown>)[optionDefinition.name] = v;
|
||||
} else {
|
||||
const k = _.lowerFirst(optionDefinition.name.split("-").map(_.upperFirst).join(""));
|
||||
options[k] = v;
|
||||
}
|
||||
}
|
||||
@ -630,7 +635,7 @@ function parseOptions(definitions: OptionDefinition[], argv: string[], partial:
|
||||
return options;
|
||||
}
|
||||
|
||||
function usage(targetLanguages: TargetLanguage[]): void {
|
||||
function usage(targetLanguages: readonly TargetLanguage[]): void {
|
||||
const rendererSections: UsageSection[] = [];
|
||||
|
||||
for (const language of targetLanguages) {
|
||||
@ -711,7 +716,8 @@ export function jsonInputForTargetLanguage(
|
||||
handleJSONRefs = false
|
||||
): JSONInput<Readable> {
|
||||
if (typeof targetLanguage === "string") {
|
||||
targetLanguage = defined(languageNamed(targetLanguage, languages));
|
||||
const languageName = isLanguageName(targetLanguage) ? targetLanguage : "typescript";
|
||||
targetLanguage = defined(languageNamed(languageName, languages));
|
||||
}
|
||||
|
||||
const compressedJSON = new CompressedJSONFromStream(targetLanguage.dateTimeRecognizer, handleJSONRefs);
|
||||
@ -901,11 +907,12 @@ export async function makeQuicktypeOptions(
|
||||
}
|
||||
}
|
||||
|
||||
const lang = languageNamed(options.lang, targetLanguages);
|
||||
if (lang === undefined) {
|
||||
if (!isLanguageName(options.lang)) {
|
||||
return messageError("DriverUnknownOutputLanguage", { lang: options.lang });
|
||||
}
|
||||
|
||||
const lang = languageNamed(options.lang, targetLanguages);
|
||||
|
||||
const quicktypeOptions: Partial<Options> = {
|
||||
lang,
|
||||
alphabetizeProperties: options.alphabetizeProperties,
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
callAndExpectFailure
|
||||
} from "./utils";
|
||||
import * as languages from "./languages";
|
||||
import { RendererOptions } from "quicktype-core";
|
||||
import type { LanguageName, Option, RendererOptions } from "quicktype-core";
|
||||
import { mustNotHappen, defined } from "../packages/quicktype-core/dist/support/Support";
|
||||
import { DefaultDateTimeRecognizer } from "../packages/quicktype-core/dist/DateTime";
|
||||
|
||||
@ -80,8 +80,11 @@ function additionalTestFiles(base: string, extension: string, features: string[]
|
||||
|
||||
function runEnvForLanguage(additionalRendererOptions: RendererOptions): NodeJS.ProcessEnv {
|
||||
const newEnv = Object.assign({}, process.env);
|
||||
for (const o of Object.getOwnPropertyNames(additionalRendererOptions)) {
|
||||
newEnv["QUICKTYPE_" + o.toUpperCase().replace("-", "_")] = additionalRendererOptions[o].toString();
|
||||
|
||||
for (const option of Object.keys(additionalRendererOptions)) {
|
||||
newEnv["QUICKTYPE_" + option.toUpperCase().replace("-", "_")] = (
|
||||
additionalRendererOptions[option as keyof typeof additionalRendererOptions] as Option<string, unknown>
|
||||
).name;
|
||||
}
|
||||
return newEnv;
|
||||
}
|
||||
@ -379,7 +382,7 @@ class JSONToXToYFixture extends JSONFixture {
|
||||
|
||||
constructor(
|
||||
private readonly _fixturePrefix: string,
|
||||
languageXName: string,
|
||||
languageXName: LanguageName,
|
||||
languageXOutputFilename: string,
|
||||
rendererOptions: RendererOptions,
|
||||
skipJSON: string[],
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { type LanguageName } from "quicktype-core";
|
||||
|
||||
import * as process from "process";
|
||||
// @ts-ignore
|
||||
import { RendererOptions } from "../dist/quicktype-core/Run";
|
||||
@ -18,7 +20,7 @@ export type LanguageFeature =
|
||||
| "pattern";
|
||||
|
||||
export interface Language {
|
||||
name: string;
|
||||
name: LanguageName;
|
||||
base: string;
|
||||
setupCommand?: string;
|
||||
compileCommand?: string;
|
||||
@ -94,7 +96,7 @@ export const CSharpLanguageSystemTextJson: Language = {
|
||||
skipSchema: [
|
||||
"top-level-enum.schema" // The code we generate for top-level enums is incompatible with the driver
|
||||
],
|
||||
rendererOptions: { "check-required": "true", framework: "SystemTextJson" },
|
||||
rendererOptions: { "check-required": "true", "framework": "SystemTextJson" },
|
||||
quickTestRendererOptions: [
|
||||
{ "array-type": "list" },
|
||||
{ "csharp-version": "6" },
|
||||
@ -147,7 +149,7 @@ export const JavaLanguageWithLegacyDateTime: Language = {
|
||||
export const JavaLanguageWithLombok: Language = {
|
||||
...JavaLanguage,
|
||||
base: "test/fixtures/java-lombok",
|
||||
quickTestRendererOptions: [{ "array-type": "list", lombok: "true" }]
|
||||
quickTestRendererOptions: [{ "array-type": "list", "lombok": "true" }]
|
||||
};
|
||||
|
||||
export const PythonLanguage: Language = {
|
||||
|
Reference in New Issue
Block a user