@zimic/http 0.0.1-canary.2
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.md +16 -0
- package/README.md +230 -0
- package/dist/chunk-VHQRAQPQ.mjs +1371 -0
- package/dist/chunk-VHQRAQPQ.mjs.map +1 -0
- package/dist/chunk-VUDGONB5.js +1382 -0
- package/dist/chunk-VUDGONB5.js.map +1 -0
- package/dist/cli.js +116 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +109 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.ts +1306 -0
- package/dist/index.js +544 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +537 -0
- package/dist/index.mjs.map +1 -0
- package/dist/typegen.d.ts +86 -0
- package/dist/typegen.js +12 -0
- package/dist/typegen.js.map +1 -0
- package/dist/typegen.mjs +3 -0
- package/dist/typegen.mjs.map +1 -0
- package/index.d.ts +1 -0
- package/package.json +110 -0
- package/src/cli/cli.ts +92 -0
- package/src/cli/index.ts +4 -0
- package/src/cli/typegen/openapi.ts +24 -0
- package/src/formData/HttpFormData.ts +300 -0
- package/src/formData/types.ts +110 -0
- package/src/headers/HttpHeaders.ts +217 -0
- package/src/headers/types.ts +65 -0
- package/src/index.ts +55 -0
- package/src/pathParams/types.ts +67 -0
- package/src/searchParams/HttpSearchParams.ts +258 -0
- package/src/searchParams/types.ts +133 -0
- package/src/typegen/index.ts +12 -0
- package/src/typegen/namespace/TypegenNamespace.ts +18 -0
- package/src/typegen/openapi/generate.ts +168 -0
- package/src/typegen/openapi/transform/components.ts +481 -0
- package/src/typegen/openapi/transform/context.ts +67 -0
- package/src/typegen/openapi/transform/filters.ts +71 -0
- package/src/typegen/openapi/transform/imports.ts +15 -0
- package/src/typegen/openapi/transform/io.ts +86 -0
- package/src/typegen/openapi/transform/methods.ts +803 -0
- package/src/typegen/openapi/transform/operations.ts +120 -0
- package/src/typegen/openapi/transform/paths.ts +119 -0
- package/src/typegen/openapi/utils/types.ts +45 -0
- package/src/types/arrays.d.ts +4 -0
- package/src/types/json.ts +89 -0
- package/src/types/objects.d.ts +14 -0
- package/src/types/requests.ts +96 -0
- package/src/types/schema.ts +834 -0
- package/src/types/strings.d.ts +9 -0
- package/src/types/utils.ts +64 -0
- package/src/utils/console.ts +7 -0
- package/src/utils/data.ts +13 -0
- package/src/utils/files.ts +28 -0
- package/src/utils/imports.ts +12 -0
- package/src/utils/prettier.ts +13 -0
- package/src/utils/strings.ts +3 -0
- package/src/utils/time.ts +25 -0
- package/src/utils/urls.ts +52 -0
- package/typegen.d.ts +1 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import filesystem from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
|
|
5
|
+
import { isDefined } from '@/utils/data';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
isComponentsDeclaration,
|
|
9
|
+
normalizeComponents,
|
|
10
|
+
populateReferencedComponents,
|
|
11
|
+
removeUnreferencedComponents,
|
|
12
|
+
} from './transform/components';
|
|
13
|
+
import { createTypeTransformationContext, TypeTransformContext } from './transform/context';
|
|
14
|
+
import { readPathFiltersFromFile, ignoreEmptyFilters } from './transform/filters';
|
|
15
|
+
import { createImportDeclarations } from './transform/imports';
|
|
16
|
+
import {
|
|
17
|
+
convertTypesToString,
|
|
18
|
+
importTypesFromOpenAPI,
|
|
19
|
+
prepareTypeOutputToSave,
|
|
20
|
+
writeTypeOutputToStandardOutput,
|
|
21
|
+
} from './transform/io';
|
|
22
|
+
import { isOperationsDeclaration, normalizeOperations, removeUnreferencedOperations } from './transform/operations';
|
|
23
|
+
import { isPathsDeclaration, normalizePaths } from './transform/paths';
|
|
24
|
+
|
|
25
|
+
const RESOURCES_TO_REMOVE_IF_NOT_NORMALIZED = ['paths', 'webhooks', 'operations', 'components', '$defs'];
|
|
26
|
+
|
|
27
|
+
function removeUnknownResources(node: ts.Node | undefined) {
|
|
28
|
+
const isUnknownResource =
|
|
29
|
+
!node ||
|
|
30
|
+
((ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) &&
|
|
31
|
+
RESOURCES_TO_REMOVE_IF_NOT_NORMALIZED.includes(node.name.text));
|
|
32
|
+
|
|
33
|
+
if (isUnknownResource) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return node;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeRawNodes(rawNodes: ts.Node[], context: TypeTransformContext, options: { prune: boolean }) {
|
|
41
|
+
let normalizedNodes = rawNodes.map((node) => (isPathsDeclaration(node) ? normalizePaths(node, context) : node));
|
|
42
|
+
|
|
43
|
+
if (options.prune) {
|
|
44
|
+
normalizedNodes = normalizedNodes
|
|
45
|
+
.map((node) => (isOperationsDeclaration(node) ? removeUnreferencedOperations(node, context) : node))
|
|
46
|
+
.filter(isDefined);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
normalizedNodes = normalizedNodes
|
|
50
|
+
.map((node) => (isOperationsDeclaration(node) ? normalizeOperations(node, context) : node))
|
|
51
|
+
.filter(isDefined);
|
|
52
|
+
|
|
53
|
+
if (options.prune) {
|
|
54
|
+
for (const node of normalizedNodes) {
|
|
55
|
+
if (isComponentsDeclaration(node, context)) {
|
|
56
|
+
populateReferencedComponents(node, context);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
normalizedNodes = normalizedNodes
|
|
61
|
+
.map((node) => (isComponentsDeclaration(node, context) ? removeUnreferencedComponents(node, context) : node))
|
|
62
|
+
.filter(isDefined);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
normalizedNodes = normalizedNodes
|
|
66
|
+
.map((node) => (isComponentsDeclaration(node, context) ? normalizeComponents(node, context) : node))
|
|
67
|
+
.map(removeUnknownResources)
|
|
68
|
+
.filter(isDefined);
|
|
69
|
+
|
|
70
|
+
return normalizedNodes;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The options to use when generating types from an OpenAPI schema.
|
|
75
|
+
*
|
|
76
|
+
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐typegen#typegengeneratefromopenapioptions `typegen.generateFromOpenAPI(options)` API reference}
|
|
77
|
+
*/
|
|
78
|
+
export interface OpenAPITypegenOptions {
|
|
79
|
+
/**
|
|
80
|
+
* The path to a local OpenAPI schema file or an URL to fetch it. Version 3 is supported as YAML or JSON.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* './schema.yaml';
|
|
84
|
+
* 'https://example.com/openapi/schema.yaml';
|
|
85
|
+
*/
|
|
86
|
+
input: string;
|
|
87
|
+
/**
|
|
88
|
+
* The path to write the generated types to. If not provided, the types will be written to stdout.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* './schema.ts';
|
|
92
|
+
*/
|
|
93
|
+
output?: string;
|
|
94
|
+
/**
|
|
95
|
+
* The name of the service to use in the generated types.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* 'MyService';
|
|
99
|
+
*/
|
|
100
|
+
serviceName: string;
|
|
101
|
+
/** Whether to include comments in the generated types. */
|
|
102
|
+
includeComments: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Whether to remove unused operations and components from the generated types. This is useful for reducing the size
|
|
105
|
+
* of the output file.
|
|
106
|
+
*/
|
|
107
|
+
prune: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* One or more expressions to filter the types to generate. Filters must follow the format `<method> <path>`, where
|
|
110
|
+
* `<method>` is an HTTP method or `*`, and `<path>` is a literal path or a glob. Filters are case-sensitive regarding
|
|
111
|
+
* paths. Negative filters can be created by prefixing the expression with `!`. If more than one positive filter is
|
|
112
|
+
* provided, they will be combined with OR, while negative filters will be combined with AND.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ['GET /users', '* /users', 'GET,POST /users/*', 'DELETE /users/**\\/*', '!GET /notifications'];
|
|
116
|
+
*/
|
|
117
|
+
filters?: string[];
|
|
118
|
+
/**
|
|
119
|
+
* A path to a file containing filter expressions. One expression is expected per line and the format is the same as
|
|
120
|
+
* used in a `--filter` option. Comments are prefixed with `#`. A filter file can be used alongside additional
|
|
121
|
+
* `--filter` expressions.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* './filters.txt';
|
|
125
|
+
*/
|
|
126
|
+
filterFile?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generates TypeScript types from an OpenAPI schema.
|
|
131
|
+
*
|
|
132
|
+
* @param options The options to use when generating the types.
|
|
133
|
+
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐typegen#typegengeneratefromopenapioptions `typegen.generateFromOpenAPI(options)` API reference}
|
|
134
|
+
*/
|
|
135
|
+
async function generateTypesFromOpenAPI({
|
|
136
|
+
input: inputFilePathOrURL,
|
|
137
|
+
output: outputFilePath,
|
|
138
|
+
serviceName,
|
|
139
|
+
includeComments,
|
|
140
|
+
prune,
|
|
141
|
+
filters: filtersFromArguments = [],
|
|
142
|
+
filterFile,
|
|
143
|
+
}: OpenAPITypegenOptions) {
|
|
144
|
+
const filtersFromFile = filterFile ? await readPathFiltersFromFile(filterFile) : [];
|
|
145
|
+
const filters = ignoreEmptyFilters([...filtersFromFile, ...filtersFromArguments]);
|
|
146
|
+
|
|
147
|
+
const rawNodes = await importTypesFromOpenAPI(inputFilePathOrURL);
|
|
148
|
+
const context = createTypeTransformationContext(serviceName, filters);
|
|
149
|
+
const nodes = normalizeRawNodes(rawNodes, context, { prune });
|
|
150
|
+
|
|
151
|
+
const importDeclarations = createImportDeclarations(context);
|
|
152
|
+
|
|
153
|
+
for (const declaration of importDeclarations) {
|
|
154
|
+
nodes.unshift(declaration);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const typeOutput = await convertTypesToString(nodes, { includeComments });
|
|
158
|
+
const formattedOutput = prepareTypeOutputToSave(typeOutput);
|
|
159
|
+
|
|
160
|
+
const shouldWriteToStdout = outputFilePath === undefined;
|
|
161
|
+
if (shouldWriteToStdout) {
|
|
162
|
+
await writeTypeOutputToStandardOutput(formattedOutput);
|
|
163
|
+
} else {
|
|
164
|
+
await filesystem.writeFile(path.resolve(outputFilePath), formattedOutput);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default generateTypesFromOpenAPI;
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
import { Override } from '@/types/utils';
|
|
4
|
+
import { isDefined } from '@/utils/data';
|
|
5
|
+
|
|
6
|
+
import { isNeverType, isUnknownType } from '../utils/types';
|
|
7
|
+
import { ComponentPath, TypeTransformContext } from './context';
|
|
8
|
+
import { normalizeContentType, normalizeResponse } from './methods';
|
|
9
|
+
import { normalizePath } from './paths';
|
|
10
|
+
|
|
11
|
+
export function createComponentsIdentifierText(serviceName: string) {
|
|
12
|
+
return `${serviceName}Components`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createComponentsIdentifier(serviceName: string) {
|
|
16
|
+
return ts.factory.createIdentifier(createComponentsIdentifierText(serviceName));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ComponentsDeclaration = ts.InterfaceDeclaration;
|
|
20
|
+
|
|
21
|
+
export function isComponentsDeclaration(
|
|
22
|
+
node: ts.Node | undefined,
|
|
23
|
+
context: TypeTransformContext,
|
|
24
|
+
): node is ComponentsDeclaration {
|
|
25
|
+
const componentIdentifiers = ['components', createComponentsIdentifierText(context.serviceName)];
|
|
26
|
+
return node !== undefined && ts.isInterfaceDeclaration(node) && componentIdentifiers.includes(node.name.text);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ComponentGroup = Override<
|
|
30
|
+
ts.PropertySignature,
|
|
31
|
+
{
|
|
32
|
+
type: ts.TypeLiteralNode;
|
|
33
|
+
name: ts.Identifier | ts.StringLiteral;
|
|
34
|
+
}
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
function isComponentGroup(node: ts.TypeElement): node is ComponentGroup {
|
|
38
|
+
return (
|
|
39
|
+
ts.isPropertySignature(node) &&
|
|
40
|
+
node.type !== undefined &&
|
|
41
|
+
ts.isTypeLiteralNode(node.type) &&
|
|
42
|
+
ts.isIdentifier(node.name)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type Component = Override<
|
|
47
|
+
ts.PropertySignature,
|
|
48
|
+
{
|
|
49
|
+
type: ts.TypeNode;
|
|
50
|
+
name: ts.Identifier | ts.StringLiteral;
|
|
51
|
+
}
|
|
52
|
+
>;
|
|
53
|
+
|
|
54
|
+
function isComponent(node: ts.TypeElement): node is Component {
|
|
55
|
+
return (
|
|
56
|
+
ts.isPropertySignature(node) &&
|
|
57
|
+
node.type !== undefined &&
|
|
58
|
+
!isNeverType(node.type) &&
|
|
59
|
+
(ts.isIdentifier(node.name) || ts.isStringLiteral(node.name))
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type RequestComponent = Override<Component, { type: ts.TypeLiteralNode }>;
|
|
64
|
+
|
|
65
|
+
function isRequestComponent(node: Component): node is RequestComponent {
|
|
66
|
+
return ts.isTypeLiteralNode(node.type);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function unchangedIndexedAccessTypeNode(node: ts.IndexedAccessTypeNode) {
|
|
70
|
+
return node;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function visitComponentReferences(
|
|
74
|
+
node: ts.TypeNode,
|
|
75
|
+
context: TypeTransformContext & {
|
|
76
|
+
isComponentIndexedAccess?: boolean;
|
|
77
|
+
partialComponentPath?: string[];
|
|
78
|
+
},
|
|
79
|
+
options: {
|
|
80
|
+
onComponentReference: (node: ts.IndexedAccessTypeNode, componentPath: ComponentPath) => void;
|
|
81
|
+
renameComponentReference?: (
|
|
82
|
+
node: ts.IndexedAccessTypeNode,
|
|
83
|
+
resources: {
|
|
84
|
+
objectType: ts.TypeReferenceNode;
|
|
85
|
+
indexType: ts.LiteralTypeNode;
|
|
86
|
+
componentGroupName: string;
|
|
87
|
+
},
|
|
88
|
+
) => ts.IndexedAccessTypeNode;
|
|
89
|
+
},
|
|
90
|
+
): ts.TypeNode {
|
|
91
|
+
const { onComponentReference, renameComponentReference = unchangedIndexedAccessTypeNode } = options;
|
|
92
|
+
|
|
93
|
+
if (isUnknownType(node)) {
|
|
94
|
+
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
98
|
+
const newTypeArguments = node.typeArguments?.map((type) => visitComponentReferences(type, context, options));
|
|
99
|
+
return ts.factory.updateTypeReferenceNode(node, node.typeName, ts.factory.createNodeArray(newTypeArguments));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (ts.isArrayTypeNode(node)) {
|
|
103
|
+
const newElementType = visitComponentReferences(node.elementType, context, options);
|
|
104
|
+
return ts.factory.updateArrayTypeNode(node, newElementType);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (ts.isTupleTypeNode(node)) {
|
|
108
|
+
const newElements = node.elements.map((element) => visitComponentReferences(element, context, options));
|
|
109
|
+
return ts.factory.updateTupleTypeNode(node, ts.factory.createNodeArray(newElements));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (ts.isUnionTypeNode(node)) {
|
|
113
|
+
const newTypes = node.types.map((type) => visitComponentReferences(type, context, options));
|
|
114
|
+
return ts.factory.updateUnionTypeNode(node, ts.factory.createNodeArray(newTypes));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (ts.isIntersectionTypeNode(node)) {
|
|
118
|
+
const newTypes = node.types.map((type) => visitComponentReferences(type, context, options));
|
|
119
|
+
return ts.factory.updateIntersectionTypeNode(node, ts.factory.createNodeArray(newTypes));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
123
|
+
const newType = visitComponentReferences(node.type, context, options);
|
|
124
|
+
return ts.factory.updateParenthesizedType(node, newType);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
128
|
+
const newMembers = node.members.map((member) => {
|
|
129
|
+
if (ts.isPropertySignature(member) && member.type) {
|
|
130
|
+
const newType = visitComponentReferences(member.type, context, options);
|
|
131
|
+
return ts.factory.updatePropertySignature(member, member.modifiers, member.name, member.questionToken, newType);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* istanbul ignore else -- @preserve */
|
|
135
|
+
if (ts.isIndexSignatureDeclaration(member)) {
|
|
136
|
+
const newType = visitComponentReferences(member.type, context, options);
|
|
137
|
+
return ts.factory.updateIndexSignature(member, member.modifiers, member.parameters, newType);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* istanbul ignore next -- @preserve
|
|
141
|
+
* All members are expected to be either a property signature or an index signature. */
|
|
142
|
+
return member;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return ts.factory.updateTypeLiteralNode(node, ts.factory.createNodeArray(newMembers));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (ts.isIndexedAccessTypeNode(node)) {
|
|
149
|
+
const isRootIndexedAccess = context.isComponentIndexedAccess ?? true;
|
|
150
|
+
|
|
151
|
+
if (ts.isIndexedAccessTypeNode(node.objectType)) {
|
|
152
|
+
const childContext: typeof context = { ...context, isComponentIndexedAccess: false };
|
|
153
|
+
const newObjectType = visitComponentReferences(node.objectType, childContext, options);
|
|
154
|
+
|
|
155
|
+
const newNode = ts.factory.updateIndexedAccessTypeNode(node, newObjectType, node.indexType);
|
|
156
|
+
|
|
157
|
+
/* istanbul ignore else -- @preserve
|
|
158
|
+
* Component indexed accesses are always expected to have child indexed accesses. */
|
|
159
|
+
if (childContext.partialComponentPath && childContext.partialComponentPath.length > 0) {
|
|
160
|
+
const hasIndexTypeName =
|
|
161
|
+
ts.isLiteralTypeNode(node.indexType) &&
|
|
162
|
+
(ts.isIdentifier(node.indexType.literal) || ts.isStringLiteral(node.indexType.literal));
|
|
163
|
+
|
|
164
|
+
/* istanbul ignore else -- @preserve
|
|
165
|
+
* Component indexed accesses are always expected to have child indexed accesses. */
|
|
166
|
+
if (hasIndexTypeName) {
|
|
167
|
+
const componentName = node.indexType.literal.text;
|
|
168
|
+
childContext.partialComponentPath.push(componentName);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* istanbul ignore else -- @preserve
|
|
172
|
+
* Component indexed accesses are always expected to have child indexed accesses. */
|
|
173
|
+
if (isRootIndexedAccess) {
|
|
174
|
+
const componentGroupName = childContext.partialComponentPath[0];
|
|
175
|
+
const componentName = childContext.partialComponentPath.slice(1).join('.');
|
|
176
|
+
const componentPath = `${componentGroupName}.${componentName}` as const;
|
|
177
|
+
onComponentReference(newNode, componentPath);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return newNode;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const componentIdentifiers = ['components', createComponentsIdentifierText(context.serviceName)];
|
|
185
|
+
|
|
186
|
+
const isComponentIndexedAccess =
|
|
187
|
+
ts.isTypeReferenceNode(node.objectType) &&
|
|
188
|
+
ts.isIdentifier(node.objectType.typeName) &&
|
|
189
|
+
componentIdentifiers.includes(node.objectType.typeName.text) &&
|
|
190
|
+
ts.isLiteralTypeNode(node.indexType) &&
|
|
191
|
+
(ts.isIdentifier(node.indexType.literal) || ts.isStringLiteral(node.indexType.literal));
|
|
192
|
+
|
|
193
|
+
/* istanbul ignore else -- @preserve
|
|
194
|
+
* All indexed accesses are expected to point to components. */
|
|
195
|
+
if (isComponentIndexedAccess) {
|
|
196
|
+
const isRawComponent = node.objectType.typeName.text === 'components';
|
|
197
|
+
const componentGroupName = node.indexType.literal.text;
|
|
198
|
+
|
|
199
|
+
const newNode = isRawComponent
|
|
200
|
+
? renameComponentReference(node, {
|
|
201
|
+
objectType: node.objectType,
|
|
202
|
+
indexType: node.indexType,
|
|
203
|
+
componentGroupName,
|
|
204
|
+
})
|
|
205
|
+
: node;
|
|
206
|
+
|
|
207
|
+
const newNodeHasComponentGroupName =
|
|
208
|
+
ts.isLiteralTypeNode(newNode.indexType) &&
|
|
209
|
+
(ts.isIdentifier(newNode.indexType.literal) || ts.isStringLiteral(newNode.indexType.literal));
|
|
210
|
+
|
|
211
|
+
/* istanbul ignore else -- @preserve
|
|
212
|
+
* All component indexed accesses are expected to have an index type name. */
|
|
213
|
+
if (newNodeHasComponentGroupName) {
|
|
214
|
+
const newComponentGroupName = newNode.indexType.literal.text;
|
|
215
|
+
context.partialComponentPath = [newComponentGroupName];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return newNode;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return node;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function normalizeComponentGroupName(rawComponentGroupName: string) {
|
|
226
|
+
if (rawComponentGroupName === 'requestBodies') {
|
|
227
|
+
return 'requests';
|
|
228
|
+
}
|
|
229
|
+
return rawComponentGroupName;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function renameComponentReferences(node: ts.TypeNode, context: TypeTransformContext): ts.TypeNode {
|
|
233
|
+
return visitComponentReferences(node, context, {
|
|
234
|
+
onComponentReference(_node, componentPath) {
|
|
235
|
+
context.referencedTypes.components.add(componentPath);
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
renameComponentReference(node, { indexType, objectType, componentGroupName }) {
|
|
239
|
+
const newIdentifier = createComponentsIdentifier(context.serviceName);
|
|
240
|
+
const newObjectType = ts.factory.updateTypeReferenceNode(objectType, newIdentifier, objectType.typeArguments);
|
|
241
|
+
|
|
242
|
+
const newComponentGroupName = normalizeComponentGroupName(componentGroupName);
|
|
243
|
+
const newIndexType = ts.factory.updateLiteralTypeNode(
|
|
244
|
+
indexType,
|
|
245
|
+
ts.factory.createStringLiteral(newComponentGroupName),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return ts.factory.updateIndexedAccessTypeNode(node, newObjectType, newIndexType);
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function processPendingRequestComponentActions(component: RequestComponent, context: TypeTransformContext) {
|
|
254
|
+
const pendingRequestActions = context.pendingActions.components.requests;
|
|
255
|
+
|
|
256
|
+
const componentName = component.name.text;
|
|
257
|
+
const shouldBeMarkedAsOptional = pendingRequestActions.toMarkBodyAsOptional.has(componentName);
|
|
258
|
+
|
|
259
|
+
const bodyQuestionToken = shouldBeMarkedAsOptional
|
|
260
|
+
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
261
|
+
: component.questionToken;
|
|
262
|
+
|
|
263
|
+
pendingRequestActions.toMarkBodyAsOptional.delete(componentName);
|
|
264
|
+
|
|
265
|
+
return { bodyQuestionToken };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function wrapRequestComponentType(type: ts.TypeNode, context: TypeTransformContext) {
|
|
269
|
+
context.typeImports.http.add('HttpSchema');
|
|
270
|
+
|
|
271
|
+
const httpSchemaRequestWrapper = ts.factory.createQualifiedName(
|
|
272
|
+
ts.factory.createIdentifier('HttpSchema'),
|
|
273
|
+
ts.factory.createIdentifier('Request'),
|
|
274
|
+
);
|
|
275
|
+
return ts.factory.createTypeReferenceNode(httpSchemaRequestWrapper, [type]);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function normalizeRequestComponent(component: Component, context: TypeTransformContext) {
|
|
279
|
+
/* istanbul ignore if -- @preserve
|
|
280
|
+
* Component group members in `requests` are always expected the be request components. */
|
|
281
|
+
if (!isRequestComponent(component)) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const { bodyQuestionToken } = processPendingRequestComponentActions(component, context);
|
|
286
|
+
const newType = normalizeContentType(component.type, context, { bodyQuestionToken });
|
|
287
|
+
|
|
288
|
+
return ts.factory.updatePropertySignature(
|
|
289
|
+
component,
|
|
290
|
+
component.modifiers,
|
|
291
|
+
component.name,
|
|
292
|
+
component.questionToken,
|
|
293
|
+
wrapRequestComponentType(newType, context),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function normalizeComponent(
|
|
298
|
+
component: ts.TypeElement,
|
|
299
|
+
componentGroupName: string,
|
|
300
|
+
context: TypeTransformContext,
|
|
301
|
+
): ts.TypeElement | undefined {
|
|
302
|
+
/* istanbul ignore if -- @preserve
|
|
303
|
+
* Component group members are always expected the be components. */
|
|
304
|
+
if (!isComponent(component)) {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (componentGroupName === 'requests') {
|
|
309
|
+
return normalizeRequestComponent(component, context);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (componentGroupName === 'responses') {
|
|
313
|
+
const responseComponent = normalizeResponse(component, context, { isComponent: true });
|
|
314
|
+
return responseComponent?.newSignature;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (componentGroupName === 'pathItems') {
|
|
318
|
+
return normalizePath(component, context, { isComponent: true });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return ts.factory.updatePropertySignature(
|
|
322
|
+
component,
|
|
323
|
+
component.modifiers,
|
|
324
|
+
component.name,
|
|
325
|
+
component.questionToken,
|
|
326
|
+
renameComponentReferences(component.type, context),
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function normalizeComponentGroup(componentGroup: ts.TypeElement, context: TypeTransformContext) {
|
|
331
|
+
if (!isComponentGroup(componentGroup)) {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const componentGroupName = normalizeComponentGroupName(componentGroup.name.text);
|
|
336
|
+
const newIdentifier = ts.factory.createIdentifier(componentGroupName);
|
|
337
|
+
|
|
338
|
+
const newComponents = componentGroup.type.members
|
|
339
|
+
.map((component) => normalizeComponent(component, componentGroupName, context))
|
|
340
|
+
.filter(isDefined);
|
|
341
|
+
|
|
342
|
+
const newType = ts.factory.updateTypeLiteralNode(componentGroup.type, ts.factory.createNodeArray(newComponents));
|
|
343
|
+
|
|
344
|
+
return ts.factory.updatePropertySignature(
|
|
345
|
+
componentGroup,
|
|
346
|
+
componentGroup.modifiers,
|
|
347
|
+
newIdentifier,
|
|
348
|
+
componentGroup.questionToken,
|
|
349
|
+
newType,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function normalizeComponents(components: ts.InterfaceDeclaration, context: TypeTransformContext) {
|
|
354
|
+
const newIdentifier = createComponentsIdentifier(context.serviceName);
|
|
355
|
+
|
|
356
|
+
const newMembers = components.members
|
|
357
|
+
.map((componentGroup) => normalizeComponentGroup(componentGroup, context))
|
|
358
|
+
.filter(isDefined);
|
|
359
|
+
|
|
360
|
+
return ts.factory.updateInterfaceDeclaration(
|
|
361
|
+
components,
|
|
362
|
+
components.modifiers,
|
|
363
|
+
newIdentifier,
|
|
364
|
+
components.typeParameters,
|
|
365
|
+
components.heritageClauses,
|
|
366
|
+
newMembers,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function populateReferencedComponents(components: ts.InterfaceDeclaration, context: TypeTransformContext) {
|
|
371
|
+
const pathsToVisit = new Set(context.referencedTypes.components);
|
|
372
|
+
|
|
373
|
+
while (pathsToVisit.size > 0) {
|
|
374
|
+
const previousPathsToVisit = new Set(pathsToVisit);
|
|
375
|
+
pathsToVisit.clear();
|
|
376
|
+
|
|
377
|
+
for (const componentGroup of components.members) {
|
|
378
|
+
if (!isComponentGroup(componentGroup)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const componentGroupName = normalizeComponentGroupName(componentGroup.name.text);
|
|
383
|
+
|
|
384
|
+
for (const component of componentGroup.type.members) {
|
|
385
|
+
/* istanbul ignore if -- @preserve
|
|
386
|
+
* Component group members are always expected the be components. */
|
|
387
|
+
if (!isComponent(component)) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const componentName = component.name.text;
|
|
392
|
+
const componentPath = `${componentGroupName}.${componentName}` as const;
|
|
393
|
+
const isComponentToVisit = previousPathsToVisit.has(componentPath);
|
|
394
|
+
|
|
395
|
+
if (!isComponentToVisit) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
context.referencedTypes.components.add(componentPath);
|
|
400
|
+
|
|
401
|
+
visitComponentReferences(component.type, context, {
|
|
402
|
+
onComponentReference(_node, componentPath) {
|
|
403
|
+
const isKnownReferencedComponent = context.referencedTypes.components.has(componentPath);
|
|
404
|
+
if (!isKnownReferencedComponent) {
|
|
405
|
+
pathsToVisit.add(componentPath);
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function removeComponentIfUnreferenced(
|
|
415
|
+
component: ts.TypeElement,
|
|
416
|
+
componentGroupName: string,
|
|
417
|
+
context: TypeTransformContext,
|
|
418
|
+
) {
|
|
419
|
+
/* istanbul ignore if -- @preserve
|
|
420
|
+
* Component group members are always expected the be components. */
|
|
421
|
+
if (!isComponent(component)) {
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const componentName = component.name.text;
|
|
426
|
+
const componentPath = `${componentGroupName}.${componentName}` as const;
|
|
427
|
+
|
|
428
|
+
if (context.referencedTypes.components.has(componentPath)) {
|
|
429
|
+
context.referencedTypes.components.delete(componentPath);
|
|
430
|
+
return component;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function removeUnreferencedComponentsInGroup(componentGroup: ts.TypeElement, context: TypeTransformContext) {
|
|
437
|
+
/* istanbul ignore if -- @preserve
|
|
438
|
+
* Component members are always expected the be component groups. */
|
|
439
|
+
if (!isComponentGroup(componentGroup)) {
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const componentGroupName = normalizeComponentGroupName(componentGroup.name.text);
|
|
444
|
+
|
|
445
|
+
const newComponents = componentGroup.type.members
|
|
446
|
+
.map((component) => removeComponentIfUnreferenced(component, componentGroupName, context))
|
|
447
|
+
.filter(isDefined);
|
|
448
|
+
|
|
449
|
+
if (newComponents.length === 0) {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return ts.factory.updatePropertySignature(
|
|
454
|
+
componentGroup,
|
|
455
|
+
componentGroup.modifiers,
|
|
456
|
+
componentGroup.name,
|
|
457
|
+
componentGroup.questionToken,
|
|
458
|
+
ts.factory.updateTypeLiteralNode(componentGroup.type, ts.factory.createNodeArray(newComponents)),
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function removeUnreferencedComponents(components: ts.InterfaceDeclaration, context: TypeTransformContext) {
|
|
463
|
+
const newComponentGroups = components.members
|
|
464
|
+
.map((componentGroup) => removeUnreferencedComponentsInGroup(componentGroup, context))
|
|
465
|
+
.filter(isDefined);
|
|
466
|
+
|
|
467
|
+
context.referencedTypes.components.clear();
|
|
468
|
+
|
|
469
|
+
if (newComponentGroups.length === 0) {
|
|
470
|
+
return undefined;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return ts.factory.updateInterfaceDeclaration(
|
|
474
|
+
components,
|
|
475
|
+
components.modifiers,
|
|
476
|
+
components.name,
|
|
477
|
+
components.typeParameters,
|
|
478
|
+
components.heritageClauses,
|
|
479
|
+
newComponentGroups,
|
|
480
|
+
);
|
|
481
|
+
}
|