@typescript-eslint/rule-schema-to-typescript-types 0.0.0 → 8.45.1-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1003 -0
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +10 -0
- package/dist/generateArrayType.d.ts +3 -0
- package/dist/generateArrayType.js +107 -0
- package/dist/generateObjectType.d.ts +3 -0
- package/dist/generateObjectType.js +43 -0
- package/dist/generateType.d.ts +3 -0
- package/dist/generateType.js +99 -0
- package/dist/generateUnionType.d.ts +3 -0
- package/dist/generateUnionType.js +37 -0
- package/dist/getCommentLines.d.ts +2 -0
- package/dist/getCommentLines.js +7 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +59 -0
- package/dist/optimizeAST.d.ts +2 -0
- package/dist/optimizeAST.js +60 -0
- package/dist/printAST.d.ts +3 -0
- package/dist/printAST.js +130 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.js +1 -0
- package/package.json +58 -1
- package/src/errors.ts +17 -0
- package/src/generateArrayType.ts +150 -0
- package/src/generateObjectType.ts +58 -0
- package/src/generateType.ts +126 -0
- package/src/generateUnionType.ts +54 -0
- package/src/getCommentLines.ts +9 -0
- package/src/index.ts +80 -0
- package/src/optimizeAST.ts +72 -0
- package/src/printAST.ts +168 -0
- package/src/types.ts +55 -0
- package/tests/index.test.ts +148 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +19 -0
- package/tsconfig.spec.json +14 -0
- package/vitest.config.mts +22 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 typescript-eslint and other contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# `@typescript-eslint/rule-schema-to-typescript-types`
|
|
2
|
+
|
|
3
|
+
> Tool for generating TypeScript type definitions from a rule's options schema
|
|
4
|
+
|
|
5
|
+
## ✋ Internal Package
|
|
6
|
+
|
|
7
|
+
This is an _internal package_ to the [typescript-eslint monorepo](https://github.com/typescript-eslint/typescript-eslint).
|
|
8
|
+
|
|
9
|
+
👉 See **https://typescript-eslint.io** for docs on typescript-eslint.
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export class NotSupportedError extends Error {
|
|
2
|
+
constructor(thing, target) {
|
|
3
|
+
super(`Generating a type for ${thing} is not currently supported:\n${JSON.stringify(target, null, 2)}`);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export class UnexpectedError extends Error {
|
|
7
|
+
constructor(error, target) {
|
|
8
|
+
super(`Unexpected Error: ${error}:\n${JSON.stringify(target, null, 2)}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { JSONSchema4ArraySchema } from '@typescript-eslint/utils/json-schema';
|
|
2
|
+
import type { ArrayAST, RefMap, TupleAST, UnionAST } from './types.js';
|
|
3
|
+
export declare function generateArrayType(schema: JSONSchema4ArraySchema, refMap: RefMap): ArrayAST | TupleAST | UnionAST;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { TSUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { NotSupportedError, UnexpectedError } from './errors.js';
|
|
3
|
+
import { generateType } from './generateType.js';
|
|
4
|
+
import { getCommentLines } from './getCommentLines.js';
|
|
5
|
+
/**
|
|
6
|
+
* If there are more than 20 tuple items then we will not make it a tuple type
|
|
7
|
+
* and instead will just make it an array type for brevity
|
|
8
|
+
*/
|
|
9
|
+
const MAX_ITEMS_TO_TUPLIZE = 20;
|
|
10
|
+
export function generateArrayType(schema, refMap) {
|
|
11
|
+
if (!schema.items) {
|
|
12
|
+
// it's technically valid to declare things like {type: 'array'} -> any[]
|
|
13
|
+
// but that's obviously dumb and loose so let's not even bother with it
|
|
14
|
+
throw new UnexpectedError('Unexpected missing items', schema);
|
|
15
|
+
}
|
|
16
|
+
if (!TSUtils.isArray(schema.items) && schema.additionalItems) {
|
|
17
|
+
throw new NotSupportedError('singlely-typed array with additionalItems', schema);
|
|
18
|
+
}
|
|
19
|
+
const commentLines = getCommentLines(schema);
|
|
20
|
+
const minItems = schema.minItems ?? 0;
|
|
21
|
+
const maxItems = schema.maxItems != null && schema.maxItems < MAX_ITEMS_TO_TUPLIZE
|
|
22
|
+
? schema.maxItems
|
|
23
|
+
: -1;
|
|
24
|
+
const hasMaxItems = maxItems >= 0;
|
|
25
|
+
if (!TSUtils.isArray(schema.items)) {
|
|
26
|
+
// While we could support `minItems` and `maxItems` with tuple types,
|
|
27
|
+
// for example `[T, ...T[]]`, it harms readability for documentation purposes.
|
|
28
|
+
// See https://github.com/typescript-eslint/typescript-eslint/issues/11117
|
|
29
|
+
return {
|
|
30
|
+
commentLines,
|
|
31
|
+
elementType: generateType(schema.items, refMap),
|
|
32
|
+
type: 'array',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// treat as a tuple
|
|
36
|
+
const items = schema.items;
|
|
37
|
+
let spreadItemSchema = null;
|
|
38
|
+
if (hasMaxItems && items.length < maxItems) {
|
|
39
|
+
spreadItemSchema =
|
|
40
|
+
typeof schema.additionalItems === 'object'
|
|
41
|
+
? schema.additionalItems
|
|
42
|
+
: { type: 'any' };
|
|
43
|
+
}
|
|
44
|
+
// quick validation so we generate sensible types
|
|
45
|
+
if (hasMaxItems && maxItems < items.length) {
|
|
46
|
+
throw new UnexpectedError(`maxItems (${maxItems}) is smaller than the number of items schemas provided (${items.length})`, schema);
|
|
47
|
+
}
|
|
48
|
+
if (maxItems > items.length && spreadItemSchema == null) {
|
|
49
|
+
throw new UnexpectedError('maxItems is larger than the number of items schemas, but there was not an additionalItems schema provided', schema);
|
|
50
|
+
}
|
|
51
|
+
const itemTypes = items.map(i => generateType(i, refMap));
|
|
52
|
+
const spreadItem = spreadItemSchema == null ? null : generateType(spreadItemSchema, refMap);
|
|
53
|
+
if (itemTypes.length > minItems) {
|
|
54
|
+
/*
|
|
55
|
+
if there are more items than the min, we return a union of tuples instead of
|
|
56
|
+
using the optional element operator. This is done because it is more type-safe.
|
|
57
|
+
|
|
58
|
+
// optional element operator
|
|
59
|
+
type A = [string, string?, string?]
|
|
60
|
+
const a: A = ['a', undefined, 'c'] // no error
|
|
61
|
+
|
|
62
|
+
// union of tuples
|
|
63
|
+
type B = [string] | [string, string] | [string, string, string]
|
|
64
|
+
const b: B = ['a', undefined, 'c'] // TS error
|
|
65
|
+
*/
|
|
66
|
+
const cumulativeTypesList = itemTypes.slice(0, minItems);
|
|
67
|
+
const typesToUnion = [];
|
|
68
|
+
if (cumulativeTypesList.length > 0) {
|
|
69
|
+
// actually has minItems, so add the initial state
|
|
70
|
+
typesToUnion.push(createTupleType(cumulativeTypesList));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// no minItems means it's acceptable to have an empty tuple type
|
|
74
|
+
typesToUnion.push(createTupleType([]));
|
|
75
|
+
}
|
|
76
|
+
for (let i = minItems; i < itemTypes.length; i += 1) {
|
|
77
|
+
cumulativeTypesList.push(itemTypes[i]);
|
|
78
|
+
if (i === itemTypes.length - 1) {
|
|
79
|
+
// only the last item in the union should have the spread parameter
|
|
80
|
+
typesToUnion.push(createTupleType(cumulativeTypesList, spreadItem));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
typesToUnion.push(createTupleType(cumulativeTypesList));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
commentLines,
|
|
88
|
+
elements: typesToUnion,
|
|
89
|
+
type: 'union',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
commentLines,
|
|
94
|
+
elements: itemTypes,
|
|
95
|
+
spreadType: spreadItem,
|
|
96
|
+
type: 'tuple',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function createTupleType(elements, spreadType = null) {
|
|
100
|
+
return {
|
|
101
|
+
type: 'tuple',
|
|
102
|
+
// clone the array because we know we'll keep mutating it
|
|
103
|
+
commentLines: [],
|
|
104
|
+
elements: [...elements],
|
|
105
|
+
spreadType,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { requiresQuoting } from '@typescript-eslint/type-utils';
|
|
2
|
+
import { TSUtils } from '@typescript-eslint/utils';
|
|
3
|
+
import { generateType } from './generateType.js';
|
|
4
|
+
import { getCommentLines } from './getCommentLines.js';
|
|
5
|
+
export function generateObjectType(schema, refMap) {
|
|
6
|
+
const commentLines = getCommentLines(schema);
|
|
7
|
+
let indexSignature = null;
|
|
8
|
+
if (schema.additionalProperties === true ||
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/internal/eqeq-nullish
|
|
10
|
+
schema.additionalProperties === undefined) {
|
|
11
|
+
indexSignature = {
|
|
12
|
+
commentLines: [],
|
|
13
|
+
type: 'type-reference',
|
|
14
|
+
typeName: 'unknown',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
else if (typeof schema.additionalProperties === 'object') {
|
|
18
|
+
const indexSigType = generateType(schema.additionalProperties, refMap);
|
|
19
|
+
indexSignature = indexSigType;
|
|
20
|
+
}
|
|
21
|
+
const properties = [];
|
|
22
|
+
const required = new Set(TSUtils.isArray(schema.required) ? schema.required : []);
|
|
23
|
+
if (schema.properties) {
|
|
24
|
+
const propertyDefs = Object.entries(schema.properties);
|
|
25
|
+
for (const [propName, propSchema] of propertyDefs) {
|
|
26
|
+
const propType = generateType(propSchema, refMap);
|
|
27
|
+
const sanitisedPropName = requiresQuoting(propName)
|
|
28
|
+
? `'${propName}'`
|
|
29
|
+
: propName;
|
|
30
|
+
properties.push({
|
|
31
|
+
name: sanitisedPropName,
|
|
32
|
+
optional: !required.has(propName),
|
|
33
|
+
type: propType,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
commentLines,
|
|
39
|
+
indexSignature,
|
|
40
|
+
properties,
|
|
41
|
+
type: 'object',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TSUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { NotSupportedError, UnexpectedError } from './errors.js';
|
|
3
|
+
import { generateArrayType } from './generateArrayType.js';
|
|
4
|
+
import { generateObjectType } from './generateObjectType.js';
|
|
5
|
+
import { generateUnionType } from './generateUnionType.js';
|
|
6
|
+
import { getCommentLines } from './getCommentLines.js';
|
|
7
|
+
// keywords we probably should support but currently do not support
|
|
8
|
+
const UNSUPPORTED_KEYWORDS = new Set([
|
|
9
|
+
'allOf',
|
|
10
|
+
'dependencies',
|
|
11
|
+
'extends',
|
|
12
|
+
'maxProperties',
|
|
13
|
+
'minProperties',
|
|
14
|
+
'multipleOf',
|
|
15
|
+
'not',
|
|
16
|
+
'patternProperties',
|
|
17
|
+
]);
|
|
18
|
+
export function generateType(schema, refMap) {
|
|
19
|
+
const unsupportedProps = Object.keys(schema).filter(key => UNSUPPORTED_KEYWORDS.has(key));
|
|
20
|
+
if (unsupportedProps.length > 0) {
|
|
21
|
+
throw new NotSupportedError(unsupportedProps.join(','), schema);
|
|
22
|
+
}
|
|
23
|
+
const commentLines = getCommentLines(schema);
|
|
24
|
+
if (schema.$ref) {
|
|
25
|
+
const refName = refMap.get(schema.$ref);
|
|
26
|
+
if (refName == null) {
|
|
27
|
+
throw new UnexpectedError(`Could not find definition for $ref ${schema.$ref}.\nAvailable refs:\n${[...refMap.keys()].join('\n')})`, schema);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
commentLines,
|
|
31
|
+
type: 'type-reference',
|
|
32
|
+
typeName: refName,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if ('enum' in schema && schema.enum) {
|
|
36
|
+
return {
|
|
37
|
+
...generateUnionType(schema.enum, refMap),
|
|
38
|
+
commentLines,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if ('anyOf' in schema && schema.anyOf) {
|
|
42
|
+
return {
|
|
43
|
+
// a union isn't *TECHNICALLY* correct - technically anyOf is actually
|
|
44
|
+
// anyOf: [T, U, V] -> T | U | V | T & U | T & V | U & V
|
|
45
|
+
// in practice though it is most used to emulate a oneOf
|
|
46
|
+
...generateUnionType(schema.anyOf, refMap),
|
|
47
|
+
commentLines,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if ('oneOf' in schema && schema.oneOf) {
|
|
51
|
+
return {
|
|
52
|
+
...generateUnionType(schema.oneOf, refMap),
|
|
53
|
+
commentLines,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (!('type' in schema) || schema.type == null) {
|
|
57
|
+
throw new NotSupportedError('untyped schemas without one of [$ref, enum, oneOf]', schema);
|
|
58
|
+
}
|
|
59
|
+
if (TSUtils.isArray(schema.type)) {
|
|
60
|
+
throw new NotSupportedError('schemas with multiple types', schema);
|
|
61
|
+
}
|
|
62
|
+
switch (schema.type) {
|
|
63
|
+
case 'any':
|
|
64
|
+
return {
|
|
65
|
+
commentLines,
|
|
66
|
+
type: 'type-reference',
|
|
67
|
+
typeName: 'unknown',
|
|
68
|
+
};
|
|
69
|
+
case 'null':
|
|
70
|
+
return {
|
|
71
|
+
commentLines,
|
|
72
|
+
type: 'type-reference',
|
|
73
|
+
typeName: 'null',
|
|
74
|
+
};
|
|
75
|
+
case 'number':
|
|
76
|
+
case 'string':
|
|
77
|
+
return {
|
|
78
|
+
code: schema.type,
|
|
79
|
+
commentLines,
|
|
80
|
+
type: 'literal',
|
|
81
|
+
};
|
|
82
|
+
case 'array':
|
|
83
|
+
return generateArrayType(schema, refMap);
|
|
84
|
+
case 'boolean':
|
|
85
|
+
return {
|
|
86
|
+
commentLines,
|
|
87
|
+
type: 'type-reference',
|
|
88
|
+
typeName: 'boolean',
|
|
89
|
+
};
|
|
90
|
+
case 'integer':
|
|
91
|
+
return {
|
|
92
|
+
commentLines,
|
|
93
|
+
type: 'type-reference',
|
|
94
|
+
typeName: 'number',
|
|
95
|
+
};
|
|
96
|
+
case 'object':
|
|
97
|
+
return generateObjectType(schema, refMap);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NotSupportedError } from './errors.js';
|
|
2
|
+
import { generateType } from './generateType.js';
|
|
3
|
+
export function generateUnionType(members, refMap) {
|
|
4
|
+
const elements = [];
|
|
5
|
+
for (const memberSchema of members) {
|
|
6
|
+
elements.push((() => {
|
|
7
|
+
switch (typeof memberSchema) {
|
|
8
|
+
case 'string':
|
|
9
|
+
return {
|
|
10
|
+
code: `'${memberSchema.replaceAll("'", "\\'")}'`,
|
|
11
|
+
commentLines: [],
|
|
12
|
+
type: 'literal',
|
|
13
|
+
};
|
|
14
|
+
case 'number':
|
|
15
|
+
case 'boolean':
|
|
16
|
+
return {
|
|
17
|
+
code: `${memberSchema}`,
|
|
18
|
+
commentLines: [],
|
|
19
|
+
type: 'literal',
|
|
20
|
+
};
|
|
21
|
+
case 'object':
|
|
22
|
+
if (memberSchema == null) {
|
|
23
|
+
throw new NotSupportedError('null in an enum', memberSchema);
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(memberSchema)) {
|
|
26
|
+
throw new NotSupportedError('array in an enum', memberSchema);
|
|
27
|
+
}
|
|
28
|
+
return generateType(memberSchema, refMap);
|
|
29
|
+
}
|
|
30
|
+
})());
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
commentLines: [],
|
|
34
|
+
elements,
|
|
35
|
+
type: 'union',
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema';
|
|
2
|
+
/**
|
|
3
|
+
* Converts rule options schema(s) to the equivalent TypeScript type string.
|
|
4
|
+
*
|
|
5
|
+
* @param schema Original rule schema(s) as declared in `meta.schema`.
|
|
6
|
+
* @returns Stringified TypeScript type(s) equivalent to the options schema(s).
|
|
7
|
+
*/
|
|
8
|
+
export declare function schemaToTypes(schema: JSONSchema4 | readonly JSONSchema4[]): string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { TSUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { generateType } from './generateType.js';
|
|
3
|
+
import { optimizeAST } from './optimizeAST.js';
|
|
4
|
+
import { printTypeAlias } from './printAST.js';
|
|
5
|
+
/**
|
|
6
|
+
* Converts rule options schema(s) to the equivalent TypeScript type string.
|
|
7
|
+
*
|
|
8
|
+
* @param schema Original rule schema(s) as declared in `meta.schema`.
|
|
9
|
+
* @returns Stringified TypeScript type(s) equivalent to the options schema(s).
|
|
10
|
+
*/
|
|
11
|
+
export function schemaToTypes(schema) {
|
|
12
|
+
const [isArraySchema, schemaNormalized] = TSUtils.isArray(schema)
|
|
13
|
+
? [true, schema]
|
|
14
|
+
: [false, [schema]];
|
|
15
|
+
if (schemaNormalized.length === 0) {
|
|
16
|
+
return ['/** No options declared */', 'type Options = [];'].join('\n');
|
|
17
|
+
}
|
|
18
|
+
const refTypes = [];
|
|
19
|
+
const types = [];
|
|
20
|
+
for (let i = 0; i < schemaNormalized.length; i += 1) {
|
|
21
|
+
const result = compileSchema(schemaNormalized[i], i);
|
|
22
|
+
refTypes.push(...result.refTypes);
|
|
23
|
+
types.push(result.type);
|
|
24
|
+
}
|
|
25
|
+
const optionsType = isArraySchema
|
|
26
|
+
? printTypeAlias('Options', {
|
|
27
|
+
commentLines: [],
|
|
28
|
+
elements: types,
|
|
29
|
+
spreadType: null,
|
|
30
|
+
type: 'tuple',
|
|
31
|
+
})
|
|
32
|
+
: printTypeAlias('Options', types[0]);
|
|
33
|
+
return [...refTypes, optionsType].join('\n\n');
|
|
34
|
+
}
|
|
35
|
+
function compileSchema(schema, index) {
|
|
36
|
+
const refTypes = [];
|
|
37
|
+
const refMap = new Map();
|
|
38
|
+
// we only support defs at the top level for simplicity
|
|
39
|
+
const defs = schema.$defs ?? schema.definitions;
|
|
40
|
+
if (defs) {
|
|
41
|
+
for (const [defKey, defSchema] of Object.entries(defs)) {
|
|
42
|
+
const typeName = toPascalCase(defKey);
|
|
43
|
+
refMap.set(`#/$defs/${defKey}`, typeName);
|
|
44
|
+
refMap.set(`#/items/${index}/$defs/${defKey}`, typeName);
|
|
45
|
+
const type = generateType(defSchema, refMap);
|
|
46
|
+
optimizeAST(type);
|
|
47
|
+
refTypes.push(printTypeAlias(typeName, type));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const type = generateType(schema, refMap);
|
|
51
|
+
optimizeAST(type);
|
|
52
|
+
return {
|
|
53
|
+
refTypes,
|
|
54
|
+
type,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function toPascalCase(key) {
|
|
58
|
+
return key[0].toUpperCase() + key.substring(1);
|
|
59
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function optimizeAST(ast) {
|
|
2
|
+
if (ast == null) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
switch (ast.type) {
|
|
6
|
+
case 'array': {
|
|
7
|
+
optimizeAST(ast.elementType);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
case 'literal':
|
|
11
|
+
return;
|
|
12
|
+
case 'object': {
|
|
13
|
+
for (const property of ast.properties) {
|
|
14
|
+
optimizeAST(property.type);
|
|
15
|
+
}
|
|
16
|
+
optimizeAST(ast.indexSignature);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
case 'tuple': {
|
|
20
|
+
for (const element of ast.elements) {
|
|
21
|
+
optimizeAST(element);
|
|
22
|
+
}
|
|
23
|
+
optimizeAST(ast.spreadType);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
case 'type-reference':
|
|
27
|
+
return;
|
|
28
|
+
case 'union': {
|
|
29
|
+
const elements = unwrapUnions(ast);
|
|
30
|
+
for (const element of elements) {
|
|
31
|
+
optimizeAST(element);
|
|
32
|
+
}
|
|
33
|
+
// hacky way to deduplicate union members
|
|
34
|
+
const uniqueElementsMap = new Map();
|
|
35
|
+
for (const element of elements) {
|
|
36
|
+
uniqueElementsMap.set(JSON.stringify(element), element);
|
|
37
|
+
}
|
|
38
|
+
const uniqueElements = [...uniqueElementsMap.values()];
|
|
39
|
+
// @ts-expect-error -- purposely overwriting the property with a flattened list
|
|
40
|
+
ast.elements = uniqueElements;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function unwrapUnions(union) {
|
|
46
|
+
const elements = [];
|
|
47
|
+
for (const element of union.elements) {
|
|
48
|
+
if (element.type === 'union') {
|
|
49
|
+
elements.push(...unwrapUnions(element));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
elements.push(element);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (elements.length > 0) {
|
|
56
|
+
// preserve the union's comment lines by prepending them to the first element's lines
|
|
57
|
+
elements[0].commentLines.unshift(...union.commentLines);
|
|
58
|
+
}
|
|
59
|
+
return elements;
|
|
60
|
+
}
|
package/dist/printAST.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import naturalCompare from 'natural-compare';
|
|
2
|
+
export function printTypeAlias(aliasName, ast) {
|
|
3
|
+
return `${printComment(ast)}type ${aliasName} = ${printAST(ast).code}`;
|
|
4
|
+
}
|
|
5
|
+
export function printASTWithComment(ast) {
|
|
6
|
+
const result = printAST(ast);
|
|
7
|
+
return `${printComment(result)}${result.code}`;
|
|
8
|
+
}
|
|
9
|
+
function printComment({ commentLines: commentLinesIn, }) {
|
|
10
|
+
if (commentLinesIn == null || commentLinesIn.length === 0) {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
const commentLines = [];
|
|
14
|
+
for (const line of commentLinesIn) {
|
|
15
|
+
commentLines.push(...line.split('\n'));
|
|
16
|
+
}
|
|
17
|
+
if (commentLines.length === 1) {
|
|
18
|
+
return `/** ${commentLines[0]} */\n`;
|
|
19
|
+
}
|
|
20
|
+
return ['/**', ...commentLines.map(l => ` * ${l}`), ' */', ''].join('\n');
|
|
21
|
+
}
|
|
22
|
+
function printAST(ast) {
|
|
23
|
+
switch (ast.type) {
|
|
24
|
+
case 'array': {
|
|
25
|
+
const code = printAndMaybeParenthesise(ast.elementType);
|
|
26
|
+
return {
|
|
27
|
+
code: `${code.code}[]`,
|
|
28
|
+
commentLines: [...ast.commentLines, ...code.commentLines],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
case 'literal':
|
|
32
|
+
return {
|
|
33
|
+
code: ast.code,
|
|
34
|
+
commentLines: ast.commentLines,
|
|
35
|
+
};
|
|
36
|
+
case 'object': {
|
|
37
|
+
const properties = [];
|
|
38
|
+
// sort the properties so that we get consistent output regardless
|
|
39
|
+
// of import declaration order
|
|
40
|
+
const sortedPropertyDefs = ast.properties.sort((a, b) => naturalCompare(a.name, b.name));
|
|
41
|
+
for (const property of sortedPropertyDefs) {
|
|
42
|
+
const result = printAST(property.type);
|
|
43
|
+
properties.push(`${printComment(result)}${property.name}${property.optional ? '?:' : ':'} ${result.code}`);
|
|
44
|
+
}
|
|
45
|
+
if (ast.indexSignature) {
|
|
46
|
+
const result = printAST(ast.indexSignature);
|
|
47
|
+
properties.push(`${printComment(result)}[k: string]: ${result.code}`);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
// force insert a newline so prettier consistently prints all objects as multiline
|
|
51
|
+
code: `{\n${properties.join(';\n')}}`,
|
|
52
|
+
commentLines: ast.commentLines,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
case 'tuple': {
|
|
56
|
+
const elements = [];
|
|
57
|
+
for (const element of ast.elements) {
|
|
58
|
+
elements.push(printASTWithComment(element));
|
|
59
|
+
}
|
|
60
|
+
if (ast.spreadType) {
|
|
61
|
+
const result = printAndMaybeParenthesise(ast.spreadType);
|
|
62
|
+
elements.push(`${printComment(result)}...${result.code}[]`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
code: `[${elements.join(',')}]`,
|
|
66
|
+
commentLines: ast.commentLines,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
case 'type-reference':
|
|
70
|
+
return {
|
|
71
|
+
code: ast.typeName,
|
|
72
|
+
commentLines: ast.commentLines,
|
|
73
|
+
};
|
|
74
|
+
case 'union':
|
|
75
|
+
return {
|
|
76
|
+
code: ast.elements
|
|
77
|
+
.map(element => {
|
|
78
|
+
const result = printAST(element);
|
|
79
|
+
const code = `${printComment(result)} | ${result.code}`;
|
|
80
|
+
return {
|
|
81
|
+
code,
|
|
82
|
+
element,
|
|
83
|
+
};
|
|
84
|
+
})
|
|
85
|
+
// sort the union members so that we get consistent output regardless
|
|
86
|
+
// of declaration order
|
|
87
|
+
.sort((a, b) => compareElements(a, b))
|
|
88
|
+
.map(el => el.code)
|
|
89
|
+
.join('\n'),
|
|
90
|
+
commentLines: ast.commentLines,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function compareElements(a, b) {
|
|
95
|
+
if (a.element.type !== b.element.type) {
|
|
96
|
+
return naturalCompare(a.code, b.code);
|
|
97
|
+
}
|
|
98
|
+
switch (a.element.type) {
|
|
99
|
+
case 'array':
|
|
100
|
+
case 'literal':
|
|
101
|
+
case 'type-reference':
|
|
102
|
+
case 'object':
|
|
103
|
+
case 'union':
|
|
104
|
+
return naturalCompare(a.code, b.code);
|
|
105
|
+
case 'tuple': {
|
|
106
|
+
// natural compare will sort longer tuples before shorter ones
|
|
107
|
+
// which is the opposite of what we want, so we sort first by length THEN
|
|
108
|
+
// by code to ensure shorter tuples come first
|
|
109
|
+
const aElement = a.element;
|
|
110
|
+
const bElement = b.element;
|
|
111
|
+
if (aElement.elements.length !== bElement.elements.length) {
|
|
112
|
+
return aElement.elements.length - bElement.elements.length;
|
|
113
|
+
}
|
|
114
|
+
return naturalCompare(a.code, b.code);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function printAndMaybeParenthesise(ast) {
|
|
119
|
+
const printed = printAST(ast);
|
|
120
|
+
if (ast.type === 'union') {
|
|
121
|
+
return {
|
|
122
|
+
code: `(${printed.code})`,
|
|
123
|
+
commentLines: printed.commentLines,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
code: printed.code,
|
|
128
|
+
commentLines: printed.commentLines,
|
|
129
|
+
};
|
|
130
|
+
}
|