@typescript-eslint/rule-schema-to-typescript-types 8.61.2-alpha.2 → 8.61.2-alpha.4

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.
@@ -1,9 +0,0 @@
1
- import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema';
2
-
3
- export function getCommentLines(schema: JSONSchema4): string[] {
4
- const lines: string[] = [];
5
- if (schema.description) {
6
- lines.push(schema.description);
7
- }
8
- return lines;
9
- }
package/src/index.ts DELETED
@@ -1,80 +0,0 @@
1
- import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema';
2
-
3
- import { TSUtils } from '@typescript-eslint/utils';
4
-
5
- import type { SchemaAST } from './types.js';
6
-
7
- import { generateType } from './generateType.js';
8
- import { optimizeAST } from './optimizeAST.js';
9
- import { printTypeAlias } from './printAST.js';
10
-
11
- /**
12
- * Converts rule options schema(s) to the equivalent TypeScript type string.
13
- *
14
- * @param schema Original rule schema(s) as declared in `meta.schema`.
15
- * @returns Stringified TypeScript type(s) equivalent to the options schema(s).
16
- */
17
- export function schemaToTypes(
18
- schema: JSONSchema4 | readonly JSONSchema4[],
19
- ): string {
20
- const [isArraySchema, schemaNormalized] = TSUtils.isArray(schema)
21
- ? [true, schema]
22
- : [false, [schema]];
23
-
24
- if (schemaNormalized.length === 0) {
25
- return ['/** No options declared */', 'type Options = [];'].join('\n');
26
- }
27
-
28
- const refTypes: string[] = [];
29
- const types: SchemaAST[] = [];
30
- for (let i = 0; i < schemaNormalized.length; i += 1) {
31
- const result = compileSchema(schemaNormalized[i], i);
32
- refTypes.push(...result.refTypes);
33
- types.push(result.type);
34
- }
35
-
36
- const optionsType = isArraySchema
37
- ? printTypeAlias('Options', {
38
- commentLines: [],
39
- elements: types,
40
- spreadType: null,
41
- type: 'tuple',
42
- })
43
- : printTypeAlias('Options', types[0]);
44
-
45
- return [...refTypes, optionsType].join('\n\n');
46
- }
47
-
48
- function compileSchema(
49
- schema: JSONSchema4,
50
- index: number,
51
- ): { refTypes: string[]; type: SchemaAST } {
52
- const refTypes: string[] = [];
53
-
54
- const refMap = new Map<string, string>();
55
- // we only support defs at the top level for simplicity
56
- const defs = schema.$defs ?? schema.definitions;
57
- if (defs) {
58
- for (const [defKey, defSchema] of Object.entries(defs)) {
59
- const typeName = toPascalCase(defKey);
60
- refMap.set(`#/$defs/${defKey}`, typeName);
61
- refMap.set(`#/items/${index}/$defs/${defKey}`, typeName);
62
-
63
- const type = generateType(defSchema, refMap);
64
- optimizeAST(type);
65
- refTypes.push(printTypeAlias(typeName, type));
66
- }
67
- }
68
-
69
- const type = generateType(schema, refMap);
70
- optimizeAST(type);
71
-
72
- return {
73
- refTypes,
74
- type,
75
- };
76
- }
77
-
78
- function toPascalCase(key: string): string {
79
- return key[0].toUpperCase() + key.substring(1);
80
- }
@@ -1,72 +0,0 @@
1
- import type { SchemaAST, UnionAST } from './types.js';
2
-
3
- export function optimizeAST(ast: SchemaAST | null): void {
4
- if (ast == null) {
5
- return;
6
- }
7
-
8
- switch (ast.type) {
9
- case 'array': {
10
- optimizeAST(ast.elementType);
11
- return;
12
- }
13
-
14
- case 'literal':
15
- return;
16
-
17
- case 'object': {
18
- for (const property of ast.properties) {
19
- optimizeAST(property.type);
20
- }
21
- optimizeAST(ast.indexSignature);
22
- return;
23
- }
24
-
25
- case 'tuple': {
26
- for (const element of ast.elements) {
27
- optimizeAST(element);
28
- }
29
- optimizeAST(ast.spreadType);
30
- return;
31
- }
32
-
33
- case 'type-reference':
34
- return;
35
-
36
- case 'union': {
37
- const elements = unwrapUnions(ast);
38
- for (const element of elements) {
39
- optimizeAST(element);
40
- }
41
-
42
- // hacky way to deduplicate union members
43
- const uniqueElementsMap = new Map<string, SchemaAST>();
44
- for (const element of elements) {
45
- uniqueElementsMap.set(JSON.stringify(element), element);
46
- }
47
- const uniqueElements = [...uniqueElementsMap.values()];
48
-
49
- // @ts-expect-error -- purposely overwriting the property with a flattened list
50
- ast.elements = uniqueElements;
51
- return;
52
- }
53
- }
54
- }
55
-
56
- function unwrapUnions(union: UnionAST): SchemaAST[] {
57
- const elements: SchemaAST[] = [];
58
- for (const element of union.elements) {
59
- if (element.type === 'union') {
60
- elements.push(...unwrapUnions(element));
61
- } else {
62
- elements.push(element);
63
- }
64
- }
65
-
66
- if (elements.length > 0) {
67
- // preserve the union's comment lines by prepending them to the first element's lines
68
- elements[0].commentLines.unshift(...union.commentLines);
69
- }
70
-
71
- return elements;
72
- }
package/src/printAST.ts DELETED
@@ -1,169 +0,0 @@
1
- import { ASTUtils } from '@typescript-eslint/utils';
2
- import naturalCompare from 'natural-compare';
3
-
4
- import type { SchemaAST, TupleAST } from './types.js';
5
-
6
- export function printTypeAlias(aliasName: string, ast: SchemaAST): string {
7
- return `${printComment(ast)}type ${aliasName} = ${printAST(ast).code}`;
8
- }
9
-
10
- export function printASTWithComment(ast: SchemaAST): string {
11
- const result = printAST(ast);
12
- return `${printComment(result)}${result.code}`;
13
- }
14
-
15
- function printComment({
16
- commentLines: commentLinesIn,
17
- }: {
18
- readonly commentLines?: string[] | null | undefined;
19
- }): string {
20
- if (commentLinesIn == null || commentLinesIn.length === 0) {
21
- return '';
22
- }
23
-
24
- const commentLines: string[] = [];
25
- for (const line of commentLinesIn) {
26
- commentLines.push(...line.split(ASTUtils.LINEBREAK_MATCHER));
27
- }
28
-
29
- if (commentLines.length === 1) {
30
- return `/** ${commentLines[0]} */\n`;
31
- }
32
-
33
- return ['/**', ...commentLines.map(l => ` * ${l}`), ' */', ''].join('\n');
34
- }
35
-
36
- interface CodeWithComments {
37
- code: string;
38
- commentLines: string[];
39
- }
40
- function printAST(ast: SchemaAST): CodeWithComments {
41
- switch (ast.type) {
42
- case 'array': {
43
- const code = printAndMaybeParenthesise(ast.elementType);
44
- return {
45
- code: `${code.code}[]`,
46
- commentLines: [...ast.commentLines, ...code.commentLines],
47
- };
48
- }
49
-
50
- case 'literal':
51
- return {
52
- code: ast.code,
53
- commentLines: ast.commentLines,
54
- };
55
-
56
- case 'object': {
57
- const properties = [];
58
- // sort the properties so that we get consistent output regardless
59
- // of import declaration order
60
- const sortedPropertyDefs = ast.properties.sort((a, b) =>
61
- naturalCompare(a.name, b.name),
62
- );
63
- for (const property of sortedPropertyDefs) {
64
- const result = printAST(property.type);
65
- properties.push(
66
- `${printComment(result)}${property.name}${
67
- property.optional ? '?:' : ':'
68
- } ${result.code}`,
69
- );
70
- }
71
-
72
- if (ast.indexSignature) {
73
- const result = printAST(ast.indexSignature);
74
- properties.push(`${printComment(result)}[k: string]: ${result.code}`);
75
- }
76
- return {
77
- // force insert a newline so prettier consistently prints all objects as multiline
78
- code: `{\n${properties.join(';\n')}}`,
79
- commentLines: ast.commentLines,
80
- };
81
- }
82
-
83
- case 'tuple': {
84
- const elements = [];
85
- for (const element of ast.elements) {
86
- elements.push(printASTWithComment(element));
87
- }
88
- if (ast.spreadType) {
89
- const result = printAndMaybeParenthesise(ast.spreadType);
90
- elements.push(`${printComment(result)}...${result.code}[]`);
91
- }
92
-
93
- return {
94
- code: `[${elements.join(',')}]`,
95
- commentLines: ast.commentLines,
96
- };
97
- }
98
-
99
- case 'type-reference':
100
- return {
101
- code: ast.typeName,
102
- commentLines: ast.commentLines,
103
- };
104
-
105
- case 'union':
106
- return {
107
- code: ast.elements
108
- .map(element => {
109
- const result = printAST(element);
110
- const code = `${printComment(result)} | ${result.code}`;
111
- return {
112
- code,
113
- element,
114
- };
115
- })
116
- // sort the union members so that we get consistent output regardless
117
- // of declaration order
118
- .sort((a, b) => compareElements(a, b))
119
- .map(el => el.code)
120
- .join('\n'),
121
- commentLines: ast.commentLines,
122
- };
123
- }
124
- }
125
-
126
- interface Element {
127
- code: string;
128
- element: SchemaAST;
129
- }
130
- function compareElements(a: Element, b: Element): number {
131
- if (a.element.type !== b.element.type) {
132
- return naturalCompare(a.code, b.code);
133
- }
134
-
135
- switch (a.element.type) {
136
- case 'array':
137
- case 'literal':
138
- case 'type-reference':
139
- case 'object':
140
- case 'union':
141
- return naturalCompare(a.code, b.code);
142
-
143
- case 'tuple': {
144
- // natural compare will sort longer tuples before shorter ones
145
- // which is the opposite of what we want, so we sort first by length THEN
146
- // by code to ensure shorter tuples come first
147
- const aElement = a.element;
148
- const bElement = b.element as TupleAST;
149
- if (aElement.elements.length !== bElement.elements.length) {
150
- return aElement.elements.length - bElement.elements.length;
151
- }
152
- return naturalCompare(a.code, b.code);
153
- }
154
- }
155
- }
156
-
157
- function printAndMaybeParenthesise(ast: SchemaAST): CodeWithComments {
158
- const printed = printAST(ast);
159
- if (ast.type === 'union') {
160
- return {
161
- code: `(${printed.code})`,
162
- commentLines: printed.commentLines,
163
- };
164
- }
165
- return {
166
- code: printed.code,
167
- commentLines: printed.commentLines,
168
- };
169
- }
package/src/types.ts DELETED
@@ -1,55 +0,0 @@
1
- /**
2
- * Maps ref paths to generated type names.
3
- */
4
- export type RefMap = ReadonlyMap<
5
- // ref path
6
- string,
7
- // type name
8
- string
9
- >;
10
-
11
- /**
12
- * Minimal representation of the nodes in a schema being compiled to types.
13
- */
14
- export type SchemaAST =
15
- | ArrayAST
16
- | LiteralAST
17
- | ObjectAST
18
- | TupleAST
19
- | TypeReferenceAST
20
- | UnionAST;
21
-
22
- export interface BaseSchemaASTNode {
23
- readonly commentLines: string[];
24
- }
25
-
26
- export interface ArrayAST extends BaseSchemaASTNode {
27
- readonly elementType: SchemaAST;
28
- readonly type: 'array';
29
- }
30
- export interface LiteralAST extends BaseSchemaASTNode {
31
- readonly code: string;
32
- readonly type: 'literal';
33
- }
34
- export interface ObjectAST extends BaseSchemaASTNode {
35
- readonly indexSignature: SchemaAST | null;
36
- readonly properties: {
37
- readonly name: string;
38
- readonly optional: boolean;
39
- readonly type: SchemaAST;
40
- }[];
41
- readonly type: 'object';
42
- }
43
- export interface TupleAST extends BaseSchemaASTNode {
44
- readonly elements: SchemaAST[];
45
- readonly spreadType: SchemaAST | null;
46
- readonly type: 'tuple';
47
- }
48
- export interface TypeReferenceAST extends BaseSchemaASTNode {
49
- readonly type: 'type-reference';
50
- readonly typeName: string;
51
- }
52
- export interface UnionAST extends BaseSchemaASTNode {
53
- readonly elements: SchemaAST[];
54
- readonly type: 'union';
55
- }
@@ -1,245 +0,0 @@
1
- import { schemaToTypes } from '../src/index.js';
2
-
3
- describe(schemaToTypes, () => {
4
- it('returns a [] type when the schema is an empty array', () => {
5
- const actual = schemaToTypes([]);
6
-
7
- expect(actual).toMatchInlineSnapshot(`
8
- "/** No options declared */
9
- type Options = [];"
10
- `);
11
- });
12
-
13
- it('returns a single Options type when the schema is not an array', () => {
14
- const actual = schemaToTypes({ type: 'string' });
15
-
16
- expect(actual).toMatchInlineSnapshot(`"type Options = string"`);
17
- });
18
-
19
- it('returns an array Options type when the schema is an array', () => {
20
- const actual = schemaToTypes([{ type: 'string' }]);
21
-
22
- expect(actual).toMatchInlineSnapshot(`"type Options = [string]"`);
23
- });
24
-
25
- it('returns a complex Options type when the schema contains an array of items', () => {
26
- const actual = schemaToTypes([
27
- {
28
- items: [{ type: 'string' }],
29
- type: 'array',
30
- },
31
- ]);
32
-
33
- expect(actual).toMatchInlineSnapshot(`
34
- "type Options = [ | []
35
- | [string]]"
36
- `);
37
- });
38
-
39
- it('returns a complex Options type when the schema is nested', () => {
40
- const actual = schemaToTypes([
41
- {
42
- description: 'My schema items.',
43
- items: { type: 'string' },
44
- type: 'array',
45
- },
46
- ]);
47
-
48
- expect(actual).toMatchInlineSnapshot(`
49
- "type Options = [/** My schema items. */
50
- string[]]"
51
- `);
52
- });
53
-
54
- it('returns a multiline comment when the schema description contains CRLF line terminators', () => {
55
- const actual = schemaToTypes([
56
- {
57
- description: 'My schema items.\r\nMore details.\r\nEven more details.',
58
- items: { type: 'string' },
59
- type: 'array',
60
- },
61
- ]);
62
-
63
- expect(actual).toMatchInlineSnapshot(`
64
- "type Options = [/**
65
- * My schema items.
66
- * More details.
67
- * Even more details.
68
- */
69
- string[]]"
70
- `);
71
- });
72
-
73
- it('returns a multiline comment when the schema description contains CR line terminators', () => {
74
- const actual = schemaToTypes([
75
- {
76
- description: 'My schema items.\rMore details.\rEven more details.',
77
- items: { type: 'string' },
78
- type: 'array',
79
- },
80
- ]);
81
-
82
- expect(actual).toMatchInlineSnapshot(`
83
- "type Options = [/**
84
- * My schema items.
85
- * More details.
86
- * Even more details.
87
- */
88
- string[]]"
89
- `);
90
- });
91
-
92
- it('returns a multiline comment when the schema description contains LF line terminators', () => {
93
- const actual = schemaToTypes([
94
- {
95
- description: 'My schema items.\nMore details.\nEven more details.',
96
- items: { type: 'string' },
97
- type: 'array',
98
- },
99
- ]);
100
-
101
- expect(actual).toMatchInlineSnapshot(`
102
- "type Options = [/**
103
- * My schema items.
104
- * More details.
105
- * Even more details.
106
- */
107
- string[]]"
108
- `);
109
- });
110
-
111
- it('returns a multiline comment when the schema description contains LS line terminators', () => {
112
- const actual = schemaToTypes([
113
- {
114
- description:
115
- 'My schema items.\u2028More details.\u2028Even more details.',
116
- items: { type: 'string' },
117
- type: 'array',
118
- },
119
- ]);
120
-
121
- expect(actual).toMatchInlineSnapshot(`
122
- "type Options = [/**
123
- * My schema items.
124
- * More details.
125
- * Even more details.
126
- */
127
- string[]]"
128
- `);
129
- });
130
-
131
- it('returns a multiline comment when the schema description contains PS line terminators', () => {
132
- const actual = schemaToTypes([
133
- {
134
- description:
135
- 'My schema items.\u2029More details.\u2029Even more details.',
136
- items: { type: 'string' },
137
- type: 'array',
138
- },
139
- ]);
140
-
141
- expect(actual).toMatchInlineSnapshot(`
142
- "type Options = [/**
143
- * My schema items.
144
- * More details.
145
- * Even more details.
146
- */
147
- string[]]"
148
- `);
149
- });
150
-
151
- it('factors in one $ref property when a $defs property exists at the top level', () => {
152
- const actual = schemaToTypes([
153
- {
154
- $defs: {
155
- defOption: {
156
- enum: ['a', 'b'],
157
- type: 'string',
158
- },
159
- },
160
- additionalProperties: false,
161
- properties: {
162
- one: {
163
- $ref: '#/items/0/$defs/defOption',
164
- },
165
- },
166
- type: 'object',
167
- },
168
- ]);
169
-
170
- expect(actual).toMatchInlineSnapshot(`
171
- "type DefOption = | 'a'
172
- | 'b'
173
-
174
- type Options = [{
175
- one?: DefOption}]"
176
- `);
177
- });
178
-
179
- it('factors in one $ref property when a definitions property exists at the top level', () => {
180
- const actual = schemaToTypes([
181
- {
182
- additionalProperties: false,
183
- definitions: {
184
- defOption: {
185
- enum: ['a', 'b'],
186
- type: 'string',
187
- },
188
- },
189
- properties: {
190
- one: {
191
- $ref: '#/items/0/$defs/defOption',
192
- },
193
- },
194
- type: 'object',
195
- },
196
- ]);
197
-
198
- expect(actual).toMatchInlineSnapshot(`
199
- "type DefOption = | 'a'
200
- | 'b'
201
-
202
- type Options = [{
203
- one?: DefOption}]"
204
- `);
205
- });
206
-
207
- it('factors in two $ref properties when two $defs properties exist at the top level', () => {
208
- const actual = schemaToTypes([
209
- {
210
- $defs: {
211
- defOptionOne: {
212
- enum: ['a', 'b'],
213
- type: 'string',
214
- },
215
- defOptionTwo: {
216
- enum: ['c', 'd'],
217
- type: 'string',
218
- },
219
- },
220
- additionalProperties: false,
221
- properties: {
222
- one: {
223
- $ref: '#/items/0/$defs/defOptionOne',
224
- },
225
- two: {
226
- $ref: '#/items/0/$defs/defOptionTwo',
227
- },
228
- },
229
- type: 'object',
230
- },
231
- ]);
232
-
233
- expect(actual).toMatchInlineSnapshot(`
234
- "type DefOptionOne = | 'a'
235
- | 'b'
236
-
237
- type DefOptionTwo = | 'c'
238
- | 'd'
239
-
240
- type Options = [{
241
- one?: DefOptionOne;
242
- two?: DefOptionTwo}]"
243
- `);
244
- });
245
- });
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.build.json",
3
- "compilerOptions": {},
4
- "references": [
5
- {
6
- "path": "../utils/tsconfig.build.json"
7
- },
8
- {
9
- "path": "../type-utils/tsconfig.build.json"
10
- }
11
- ]
12
- }
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "files": [],
4
- "include": [],
5
- "references": [
6
- {
7
- "path": "./tsconfig.build.json"
8
- },
9
- {
10
- "path": "./tsconfig.spec.json"
11
- }
12
- ]
13
- }
@@ -1,14 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "../../dist/packages/rule-schema-to-typescript-types"
5
- },
6
- "references": [
7
- {
8
- "path": "./tsconfig.build.json"
9
- },
10
- {
11
- "path": "../../tsconfig.spec.json"
12
- }
13
- ]
14
- }
package/vitest.config.mts DELETED
@@ -1,22 +0,0 @@
1
- import * as path from 'node:path';
2
- import { defineConfig, mergeConfig } from 'vitest/config';
3
-
4
- import { vitestBaseConfig } from '../../vitest.config.base.mjs';
5
- import packageJson from './package.json' with { type: 'json' };
6
-
7
- const vitestConfig = mergeConfig(
8
- vitestBaseConfig,
9
-
10
- defineConfig({
11
- root: import.meta.dirname,
12
-
13
- test: {
14
- dir: path.join(import.meta.dirname, 'tests'),
15
- name: packageJson.name.replace('@typescript-eslint/', ''),
16
- passWithNoTests: true,
17
- root: import.meta.dirname,
18
- },
19
- }),
20
- );
21
-
22
- export default vitestConfig;