@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/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.
@@ -0,0 +1,6 @@
1
+ export declare class NotSupportedError extends Error {
2
+ constructor(thing: string, target: unknown);
3
+ }
4
+ export declare class UnexpectedError extends Error {
5
+ constructor(error: string, target: unknown);
6
+ }
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,3 @@
1
+ import type { JSONSchema4ObjectSchema } from '@typescript-eslint/utils/json-schema';
2
+ import type { ObjectAST, RefMap } from './types.js';
3
+ export declare function generateObjectType(schema: JSONSchema4ObjectSchema, refMap: RefMap): ObjectAST;
@@ -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,3 @@
1
+ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema';
2
+ import type { SchemaAST, RefMap } from './types.js';
3
+ export declare function generateType(schema: JSONSchema4, refMap: RefMap): SchemaAST;
@@ -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,3 @@
1
+ import type { JSONSchema4, JSONSchema4Type } from '@typescript-eslint/utils/json-schema';
2
+ import type { RefMap, UnionAST } from './types.js';
3
+ export declare function generateUnionType(members: (JSONSchema4 | JSONSchema4Type)[], refMap: RefMap): UnionAST;
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema';
2
+ export declare function getCommentLines(schema: JSONSchema4): string[];
@@ -0,0 +1,7 @@
1
+ export function getCommentLines(schema) {
2
+ const lines = [];
3
+ if (schema.description) {
4
+ lines.push(schema.description);
5
+ }
6
+ return lines;
7
+ }
@@ -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,2 @@
1
+ import type { SchemaAST } from './types.js';
2
+ export declare function optimizeAST(ast: SchemaAST | null): void;
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import type { SchemaAST } from './types.js';
2
+ export declare function printTypeAlias(aliasName: string, ast: SchemaAST): string;
3
+ export declare function printASTWithComment(ast: SchemaAST): string;
@@ -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
+ }