@wp-typia/project-tools 0.24.11 → 0.24.13
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/dist/runtime/add/cli-add-block-config.js +7 -15
- package/dist/runtime/add/cli-add-block-package-json.d.ts +5 -0
- package/dist/runtime/add/cli-add-block-package-json.js +32 -0
- package/dist/runtime/add/cli-add-block.js +17 -4
- package/dist/runtime/add/cli-add-workspace-admin-view-scaffold.js +1 -5
- package/dist/runtime/add/cli-add-workspace-hooked-block.js +15 -20
- package/dist/runtime/add/cli-add-workspace-mutation.d.ts +15 -2
- package/dist/runtime/add/cli-add-workspace-mutation.js +10 -4
- package/dist/runtime/add/persistence-rest-artifacts.d.ts +32 -7
- package/dist/runtime/add/persistence-rest-artifacts.js +41 -15
- package/dist/runtime/cli/cli-diagnostics.d.ts +9 -0
- package/dist/runtime/cli/cli-diagnostics.js +8 -0
- package/dist/runtime/cli/cli-init-package-json.js +5 -1
- package/dist/runtime/doctor/cli-doctor-standalone-control-flow.d.ts +38 -0
- package/dist/runtime/doctor/cli-doctor-standalone-control-flow.js +365 -0
- package/dist/runtime/doctor/cli-doctor-standalone-rest.d.ts +21 -0
- package/dist/runtime/doctor/cli-doctor-standalone-rest.js +1354 -0
- package/dist/runtime/doctor/cli-doctor-standalone-shared.d.ts +24 -0
- package/dist/runtime/doctor/cli-doctor-standalone-shared.js +50 -0
- package/dist/runtime/doctor/cli-doctor-standalone.d.ts +32 -0
- package/dist/runtime/doctor/cli-doctor-standalone.js +2594 -0
- package/dist/runtime/doctor/cli-doctor-workspace.d.ts +5 -3
- package/dist/runtime/doctor/cli-doctor-workspace.js +27 -3
- package/dist/runtime/doctor/cli-doctor.d.ts +4 -2
- package/dist/runtime/doctor/cli-doctor.js +4 -2
- package/dist/runtime/shared/json-utils.d.ts +7 -0
- package/dist/runtime/shared/json-utils.js +10 -0
- package/dist/runtime/shared/package-json-types.d.ts +1 -1
- package/dist/runtime/shared/package-versions.d.ts +2 -0
- package/dist/runtime/shared/package-versions.js +2 -0
- package/dist/runtime/shared/php-utils.d.ts +39 -2
- package/dist/runtime/shared/php-utils.js +205 -6
- package/dist/runtime/templates/block-generator-service-core.d.ts +1 -1
- package/dist/runtime/templates/block-generator-service-core.js +9 -3
- package/dist/runtime/templates/block-generator-service-spec.d.ts +5 -0
- package/dist/runtime/templates/built-in-block-artifact-documents.d.ts +1 -1
- package/dist/runtime/templates/built-in-block-artifact-documents.js +1 -1
- package/dist/runtime/templates/built-in-block-artifact-types.js +2 -2
- package/dist/runtime/templates/built-in-block-artifacts.d.ts +1 -1
- package/dist/runtime/templates/built-in-block-artifacts.js +16 -12
- package/dist/runtime/templates/built-in-block-attribute-emitters.d.ts +5 -0
- package/dist/runtime/templates/built-in-block-attribute-emitters.js +113 -8
- package/dist/runtime/templates/built-in-block-attribute-specs.js +4 -9
- package/dist/runtime/templates/built-in-block-code-artifacts.js +6 -1
- package/dist/runtime/templates/built-in-block-code-templates/compound-persistence.d.ts +2 -2
- package/dist/runtime/templates/built-in-block-code-templates/compound-persistence.js +24 -1
- package/dist/runtime/templates/built-in-block-code-templates/persistence.d.ts +2 -2
- package/dist/runtime/templates/built-in-block-code-templates/persistence.js +22 -1
- package/dist/runtime/templates/built-in-block-non-ts-compound-templates.d.ts +2 -2
- package/dist/runtime/templates/built-in-block-non-ts-compound-templates.js +6 -2
- package/dist/runtime/templates/built-in-block-non-ts-persistence-templates.d.ts +2 -2
- package/dist/runtime/templates/built-in-block-non-ts-persistence-templates.js +6 -2
- package/dist/runtime/templates/cli-scaffold-emission.d.ts +7 -1
- package/dist/runtime/templates/cli-scaffold-emission.js +10 -1
- package/dist/runtime/templates/cli-templates.d.ts +47 -0
- package/dist/runtime/templates/cli-templates.js +50 -1
- package/dist/runtime/templates/local-dev-presets.js +1 -1
- package/dist/runtime/templates/scaffold-apply-utils.d.ts +4 -8
- package/dist/runtime/templates/scaffold-apply-utils.js +44 -28
- package/dist/runtime/templates/scaffold-compiler-artifacts.d.ts +17 -0
- package/dist/runtime/templates/scaffold-compiler-artifacts.js +81 -0
- package/dist/runtime/templates/scaffold-onboarding.d.ts +5 -0
- package/dist/runtime/templates/scaffold-onboarding.js +13 -2
- package/dist/runtime/templates/scaffold-template-variable-groups.d.ts +4 -0
- package/dist/runtime/templates/scaffold-template-variable-groups.js +7 -0
- package/dist/runtime/templates/scaffold.d.ts +3 -1
- package/dist/runtime/templates/scaffold.js +2 -1
- package/dist/runtime/templates/starter-manifests.js +1 -1
- package/package.json +5 -5
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +1 -1
- package/templates/_shared/compound/persistence/package.json.mustache +1 -0
- package/templates/_shared/persistence/core/package.json.mustache +1 -0
|
@@ -0,0 +1,1354 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { containsCompletion, countOuterParserContinues, hasCanonicalRuntimeImports, hasCanonicalUnknownFlagThrow, hasEarlierAbruptCompletion, hasOnlyCanonicalParserEffects, isAllowedSyncHelperTopLevelStatement, isCanonicalSyncMainCatchHandler, isSafeErrorConstruction, unwrapStaticExpression, } from './cli-doctor-standalone-control-flow.js';
|
|
5
|
+
import { hasTypeScriptSyntaxErrors, isSafeProjectRelativePath, } from './cli-doctor-standalone-shared.js';
|
|
6
|
+
const STANDALONE_SYNC_REST_SCRIPT = path.join('scripts', 'sync-rest-contracts.ts');
|
|
7
|
+
const STANDALONE_REST_OPEN_API_FILE = path.join('src', 'api.openapi.json');
|
|
8
|
+
const STANDALONE_REST_CLIENT_FILE = path.join('src', 'api-client.ts');
|
|
9
|
+
export const STANDALONE_PERSISTENCE_BLOCK_SCHEMA_ARTIFACTS = {
|
|
10
|
+
jsonSchemaFile: path.join('src', 'typia.schema.json'),
|
|
11
|
+
openApiFile: path.join('src', 'typia.openapi.json'),
|
|
12
|
+
};
|
|
13
|
+
const STANDALONE_REST_SURFACE_PATHS = [
|
|
14
|
+
path.join('src', 'api-types.ts'),
|
|
15
|
+
path.join('src', 'api-schemas'),
|
|
16
|
+
STANDALONE_REST_OPEN_API_FILE,
|
|
17
|
+
STANDALONE_REST_CLIENT_FILE,
|
|
18
|
+
];
|
|
19
|
+
const ENDPOINT_AUTH_INTENTS = new Set([
|
|
20
|
+
'authenticated',
|
|
21
|
+
'public',
|
|
22
|
+
'public-write-protected',
|
|
23
|
+
]);
|
|
24
|
+
const ENDPOINT_AUTH_MODES = new Set([
|
|
25
|
+
'authenticated-rest-nonce',
|
|
26
|
+
'public-read',
|
|
27
|
+
'public-signed-token',
|
|
28
|
+
]);
|
|
29
|
+
const ENDPOINT_METHODS = new Set(['DELETE', 'GET', 'PATCH', 'POST', 'PUT']);
|
|
30
|
+
const ENDPOINT_WORDPRESS_AUTH_MECHANISMS = new Set([
|
|
31
|
+
'public-signed-token',
|
|
32
|
+
'rest-nonce',
|
|
33
|
+
]);
|
|
34
|
+
const RESERVED_CLIENT_IDENTIFIERS = new Set([
|
|
35
|
+
'await',
|
|
36
|
+
'break',
|
|
37
|
+
'case',
|
|
38
|
+
'catch',
|
|
39
|
+
'class',
|
|
40
|
+
'const',
|
|
41
|
+
'continue',
|
|
42
|
+
'debugger',
|
|
43
|
+
'default',
|
|
44
|
+
'delete',
|
|
45
|
+
'do',
|
|
46
|
+
'else',
|
|
47
|
+
'enum',
|
|
48
|
+
'export',
|
|
49
|
+
'extends',
|
|
50
|
+
'false',
|
|
51
|
+
'finally',
|
|
52
|
+
'for',
|
|
53
|
+
'function',
|
|
54
|
+
'if',
|
|
55
|
+
'implements',
|
|
56
|
+
'import',
|
|
57
|
+
'in',
|
|
58
|
+
'interface',
|
|
59
|
+
'instanceof',
|
|
60
|
+
'let',
|
|
61
|
+
'new',
|
|
62
|
+
'null',
|
|
63
|
+
'package',
|
|
64
|
+
'private',
|
|
65
|
+
'protected',
|
|
66
|
+
'public',
|
|
67
|
+
'return',
|
|
68
|
+
'static',
|
|
69
|
+
'super',
|
|
70
|
+
'switch',
|
|
71
|
+
'this',
|
|
72
|
+
'throw',
|
|
73
|
+
'true',
|
|
74
|
+
'try',
|
|
75
|
+
'typeof',
|
|
76
|
+
'var',
|
|
77
|
+
'void',
|
|
78
|
+
'while',
|
|
79
|
+
'with',
|
|
80
|
+
'yield',
|
|
81
|
+
]);
|
|
82
|
+
const ENDPOINT_CLIENT_BASE_IDENTIFIERS = [
|
|
83
|
+
'apiValidators',
|
|
84
|
+
'callEndpoint',
|
|
85
|
+
'createEndpoint',
|
|
86
|
+
];
|
|
87
|
+
const REST_SYNC_RUNTIME_IMPORTS = new Map([
|
|
88
|
+
[
|
|
89
|
+
'@wp-typia/block-runtime/metadata-core',
|
|
90
|
+
{
|
|
91
|
+
namedBindings: new Set([
|
|
92
|
+
'defineEndpointManifest',
|
|
93
|
+
'runSyncBlockMetadata',
|
|
94
|
+
'syncEndpointClient',
|
|
95
|
+
'syncRestOpenApi',
|
|
96
|
+
'syncTypeSchemas',
|
|
97
|
+
]),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
]);
|
|
101
|
+
const WORDPRESS_NAMED_CAPTURE_PREFIX = '(?P<';
|
|
102
|
+
const WORDPRESS_CAPTURE_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
103
|
+
function getStandaloneRestContractArtifactPaths(baseName) {
|
|
104
|
+
return {
|
|
105
|
+
jsonSchemaFile: path.join('src', 'api-schemas', `${baseName}.schema.json`),
|
|
106
|
+
openApiFile: path.join('src', 'api-schemas', `${baseName}.openapi.json`),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function getStandaloneRestArtifactPaths(manifest) {
|
|
110
|
+
return [
|
|
111
|
+
...Object.keys(manifest.contracts).flatMap((baseName) => {
|
|
112
|
+
const { jsonSchemaFile, openApiFile } = getStandaloneRestContractArtifactPaths(baseName);
|
|
113
|
+
return [jsonSchemaFile, openApiFile];
|
|
114
|
+
}),
|
|
115
|
+
STANDALONE_REST_OPEN_API_FILE,
|
|
116
|
+
STANDALONE_REST_CLIENT_FILE,
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
/** Detect whether a damaged project still exposes the generated REST surface. */
|
|
120
|
+
export function standaloneProjectRequiresRest(projectDir, packageJson) {
|
|
121
|
+
return (fs.existsSync(path.join(projectDir, STANDALONE_SYNC_REST_SCRIPT)) ||
|
|
122
|
+
STANDALONE_REST_SURFACE_PATHS.some((relativePath) => fs.existsSync(path.join(projectDir, relativePath))) ||
|
|
123
|
+
typeof packageJson.scripts?.['sync-rest'] === 'string' ||
|
|
124
|
+
['@wp-typia/rest', '@wp-typia/api-client'].some((packageName) => typeof packageJson.dependencies?.[packageName] === 'string' ||
|
|
125
|
+
typeof packageJson.devDependencies?.[packageName] === 'string'));
|
|
126
|
+
}
|
|
127
|
+
function getStaticPropertyName(name) {
|
|
128
|
+
if (ts.isIdentifier(name) ||
|
|
129
|
+
ts.isStringLiteralLike(name) ||
|
|
130
|
+
ts.isNumericLiteral(name)) {
|
|
131
|
+
return name.text;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function parseStaticExpression(rawExpression) {
|
|
136
|
+
const expression = unwrapStaticExpression(rawExpression);
|
|
137
|
+
if (ts.isStringLiteralLike(expression)) {
|
|
138
|
+
return { ok: true, value: expression.text };
|
|
139
|
+
}
|
|
140
|
+
if (ts.isNumericLiteral(expression)) {
|
|
141
|
+
return { ok: true, value: Number(expression.text) };
|
|
142
|
+
}
|
|
143
|
+
if (expression.kind === ts.SyntaxKind.TrueKeyword) {
|
|
144
|
+
return { ok: true, value: true };
|
|
145
|
+
}
|
|
146
|
+
if (expression.kind === ts.SyntaxKind.FalseKeyword) {
|
|
147
|
+
return { ok: true, value: false };
|
|
148
|
+
}
|
|
149
|
+
if (expression.kind === ts.SyntaxKind.NullKeyword) {
|
|
150
|
+
return { ok: true, value: null };
|
|
151
|
+
}
|
|
152
|
+
if (ts.isPrefixUnaryExpression(expression) &&
|
|
153
|
+
(expression.operator === ts.SyntaxKind.PlusToken ||
|
|
154
|
+
expression.operator === ts.SyntaxKind.MinusToken) &&
|
|
155
|
+
ts.isNumericLiteral(expression.operand)) {
|
|
156
|
+
const value = Number(expression.operand.text);
|
|
157
|
+
return {
|
|
158
|
+
ok: true,
|
|
159
|
+
value: expression.operator === ts.SyntaxKind.MinusToken ? -value : value,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (ts.isArrayLiteralExpression(expression)) {
|
|
163
|
+
const values = [];
|
|
164
|
+
for (const element of expression.elements) {
|
|
165
|
+
if (ts.isSpreadElement(element)) {
|
|
166
|
+
return { ok: false };
|
|
167
|
+
}
|
|
168
|
+
const parsed = parseStaticExpression(element);
|
|
169
|
+
if (!parsed.ok) {
|
|
170
|
+
return parsed;
|
|
171
|
+
}
|
|
172
|
+
values.push(parsed.value);
|
|
173
|
+
}
|
|
174
|
+
return { ok: true, value: values };
|
|
175
|
+
}
|
|
176
|
+
if (ts.isObjectLiteralExpression(expression)) {
|
|
177
|
+
const value = {};
|
|
178
|
+
for (const property of expression.properties) {
|
|
179
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
180
|
+
return { ok: false };
|
|
181
|
+
}
|
|
182
|
+
const propertyName = getStaticPropertyName(property.name);
|
|
183
|
+
if (propertyName === null) {
|
|
184
|
+
return { ok: false };
|
|
185
|
+
}
|
|
186
|
+
const parsed = parseStaticExpression(property.initializer);
|
|
187
|
+
if (!parsed.ok) {
|
|
188
|
+
return parsed;
|
|
189
|
+
}
|
|
190
|
+
value[propertyName] = parsed.value;
|
|
191
|
+
}
|
|
192
|
+
return { ok: true, value };
|
|
193
|
+
}
|
|
194
|
+
if (ts.isBinaryExpression(expression) &&
|
|
195
|
+
expression.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
196
|
+
const left = parseStaticExpression(expression.left);
|
|
197
|
+
const right = parseStaticExpression(expression.right);
|
|
198
|
+
if (!left.ok || !right.ok) {
|
|
199
|
+
return { ok: false };
|
|
200
|
+
}
|
|
201
|
+
if ((typeof left.value === 'string' || typeof left.value === 'number') &&
|
|
202
|
+
(typeof right.value === 'string' || typeof right.value === 'number')) {
|
|
203
|
+
return {
|
|
204
|
+
ok: true,
|
|
205
|
+
value: typeof left.value === 'string' || typeof right.value === 'string'
|
|
206
|
+
? String(left.value) + String(right.value)
|
|
207
|
+
: left.value + right.value,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { ok: false };
|
|
212
|
+
}
|
|
213
|
+
function isStaticRecord(value) {
|
|
214
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
215
|
+
}
|
|
216
|
+
function hasOptionalStaticString(value, propertyName) {
|
|
217
|
+
return (value[propertyName] === undefined ||
|
|
218
|
+
typeof value[propertyName] === 'string');
|
|
219
|
+
}
|
|
220
|
+
function isStaticWordPressAuthDefinition(value) {
|
|
221
|
+
return (isStaticRecord(value) &&
|
|
222
|
+
typeof value.mechanism === 'string' &&
|
|
223
|
+
ENDPOINT_WORDPRESS_AUTH_MECHANISMS.has(value.mechanism) &&
|
|
224
|
+
hasOptionalStaticString(value, 'publicTokenField'));
|
|
225
|
+
}
|
|
226
|
+
function getNormalizedStaticWordPressAuth(value) {
|
|
227
|
+
if (!value || !isStaticRecord(value)) {
|
|
228
|
+
return {
|
|
229
|
+
publicTokenField: undefined,
|
|
230
|
+
wordpressMechanism: undefined,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (value.mechanism === 'public-signed-token') {
|
|
234
|
+
return {
|
|
235
|
+
publicTokenField: typeof value.publicTokenField === 'string'
|
|
236
|
+
? value.publicTokenField
|
|
237
|
+
: 'publicWriteToken',
|
|
238
|
+
wordpressMechanism: value.mechanism,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
publicTokenField: undefined,
|
|
243
|
+
wordpressMechanism: value.mechanism,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function hasMatchingStaticEndpointAuth(value) {
|
|
247
|
+
const auth = typeof value.auth === 'string' ? value.auth : undefined;
|
|
248
|
+
const authMode = typeof value.authMode === 'string' ? value.authMode : undefined;
|
|
249
|
+
const wordpressAuth = getNormalizedStaticWordPressAuth(value.wordpressAuth);
|
|
250
|
+
let normalizedAuth;
|
|
251
|
+
if (auth) {
|
|
252
|
+
if ((auth === 'public' && wordpressAuth.wordpressMechanism !== undefined) ||
|
|
253
|
+
(auth === 'authenticated' &&
|
|
254
|
+
wordpressAuth.wordpressMechanism === 'public-signed-token') ||
|
|
255
|
+
(auth === 'public-write-protected' &&
|
|
256
|
+
wordpressAuth.wordpressMechanism === 'rest-nonce')) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
let derivedAuthMode;
|
|
260
|
+
if (auth === 'public') {
|
|
261
|
+
derivedAuthMode = 'public-read';
|
|
262
|
+
}
|
|
263
|
+
else if (auth === 'authenticated' &&
|
|
264
|
+
wordpressAuth.wordpressMechanism === 'rest-nonce') {
|
|
265
|
+
derivedAuthMode = 'authenticated-rest-nonce';
|
|
266
|
+
}
|
|
267
|
+
else if (auth === 'public-write-protected' &&
|
|
268
|
+
wordpressAuth.wordpressMechanism === 'public-signed-token') {
|
|
269
|
+
derivedAuthMode = 'public-signed-token';
|
|
270
|
+
}
|
|
271
|
+
normalizedAuth = {
|
|
272
|
+
auth,
|
|
273
|
+
authMode: derivedAuthMode,
|
|
274
|
+
...wordpressAuth,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
let legacyAuth;
|
|
278
|
+
if (authMode) {
|
|
279
|
+
if (authMode === 'public-read') {
|
|
280
|
+
legacyAuth = {
|
|
281
|
+
auth: 'public',
|
|
282
|
+
authMode,
|
|
283
|
+
publicTokenField: undefined,
|
|
284
|
+
wordpressMechanism: undefined,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
else if (authMode === 'authenticated-rest-nonce') {
|
|
288
|
+
legacyAuth = {
|
|
289
|
+
auth: 'authenticated',
|
|
290
|
+
authMode,
|
|
291
|
+
publicTokenField: undefined,
|
|
292
|
+
wordpressMechanism: 'rest-nonce',
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
legacyAuth = {
|
|
297
|
+
auth: 'public-write-protected',
|
|
298
|
+
authMode,
|
|
299
|
+
publicTokenField: 'publicWriteToken',
|
|
300
|
+
wordpressMechanism: 'public-signed-token',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (normalizedAuth && legacyAuth) {
|
|
305
|
+
return (normalizedAuth.auth === legacyAuth.auth &&
|
|
306
|
+
normalizedAuth.authMode === legacyAuth.authMode &&
|
|
307
|
+
normalizedAuth.wordpressMechanism === legacyAuth.wordpressMechanism &&
|
|
308
|
+
normalizedAuth.publicTokenField === legacyAuth.publicTokenField);
|
|
309
|
+
}
|
|
310
|
+
return normalizedAuth !== undefined || legacyAuth !== undefined;
|
|
311
|
+
}
|
|
312
|
+
function isValidEndpointOperationId(value) {
|
|
313
|
+
return (/^[$A-Z_][0-9A-Z_$]*$/iu.test(value) &&
|
|
314
|
+
!RESERVED_CLIENT_IDENTIFIERS.has(value));
|
|
315
|
+
}
|
|
316
|
+
function toEndpointClientPropertyName(value) {
|
|
317
|
+
return value
|
|
318
|
+
.replace(/[^A-Za-z0-9]+(.)/gu, (_match, next) => next.toUpperCase())
|
|
319
|
+
.replace(/[^A-Za-z0-9]+$/u, '')
|
|
320
|
+
.replace(/^[A-Z]/u, (match) => match.toLowerCase());
|
|
321
|
+
}
|
|
322
|
+
function findWordPressRegexGroupEnd(value, start) {
|
|
323
|
+
if (value[start] !== '(')
|
|
324
|
+
return null;
|
|
325
|
+
let depth = 0;
|
|
326
|
+
let escaped = false;
|
|
327
|
+
let inCharacterClass = false;
|
|
328
|
+
for (let index = start; index < value.length; index += 1) {
|
|
329
|
+
const character = value[index] ?? '';
|
|
330
|
+
if (escaped) {
|
|
331
|
+
escaped = false;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (character === '\\') {
|
|
335
|
+
escaped = true;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (character === '[' && !inCharacterClass) {
|
|
339
|
+
inCharacterClass = true;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (character === ']' && inCharacterClass) {
|
|
343
|
+
inCharacterClass = false;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (inCharacterClass)
|
|
347
|
+
continue;
|
|
348
|
+
if (character === '(') {
|
|
349
|
+
depth += 1;
|
|
350
|
+
}
|
|
351
|
+
else if (character === ')') {
|
|
352
|
+
depth -= 1;
|
|
353
|
+
if (depth === 0)
|
|
354
|
+
return index + 1;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
function hasWordPressNamedCapture(value) {
|
|
360
|
+
let start = value.indexOf(WORDPRESS_NAMED_CAPTURE_PREFIX);
|
|
361
|
+
while (start !== -1) {
|
|
362
|
+
const nameStart = start + WORDPRESS_NAMED_CAPTURE_PREFIX.length;
|
|
363
|
+
const nameEnd = value.indexOf('>', nameStart);
|
|
364
|
+
if (nameEnd === -1)
|
|
365
|
+
return false;
|
|
366
|
+
const name = value.slice(nameStart, nameEnd);
|
|
367
|
+
const groupEnd = findWordPressRegexGroupEnd(value, start);
|
|
368
|
+
if (WORDPRESS_CAPTURE_NAME_PATTERN.test(name) &&
|
|
369
|
+
groupEnd !== null &&
|
|
370
|
+
groupEnd > nameEnd) {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
start = value.indexOf(WORDPRESS_NAMED_CAPTURE_PREFIX, nameStart);
|
|
374
|
+
}
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
function isStaticEndpointDefinition(value) {
|
|
378
|
+
if (!isStaticRecord(value)) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
const auth = value.auth;
|
|
382
|
+
const authMode = value.authMode;
|
|
383
|
+
const tags = value.tags;
|
|
384
|
+
return (typeof value.method === 'string' &&
|
|
385
|
+
ENDPOINT_METHODS.has(value.method) &&
|
|
386
|
+
typeof value.operationId === 'string' &&
|
|
387
|
+
value.operationId.length > 0 &&
|
|
388
|
+
typeof value.path === 'string' &&
|
|
389
|
+
value.path.startsWith('/') &&
|
|
390
|
+
typeof value.responseContract === 'string' &&
|
|
391
|
+
value.responseContract.length > 0 &&
|
|
392
|
+
Array.isArray(tags) &&
|
|
393
|
+
tags.every((tag) => typeof tag === 'string') &&
|
|
394
|
+
(auth !== undefined || authMode !== undefined) &&
|
|
395
|
+
(auth === undefined ||
|
|
396
|
+
(typeof auth === 'string' && ENDPOINT_AUTH_INTENTS.has(auth))) &&
|
|
397
|
+
(authMode === undefined ||
|
|
398
|
+
(typeof authMode === 'string' && ENDPOINT_AUTH_MODES.has(authMode))) &&
|
|
399
|
+
hasOptionalStaticString(value, 'bodyContract') &&
|
|
400
|
+
hasOptionalStaticString(value, 'queryContract') &&
|
|
401
|
+
hasOptionalStaticString(value, 'summary') &&
|
|
402
|
+
(value.wordpressAuth === undefined ||
|
|
403
|
+
isStaticWordPressAuthDefinition(value.wordpressAuth)) &&
|
|
404
|
+
hasMatchingStaticEndpointAuth(value));
|
|
405
|
+
}
|
|
406
|
+
function isStaticOpenApiInfo(value) {
|
|
407
|
+
return (isStaticRecord(value) &&
|
|
408
|
+
hasOptionalStaticString(value, 'description') &&
|
|
409
|
+
hasOptionalStaticString(value, 'title') &&
|
|
410
|
+
hasOptionalStaticString(value, 'version'));
|
|
411
|
+
}
|
|
412
|
+
function isEndpointManifestDefinition(value) {
|
|
413
|
+
if (!isStaticRecord(value)) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
const contracts = value.contracts;
|
|
417
|
+
const endpoints = value.endpoints;
|
|
418
|
+
if (!contracts || !isStaticRecord(contracts) || !Array.isArray(endpoints)) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
if (!Object.values(contracts).every((contract) => isStaticRecord(contract) &&
|
|
422
|
+
typeof contract.sourceTypeName === 'string' &&
|
|
423
|
+
contract.sourceTypeName.length > 0 &&
|
|
424
|
+
(contract.schemaName === undefined ||
|
|
425
|
+
typeof contract.schemaName === 'string')) ||
|
|
426
|
+
!endpoints.every(isStaticEndpointDefinition) ||
|
|
427
|
+
(value.info !== undefined && !isStaticOpenApiInfo(value.info))) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
const contractNames = new Set(Object.keys(contracts));
|
|
431
|
+
const operationIds = new Set();
|
|
432
|
+
const occupiedIdentifiers = new Set([
|
|
433
|
+
...ENDPOINT_CLIENT_BASE_IDENTIFIERS,
|
|
434
|
+
...(endpoints.some((endpoint) => isStaticRecord(endpoint) &&
|
|
435
|
+
endpoint.bodyContract === undefined &&
|
|
436
|
+
endpoint.queryContract === undefined)
|
|
437
|
+
? ['validateNoRequest']
|
|
438
|
+
: []),
|
|
439
|
+
...(endpoints.some((endpoint) => isStaticRecord(endpoint) &&
|
|
440
|
+
endpoint.bodyContract !== undefined &&
|
|
441
|
+
endpoint.queryContract !== undefined)
|
|
442
|
+
? ['validateCombinedRequest']
|
|
443
|
+
: []),
|
|
444
|
+
]);
|
|
445
|
+
const validatorPropertyNames = new Map();
|
|
446
|
+
for (const endpointValue of endpoints) {
|
|
447
|
+
const endpoint = endpointValue;
|
|
448
|
+
const operationId = endpoint.operationId;
|
|
449
|
+
const endpointConstantName = `${operationId}Endpoint`;
|
|
450
|
+
if (!isValidEndpointOperationId(operationId) ||
|
|
451
|
+
!isValidEndpointOperationId(endpointConstantName) ||
|
|
452
|
+
operationIds.has(operationId)) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
for (const identifier of [operationId, endpointConstantName]) {
|
|
456
|
+
if (occupiedIdentifiers.has(identifier)) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
occupiedIdentifiers.add(identifier);
|
|
460
|
+
}
|
|
461
|
+
operationIds.add(operationId);
|
|
462
|
+
if (typeof endpoint.path === 'string' &&
|
|
463
|
+
hasWordPressNamedCapture(endpoint.path) &&
|
|
464
|
+
endpoint.bodyContract === undefined &&
|
|
465
|
+
endpoint.queryContract === undefined) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
for (const fieldName of [
|
|
469
|
+
'bodyContract',
|
|
470
|
+
'queryContract',
|
|
471
|
+
'responseContract',
|
|
472
|
+
]) {
|
|
473
|
+
const contractName = endpoint[fieldName];
|
|
474
|
+
if (contractName !== undefined &&
|
|
475
|
+
(!contractNames.has(contractName) || contractName === '')) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
if (typeof contractName === 'string') {
|
|
479
|
+
const propertyName = toEndpointClientPropertyName(contractName);
|
|
480
|
+
const previousContractName = validatorPropertyNames.get(propertyName);
|
|
481
|
+
if (previousContractName !== undefined &&
|
|
482
|
+
previousContractName !== contractName) {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
validatorPropertyNames.set(propertyName, contractName);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
function getNamedImportBindings(sourceFile, importedName) {
|
|
492
|
+
const bindings = new Set();
|
|
493
|
+
for (const statement of sourceFile.statements) {
|
|
494
|
+
if (!ts.isImportDeclaration(statement) ||
|
|
495
|
+
!ts.isStringLiteral(statement.moduleSpecifier) ||
|
|
496
|
+
statement.moduleSpecifier.text !==
|
|
497
|
+
'@wp-typia/block-runtime/metadata-core' ||
|
|
498
|
+
statement.importClause?.isTypeOnly ||
|
|
499
|
+
!statement.importClause?.namedBindings ||
|
|
500
|
+
!ts.isNamedImports(statement.importClause.namedBindings)) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
for (const element of statement.importClause.namedBindings.elements) {
|
|
504
|
+
if (!element.isTypeOnly &&
|
|
505
|
+
(element.propertyName ?? element.name).text === importedName) {
|
|
506
|
+
bindings.add(element.name.text);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return bindings;
|
|
511
|
+
}
|
|
512
|
+
function bindingNameContains(name, bindings) {
|
|
513
|
+
if (!name) {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
if (ts.isIdentifier(name)) {
|
|
517
|
+
return bindings.has(name.text);
|
|
518
|
+
}
|
|
519
|
+
return name.elements.some((element) => !ts.isOmittedExpression(element) &&
|
|
520
|
+
bindingNameContains(element.name, bindings));
|
|
521
|
+
}
|
|
522
|
+
function hasShadowedImportBinding(sourceFile, bindings) {
|
|
523
|
+
let shadowed = false;
|
|
524
|
+
function visit(node) {
|
|
525
|
+
if (shadowed || ts.isImportDeclaration(node)) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
let declaredName;
|
|
529
|
+
if (ts.isVariableDeclaration(node) || ts.isParameter(node)) {
|
|
530
|
+
declaredName = node.name;
|
|
531
|
+
}
|
|
532
|
+
else if (ts.isFunctionDeclaration(node) ||
|
|
533
|
+
ts.isFunctionExpression(node) ||
|
|
534
|
+
ts.isClassDeclaration(node) ||
|
|
535
|
+
ts.isClassExpression(node) ||
|
|
536
|
+
ts.isEnumDeclaration(node) ||
|
|
537
|
+
(ts.isImportEqualsDeclaration(node) && !node.isTypeOnly)) {
|
|
538
|
+
declaredName = node.name;
|
|
539
|
+
}
|
|
540
|
+
if (bindingNameContains(declaredName, bindings)) {
|
|
541
|
+
shadowed = true;
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
ts.forEachChild(node, visit);
|
|
545
|
+
}
|
|
546
|
+
visit(sourceFile);
|
|
547
|
+
return shadowed;
|
|
548
|
+
}
|
|
549
|
+
function hasImportedBinding(sourceFile, bindingName) {
|
|
550
|
+
return sourceFile.statements.some((statement) => {
|
|
551
|
+
if (!ts.isImportDeclaration(statement) ||
|
|
552
|
+
!statement.importClause ||
|
|
553
|
+
statement.importClause.isTypeOnly) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
const { name, namedBindings } = statement.importClause;
|
|
557
|
+
if (name?.text === bindingName) {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
if (namedBindings && ts.isNamespaceImport(namedBindings)) {
|
|
561
|
+
return namedBindings.name.text === bindingName;
|
|
562
|
+
}
|
|
563
|
+
return (namedBindings !== undefined &&
|
|
564
|
+
ts.isNamedImports(namedBindings) &&
|
|
565
|
+
namedBindings.elements.some((element) => !element.isTypeOnly && element.name.text === bindingName));
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function getStaticEndpointManifestInitializer(statement, bindings) {
|
|
569
|
+
if (!ts.isVariableStatement(statement) ||
|
|
570
|
+
!(statement.declarationList.flags & ts.NodeFlags.Const) ||
|
|
571
|
+
statement.declarationList.declarations.length !== 1) {
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
const declaration = statement.declarationList.declarations[0];
|
|
575
|
+
if (!ts.isIdentifier(declaration.name) ||
|
|
576
|
+
declaration.name.text !== 'REST_ENDPOINT_MANIFEST' ||
|
|
577
|
+
!declaration.initializer ||
|
|
578
|
+
!ts.isCallExpression(declaration.initializer) ||
|
|
579
|
+
!ts.isIdentifier(declaration.initializer.expression) ||
|
|
580
|
+
!bindings.has(declaration.initializer.expression.text) ||
|
|
581
|
+
declaration.initializer.arguments.length !== 1) {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
return declaration.initializer.arguments[0];
|
|
585
|
+
}
|
|
586
|
+
function parseStaticEndpointManifestDeclaration(statement, bindings) {
|
|
587
|
+
const initializer = getStaticEndpointManifestInitializer(statement, bindings);
|
|
588
|
+
if (!initializer) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
const parsed = parseStaticExpression(initializer);
|
|
592
|
+
return parsed.ok && isEndpointManifestDefinition(parsed.value)
|
|
593
|
+
? parsed.value
|
|
594
|
+
: null;
|
|
595
|
+
}
|
|
596
|
+
function findStaticEndpointManifest(sourceFile, bindings) {
|
|
597
|
+
const manifests = sourceFile.statements.flatMap((statement) => {
|
|
598
|
+
const manifest = parseStaticEndpointManifestDeclaration(statement, bindings);
|
|
599
|
+
return manifest ? [manifest] : [];
|
|
600
|
+
});
|
|
601
|
+
return manifests.length === 1 ? manifests[0] : null;
|
|
602
|
+
}
|
|
603
|
+
function countBindingDeclarations(sourceFile, bindingName) {
|
|
604
|
+
const bindings = new Set([bindingName]);
|
|
605
|
+
let count = 0;
|
|
606
|
+
function visit(node) {
|
|
607
|
+
if (ts.isImportDeclaration(node))
|
|
608
|
+
return;
|
|
609
|
+
let declaredName;
|
|
610
|
+
if (ts.isVariableDeclaration(node) || ts.isParameter(node)) {
|
|
611
|
+
declaredName = node.name;
|
|
612
|
+
}
|
|
613
|
+
else if (ts.isFunctionDeclaration(node) ||
|
|
614
|
+
ts.isFunctionExpression(node) ||
|
|
615
|
+
ts.isClassDeclaration(node) ||
|
|
616
|
+
ts.isClassExpression(node) ||
|
|
617
|
+
ts.isEnumDeclaration(node) ||
|
|
618
|
+
ts.isImportEqualsDeclaration(node)) {
|
|
619
|
+
declaredName = node.name;
|
|
620
|
+
}
|
|
621
|
+
if (bindingNameContains(declaredName, bindings))
|
|
622
|
+
count += 1;
|
|
623
|
+
ts.forEachChild(node, visit);
|
|
624
|
+
}
|
|
625
|
+
visit(sourceFile);
|
|
626
|
+
return count;
|
|
627
|
+
}
|
|
628
|
+
function getTopLevelFunctionDeclaration(sourceFile, functionName) {
|
|
629
|
+
const declarations = sourceFile.statements.filter((statement) => ts.isFunctionDeclaration(statement) &&
|
|
630
|
+
statement.name?.text === functionName &&
|
|
631
|
+
statement.body !== undefined);
|
|
632
|
+
return declarations.length === 1 ? declarations[0] : null;
|
|
633
|
+
}
|
|
634
|
+
function getDirectAwaitedCall(statement) {
|
|
635
|
+
if (!ts.isExpressionStatement(statement)) {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
const expression = unwrapStaticExpression(statement.expression);
|
|
639
|
+
if (!ts.isAwaitExpression(expression)) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
const awaitedExpression = unwrapStaticExpression(expression.expression);
|
|
643
|
+
return ts.isCallExpression(awaitedExpression) ? awaitedExpression : null;
|
|
644
|
+
}
|
|
645
|
+
function isNonCheckArgumentGuard(node, argumentBinding) {
|
|
646
|
+
if (!ts.isIfStatement(node) ||
|
|
647
|
+
node.elseStatement ||
|
|
648
|
+
!ts.isBinaryExpression(node.expression) ||
|
|
649
|
+
node.expression.operatorToken.kind !==
|
|
650
|
+
ts.SyntaxKind.EqualsEqualsEqualsToken) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
const { left, right } = node.expression;
|
|
654
|
+
return (((ts.isIdentifier(left) &&
|
|
655
|
+
left.text === argumentBinding &&
|
|
656
|
+
ts.isStringLiteralLike(right) &&
|
|
657
|
+
right.text !== '--check') ||
|
|
658
|
+
(ts.isStringLiteralLike(left) &&
|
|
659
|
+
left.text !== '--check' &&
|
|
660
|
+
ts.isIdentifier(right) &&
|
|
661
|
+
right.text === argumentBinding)));
|
|
662
|
+
}
|
|
663
|
+
function isLoopStatement(node) {
|
|
664
|
+
return (ts.isDoStatement(node) ||
|
|
665
|
+
ts.isForInStatement(node) ||
|
|
666
|
+
ts.isForOfStatement(node) ||
|
|
667
|
+
ts.isForStatement(node) ||
|
|
668
|
+
ts.isWhileStatement(node));
|
|
669
|
+
}
|
|
670
|
+
function labelsLoop(statement) {
|
|
671
|
+
let target = statement;
|
|
672
|
+
while (ts.isLabeledStatement(target)) {
|
|
673
|
+
target = target.statement;
|
|
674
|
+
}
|
|
675
|
+
return isLoopStatement(target);
|
|
676
|
+
}
|
|
677
|
+
function containsParserControlFlow(node, argumentBinding, check, breakableDepth = 0, loopDepth = 0, outerContinueIsSafe = false, activeLabels = new Map()) {
|
|
678
|
+
if (ts.isFunctionLike(node)) {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
if (check === 'outer-break-or-return') {
|
|
682
|
+
if (ts.isReturnStatement(node)) {
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
if (ts.isBreakStatement(node)) {
|
|
686
|
+
return node.label
|
|
687
|
+
? !activeLabels.has(node.label.text)
|
|
688
|
+
: breakableDepth === 0;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
else if (ts.isContinueStatement(node)) {
|
|
692
|
+
return node.label
|
|
693
|
+
? activeLabels.get(node.label.text) !== true
|
|
694
|
+
: loopDepth === 0 && !outerContinueIsSafe;
|
|
695
|
+
}
|
|
696
|
+
if (check === 'unsafe-continue' &&
|
|
697
|
+
isNonCheckArgumentGuard(node, argumentBinding)) {
|
|
698
|
+
return containsParserControlFlow(node.thenStatement, argumentBinding, check, breakableDepth, loopDepth, true, activeLabels);
|
|
699
|
+
}
|
|
700
|
+
if (ts.isLabeledStatement(node)) {
|
|
701
|
+
const nestedLabels = new Map(activeLabels);
|
|
702
|
+
nestedLabels.set(node.label.text, labelsLoop(node.statement));
|
|
703
|
+
return containsParserControlFlow(node.statement, argumentBinding, check, breakableDepth, loopDepth, outerContinueIsSafe, nestedLabels);
|
|
704
|
+
}
|
|
705
|
+
const nextBreakableDepth = breakableDepth +
|
|
706
|
+
(isLoopStatement(node) || ts.isSwitchStatement(node) ? 1 : 0);
|
|
707
|
+
const nextLoopDepth = loopDepth + (isLoopStatement(node) ? 1 : 0);
|
|
708
|
+
let found = false;
|
|
709
|
+
ts.forEachChild(node, (child) => {
|
|
710
|
+
if (!found &&
|
|
711
|
+
containsParserControlFlow(child, argumentBinding, check, nextBreakableDepth, nextLoopDepth, outerContinueIsSafe, activeLabels)) {
|
|
712
|
+
found = true;
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
return found;
|
|
716
|
+
}
|
|
717
|
+
function getObjectLiteralProperties(expression) {
|
|
718
|
+
const objectLiteral = unwrapStaticExpression(expression);
|
|
719
|
+
if (!ts.isObjectLiteralExpression(objectLiteral)) {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
const properties = new Map();
|
|
723
|
+
for (const property of objectLiteral.properties) {
|
|
724
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
const propertyName = getStaticPropertyName(property.name);
|
|
728
|
+
if (propertyName === null || properties.has(propertyName)) {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
properties.set(propertyName, unwrapStaticExpression(property.initializer));
|
|
732
|
+
}
|
|
733
|
+
return properties;
|
|
734
|
+
}
|
|
735
|
+
function isIdentifierPropertyAccess(expression, objectName, propertyName) {
|
|
736
|
+
return (expression !== undefined &&
|
|
737
|
+
ts.isPropertyAccessExpression(expression) &&
|
|
738
|
+
ts.isIdentifier(expression.expression) &&
|
|
739
|
+
expression.expression.text === objectName &&
|
|
740
|
+
expression.name.text === propertyName);
|
|
741
|
+
}
|
|
742
|
+
function isStringValue(expression, expected) {
|
|
743
|
+
return (expression !== undefined &&
|
|
744
|
+
ts.isStringLiteralLike(expression) &&
|
|
745
|
+
expression.text === expected);
|
|
746
|
+
}
|
|
747
|
+
function isIdentifierValue(expression, expected) {
|
|
748
|
+
return (expression !== undefined &&
|
|
749
|
+
ts.isIdentifier(expression) &&
|
|
750
|
+
expression.text === expected);
|
|
751
|
+
}
|
|
752
|
+
function isTemplatePathValue(expression, bindingName, suffix) {
|
|
753
|
+
if (!expression || !ts.isTemplateExpression(expression)) {
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
return (expression.head.text === 'src/api-schemas/' &&
|
|
757
|
+
expression.templateSpans.length === 1 &&
|
|
758
|
+
ts.isIdentifier(expression.templateSpans[0].expression) &&
|
|
759
|
+
expression.templateSpans[0].expression.text === bindingName &&
|
|
760
|
+
expression.templateSpans[0].literal.text === suffix);
|
|
761
|
+
}
|
|
762
|
+
function isContractTitleTemplate(expression, contractBinding) {
|
|
763
|
+
return (expression !== undefined &&
|
|
764
|
+
ts.isTemplateExpression(expression) &&
|
|
765
|
+
expression.head.text === '' &&
|
|
766
|
+
expression.templateSpans.length === 1 &&
|
|
767
|
+
isIdentifierPropertyAccess(expression.templateSpans[0].expression, contractBinding, 'sourceTypeName') &&
|
|
768
|
+
expression.templateSpans[0].literal.text === '');
|
|
769
|
+
}
|
|
770
|
+
function hasCanonicalOpenApiInfo(expression, contractBinding) {
|
|
771
|
+
if (!expression)
|
|
772
|
+
return false;
|
|
773
|
+
const properties = getObjectLiteralProperties(expression);
|
|
774
|
+
return (properties?.size === 2 &&
|
|
775
|
+
isContractTitleTemplate(properties.get('title'), contractBinding) &&
|
|
776
|
+
isStringValue(properties.get('version'), '1.0.0'));
|
|
777
|
+
}
|
|
778
|
+
function hasCanonicalCheckOptions(expression, optionsBinding) {
|
|
779
|
+
const properties = getObjectLiteralProperties(expression);
|
|
780
|
+
return (properties?.size === 1 &&
|
|
781
|
+
isIdentifierPropertyAccess(properties.get('check'), optionsBinding, 'check'));
|
|
782
|
+
}
|
|
783
|
+
function getDirectAwaitedVariableCall(statement) {
|
|
784
|
+
if (!ts.isVariableStatement(statement) ||
|
|
785
|
+
!(statement.declarationList.flags & ts.NodeFlags.Const) ||
|
|
786
|
+
statement.declarationList.declarations.length !== 1) {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
const declaration = statement.declarationList.declarations[0];
|
|
790
|
+
if (!ts.isIdentifier(declaration.name) ||
|
|
791
|
+
!declaration.initializer ||
|
|
792
|
+
!ts.isAwaitExpression(declaration.initializer)) {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
const awaited = unwrapStaticExpression(declaration.initializer.expression);
|
|
796
|
+
return ts.isCallExpression(awaited)
|
|
797
|
+
? { binding: declaration.name.text, call: awaited }
|
|
798
|
+
: null;
|
|
799
|
+
}
|
|
800
|
+
function hasCanonicalTrueCheck(expression) {
|
|
801
|
+
const properties = getObjectLiteralProperties(expression);
|
|
802
|
+
return (properties?.size === 1 &&
|
|
803
|
+
properties.get('check')?.kind === ts.SyntaxKind.TrueKeyword);
|
|
804
|
+
}
|
|
805
|
+
function hasDirectThrowingBlock(statement) {
|
|
806
|
+
if (!ts.isIfStatement(statement) ||
|
|
807
|
+
statement.elseStatement ||
|
|
808
|
+
!ts.isBlock(statement.thenStatement)) {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
const statements = statement.thenStatement.statements;
|
|
812
|
+
if (statements.length !== 1 || !ts.isThrowStatement(statements[0])) {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
return (statements[0].expression !== undefined &&
|
|
816
|
+
isSafeErrorConstruction(statements[0].expression));
|
|
817
|
+
}
|
|
818
|
+
function isStaleArtifactFailureGuard(statement, reportBinding) {
|
|
819
|
+
if (!hasDirectThrowingBlock(statement) ||
|
|
820
|
+
!ts.isIfStatement(statement) ||
|
|
821
|
+
!ts.isBinaryExpression(statement.expression) ||
|
|
822
|
+
statement.expression.operatorToken.kind !==
|
|
823
|
+
ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
|
824
|
+
!ts.isPropertyAccessExpression(statement.expression.left) ||
|
|
825
|
+
statement.expression.left.name.text !== 'code' ||
|
|
826
|
+
!isIdentifierPropertyAccess(statement.expression.left.expression, reportBinding, 'failure') ||
|
|
827
|
+
!ts.isStringLiteralLike(statement.expression.right)) {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
return statement.expression.right.text === 'stale-generated-artifact';
|
|
831
|
+
}
|
|
832
|
+
function isCatchAllFailureGuard(statement, reportBinding) {
|
|
833
|
+
return (hasDirectThrowingBlock(statement) &&
|
|
834
|
+
ts.isIfStatement(statement) &&
|
|
835
|
+
isIdentifierPropertyAccess(statement.expression, reportBinding, 'failure'));
|
|
836
|
+
}
|
|
837
|
+
function hasCanonicalTypeArtifactPreflight(sourceFile, sourceTypeName) {
|
|
838
|
+
const declaration = getTopLevelFunctionDeclaration(sourceFile, 'assertTypeArtifactsCurrent');
|
|
839
|
+
const syncBindings = getNamedImportBindings(sourceFile, 'runSyncBlockMetadata');
|
|
840
|
+
if (!declaration?.body ||
|
|
841
|
+
declaration.parameters.length !== 0 ||
|
|
842
|
+
declaration.asteriskToken !== undefined ||
|
|
843
|
+
!declaration.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) ||
|
|
844
|
+
declaration.body.statements.length !== 3 ||
|
|
845
|
+
countBindingDeclarations(sourceFile, 'assertTypeArtifactsCurrent') !== 1 ||
|
|
846
|
+
syncBindings.size === 0 ||
|
|
847
|
+
hasShadowedImportBinding(sourceFile, syncBindings)) {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
const invocation = getDirectAwaitedVariableCall(declaration.body.statements[0]);
|
|
851
|
+
if (!invocation ||
|
|
852
|
+
!ts.isIdentifier(invocation.call.expression) ||
|
|
853
|
+
!syncBindings.has(invocation.call.expression.text) ||
|
|
854
|
+
invocation.call.arguments.length !== 2 ||
|
|
855
|
+
!hasCanonicalTrueCheck(invocation.call.arguments[1])) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
const input = getObjectLiteralProperties(invocation.call.arguments[0]);
|
|
859
|
+
const preflightSourceTypeName = input?.get('sourceTypeName');
|
|
860
|
+
if (input?.size !== 6 ||
|
|
861
|
+
!isStringValue(input.get('blockJsonFile'), 'src/block.json') ||
|
|
862
|
+
!isStringValue(input.get('jsonSchemaFile'), STANDALONE_PERSISTENCE_BLOCK_SCHEMA_ARTIFACTS.jsonSchemaFile) ||
|
|
863
|
+
!isStringValue(input.get('manifestFile'), 'src/typia.manifest.json') ||
|
|
864
|
+
!isStringValue(input.get('openApiFile'), STANDALONE_PERSISTENCE_BLOCK_SCHEMA_ARTIFACTS.openApiFile) ||
|
|
865
|
+
!isStringValue(input.get('typesFile'), 'src/types.ts') ||
|
|
866
|
+
preflightSourceTypeName === undefined ||
|
|
867
|
+
!ts.isStringLiteralLike(preflightSourceTypeName) ||
|
|
868
|
+
(sourceTypeName !== null && preflightSourceTypeName.text !== sourceTypeName)) {
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
return (isStaleArtifactFailureGuard(declaration.body.statements[1], invocation.binding) &&
|
|
872
|
+
isCatchAllFailureGuard(declaration.body.statements[2], invocation.binding));
|
|
873
|
+
}
|
|
874
|
+
function isCanonicalRestSyncCall(call, bindings, optionsBinding, validateInput) {
|
|
875
|
+
if (call === null ||
|
|
876
|
+
!ts.isIdentifier(call.expression) ||
|
|
877
|
+
!bindings.has(call.expression.text) ||
|
|
878
|
+
call.arguments.length !== 2 ||
|
|
879
|
+
!hasCanonicalCheckOptions(call.arguments[1], optionsBinding)) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
const inputProperties = getObjectLiteralProperties(call.arguments[0]);
|
|
883
|
+
return inputProperties !== null && validateInput(inputProperties);
|
|
884
|
+
}
|
|
885
|
+
function getRestContractsLoopBindings(statement) {
|
|
886
|
+
if (!ts.isForOfStatement(statement)) {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
const expression = unwrapStaticExpression(statement.expression);
|
|
890
|
+
if (!ts.isCallExpression(expression) ||
|
|
891
|
+
!ts.isPropertyAccessExpression(expression.expression) ||
|
|
892
|
+
!ts.isIdentifier(expression.expression.expression) ||
|
|
893
|
+
expression.expression.expression.text !== 'Object' ||
|
|
894
|
+
expression.expression.name.text !== 'entries' ||
|
|
895
|
+
expression.arguments.length !== 1) {
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
const entriesTarget = unwrapStaticExpression(expression.arguments[0]);
|
|
899
|
+
if (!ts.isPropertyAccessExpression(entriesTarget) ||
|
|
900
|
+
!ts.isIdentifier(entriesTarget.expression) ||
|
|
901
|
+
entriesTarget.expression.text !== 'REST_ENDPOINT_MANIFEST' ||
|
|
902
|
+
entriesTarget.name.text !== 'contracts' ||
|
|
903
|
+
!ts.isVariableDeclarationList(statement.initializer) ||
|
|
904
|
+
statement.initializer.declarations.length !== 1) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
const loopBinding = statement.initializer.declarations[0].name;
|
|
908
|
+
if (!ts.isArrayBindingPattern(loopBinding) ||
|
|
909
|
+
loopBinding.elements.length !== 2 ||
|
|
910
|
+
loopBinding.elements.some((element) => ts.isOmittedExpression(element) || !ts.isIdentifier(element.name))) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
return {
|
|
914
|
+
baseName: loopBinding.elements[0].name.getText(),
|
|
915
|
+
contract: loopBinding.elements[1].name.getText(),
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
function getMainOptionsBinding(mainBody) {
|
|
919
|
+
const bindings = [];
|
|
920
|
+
mainBody.statements.forEach((statement, index) => {
|
|
921
|
+
if (!ts.isVariableStatement(statement) ||
|
|
922
|
+
!(statement.declarationList.flags & ts.NodeFlags.Const) ||
|
|
923
|
+
statement.declarationList.declarations.length !== 1) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
927
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
928
|
+
declaration.initializer &&
|
|
929
|
+
isCanonicalRestParseCall(declaration.initializer)) {
|
|
930
|
+
bindings.push({ binding: declaration.name.text, index });
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
return bindings.length === 1 ? bindings[0] : null;
|
|
935
|
+
}
|
|
936
|
+
function isCanonicalRestParseCall(expression) {
|
|
937
|
+
if (!ts.isCallExpression(expression) ||
|
|
938
|
+
!ts.isIdentifier(expression.expression) ||
|
|
939
|
+
expression.expression.text !== 'parseCliOptions' ||
|
|
940
|
+
expression.arguments.length !== 1) {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
const argvSlice = unwrapStaticExpression(expression.arguments[0]);
|
|
944
|
+
return (ts.isCallExpression(argvSlice) &&
|
|
945
|
+
ts.isPropertyAccessExpression(argvSlice.expression) &&
|
|
946
|
+
argvSlice.expression.name.text === 'slice' &&
|
|
947
|
+
ts.isPropertyAccessExpression(argvSlice.expression.expression) &&
|
|
948
|
+
ts.isIdentifier(argvSlice.expression.expression.expression) &&
|
|
949
|
+
argvSlice.expression.expression.expression.text === 'process' &&
|
|
950
|
+
argvSlice.expression.expression.name.text === 'argv' &&
|
|
951
|
+
argvSlice.arguments.length === 1 &&
|
|
952
|
+
ts.isNumericLiteral(argvSlice.arguments[0]) &&
|
|
953
|
+
argvSlice.arguments[0].text === '2');
|
|
954
|
+
}
|
|
955
|
+
function countIdentifierOccurrences(node, binding) {
|
|
956
|
+
let count = 0;
|
|
957
|
+
function visit(candidate) {
|
|
958
|
+
if (ts.isIdentifier(candidate) && candidate.text === binding) {
|
|
959
|
+
count += 1;
|
|
960
|
+
}
|
|
961
|
+
ts.forEachChild(candidate, visit);
|
|
962
|
+
}
|
|
963
|
+
visit(node);
|
|
964
|
+
return count;
|
|
965
|
+
}
|
|
966
|
+
function hasCanonicalRestCheckParser(sourceFile) {
|
|
967
|
+
const parser = getTopLevelFunctionDeclaration(sourceFile, 'parseCliOptions');
|
|
968
|
+
if (!parser?.body ||
|
|
969
|
+
parser.parameters.length !== 1 ||
|
|
970
|
+
!ts.isIdentifier(parser.parameters[0].name) ||
|
|
971
|
+
parser.parameters[0].dotDotDotToken !== undefined ||
|
|
972
|
+
parser.parameters[0].initializer !== undefined ||
|
|
973
|
+
parser.asteriskToken !== undefined ||
|
|
974
|
+
parser.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) ||
|
|
975
|
+
parser.body.statements.length !== 3 ||
|
|
976
|
+
countBindingDeclarations(sourceFile, 'parseCliOptions') !== 1 ||
|
|
977
|
+
hasImportedBinding(sourceFile, 'Error') ||
|
|
978
|
+
hasShadowedImportBinding(sourceFile, new Set(['Error']))) {
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
const optionsStatement = parser.body.statements[0];
|
|
982
|
+
if (!ts.isVariableStatement(optionsStatement) ||
|
|
983
|
+
!(optionsStatement.declarationList.flags & ts.NodeFlags.Const) ||
|
|
984
|
+
optionsStatement.declarationList.declarations.length !== 1) {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
const optionsDeclaration = optionsStatement.declarationList.declarations[0];
|
|
988
|
+
if (!ts.isIdentifier(optionsDeclaration.name) ||
|
|
989
|
+
!optionsDeclaration.initializer) {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
const optionsBinding = optionsDeclaration.name.text;
|
|
993
|
+
const options = getObjectLiteralProperties(optionsDeclaration.initializer);
|
|
994
|
+
if (!options ||
|
|
995
|
+
options.size !== 1 ||
|
|
996
|
+
options.get('check')?.kind !== ts.SyntaxKind.FalseKeyword) {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
const loop = parser.body.statements[1];
|
|
1000
|
+
if (!ts.isForOfStatement(loop) ||
|
|
1001
|
+
!ts.isIdentifier(loop.expression) ||
|
|
1002
|
+
loop.expression.text !== parser.parameters[0].name.text ||
|
|
1003
|
+
!ts.isVariableDeclarationList(loop.initializer) ||
|
|
1004
|
+
!(loop.initializer.flags & ts.NodeFlags.Const) ||
|
|
1005
|
+
loop.initializer.declarations.length !== 1 ||
|
|
1006
|
+
!ts.isIdentifier(loop.initializer.declarations[0].name) ||
|
|
1007
|
+
!ts.isBlock(loop.statement)) {
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
const argumentBinding = loop.initializer.declarations[0].name.text;
|
|
1011
|
+
const guardIndexes = loop.statement.statements.flatMap((statement, index) => {
|
|
1012
|
+
if (!ts.isIfStatement(statement) ||
|
|
1013
|
+
statement.elseStatement ||
|
|
1014
|
+
!ts.isBinaryExpression(statement.expression) ||
|
|
1015
|
+
statement.expression.operatorToken.kind !==
|
|
1016
|
+
ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
|
1017
|
+
!ts.isIdentifier(statement.expression.left) ||
|
|
1018
|
+
statement.expression.left.text !== argumentBinding ||
|
|
1019
|
+
!ts.isStringLiteralLike(statement.expression.right) ||
|
|
1020
|
+
statement.expression.right.text !== '--check' ||
|
|
1021
|
+
!ts.isBlock(statement.thenStatement) ||
|
|
1022
|
+
statement.thenStatement.statements.length !== 2) {
|
|
1023
|
+
return [];
|
|
1024
|
+
}
|
|
1025
|
+
const assignment = statement.thenStatement.statements[0];
|
|
1026
|
+
const expression = ts.isExpressionStatement(assignment)
|
|
1027
|
+
? assignment.expression
|
|
1028
|
+
: null;
|
|
1029
|
+
return expression &&
|
|
1030
|
+
ts.isBinaryExpression(expression) &&
|
|
1031
|
+
expression.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
1032
|
+
isIdentifierPropertyAccess(expression.left, optionsBinding, 'check') &&
|
|
1033
|
+
expression.right.kind === ts.SyntaxKind.TrueKeyword &&
|
|
1034
|
+
ts.isContinueStatement(statement.thenStatement.statements[1])
|
|
1035
|
+
? [index]
|
|
1036
|
+
: [];
|
|
1037
|
+
});
|
|
1038
|
+
if (guardIndexes.length !== 1 ||
|
|
1039
|
+
countIdentifierOccurrences(loop.statement, optionsBinding) !== 1 ||
|
|
1040
|
+
!hasOnlyCanonicalParserEffects(loop.statement, optionsBinding) ||
|
|
1041
|
+
!hasCanonicalUnknownFlagThrow(loop.statement, argumentBinding) ||
|
|
1042
|
+
countOuterParserContinues(loop.statement) !== 1 ||
|
|
1043
|
+
hasEarlierAbruptCompletion(loop.statement.statements, guardIndexes[0]) ||
|
|
1044
|
+
containsParserControlFlow(loop.statement, argumentBinding, 'outer-break-or-return') ||
|
|
1045
|
+
loop.statement.statements
|
|
1046
|
+
.slice(0, guardIndexes[0])
|
|
1047
|
+
.some((statement) => containsParserControlFlow(statement, argumentBinding, 'unsafe-continue'))) {
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
1050
|
+
const returnStatement = parser.body.statements[2];
|
|
1051
|
+
return (ts.isReturnStatement(returnStatement) &&
|
|
1052
|
+
returnStatement.expression !== undefined &&
|
|
1053
|
+
ts.isIdentifier(returnStatement.expression) &&
|
|
1054
|
+
returnStatement.expression.text === optionsBinding);
|
|
1055
|
+
}
|
|
1056
|
+
function hasTopLevelMainInvocation(sourceFile) {
|
|
1057
|
+
const main = getTopLevelFunctionDeclaration(sourceFile, 'main');
|
|
1058
|
+
const trustedGlobalBindings = new Set([
|
|
1059
|
+
'console',
|
|
1060
|
+
'Error',
|
|
1061
|
+
'Object',
|
|
1062
|
+
'process',
|
|
1063
|
+
]);
|
|
1064
|
+
const manifestBindings = getNamedImportBindings(sourceFile, 'defineEndpointManifest');
|
|
1065
|
+
if (!main?.body ||
|
|
1066
|
+
main.parameters.length !== 0 ||
|
|
1067
|
+
main.asteriskToken !== undefined ||
|
|
1068
|
+
!main.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) ||
|
|
1069
|
+
countBindingDeclarations(sourceFile, 'main') !== 1 ||
|
|
1070
|
+
[...trustedGlobalBindings].some((binding) => hasImportedBinding(sourceFile, binding)) ||
|
|
1071
|
+
hasShadowedImportBinding(sourceFile, trustedGlobalBindings)) {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
const invocationIndexes = sourceFile.statements.flatMap((statement, statementIndex) => {
|
|
1075
|
+
if (!ts.isExpressionStatement(statement)) {
|
|
1076
|
+
return [];
|
|
1077
|
+
}
|
|
1078
|
+
const expression = unwrapStaticExpression(statement.expression);
|
|
1079
|
+
if (!ts.isCallExpression(expression) ||
|
|
1080
|
+
!ts.isPropertyAccessExpression(expression.expression) ||
|
|
1081
|
+
expression.expression.name.text !== 'catch') {
|
|
1082
|
+
return [];
|
|
1083
|
+
}
|
|
1084
|
+
const receiver = unwrapStaticExpression(expression.expression.expression);
|
|
1085
|
+
if (!ts.isCallExpression(receiver) ||
|
|
1086
|
+
!ts.isIdentifier(receiver.expression) ||
|
|
1087
|
+
receiver.expression.text !== 'main' ||
|
|
1088
|
+
receiver.arguments.length !== 0 ||
|
|
1089
|
+
expression.arguments.length !== 1) {
|
|
1090
|
+
return [];
|
|
1091
|
+
}
|
|
1092
|
+
return isCanonicalSyncMainCatchHandler(expression.arguments[0])
|
|
1093
|
+
? [statementIndex]
|
|
1094
|
+
: [];
|
|
1095
|
+
});
|
|
1096
|
+
if (invocationIndexes.length !== 1) {
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
const [invocationIndex] = invocationIndexes;
|
|
1100
|
+
const hasEarlierMainCall = sourceFile.statements
|
|
1101
|
+
.slice(0, invocationIndex)
|
|
1102
|
+
.some((statement) => containsCompletion(statement, (candidate) => {
|
|
1103
|
+
if (!ts.isCallExpression(candidate)) {
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
const target = unwrapStaticExpression(candidate.expression);
|
|
1107
|
+
return ts.isIdentifier(target) && target.text === 'main';
|
|
1108
|
+
}));
|
|
1109
|
+
return (invocationIndex === sourceFile.statements.length - 1 &&
|
|
1110
|
+
sourceFile.statements
|
|
1111
|
+
.slice(0, invocationIndex)
|
|
1112
|
+
.every((statement) => isAllowedSyncHelperTopLevelStatement(statement, new Set(['@wp-typia/block-runtime/metadata-core']), (variableStatement) => {
|
|
1113
|
+
const initializer = getStaticEndpointManifestInitializer(variableStatement, manifestBindings);
|
|
1114
|
+
return (initializer !== null && parseStaticExpression(initializer).ok);
|
|
1115
|
+
})) &&
|
|
1116
|
+
!hasEarlierAbruptCompletion(sourceFile.statements, invocationIndex) &&
|
|
1117
|
+
!hasEarlierMainCall);
|
|
1118
|
+
}
|
|
1119
|
+
function hasCanonicalRestCompletionLog(statement, optionsBinding) {
|
|
1120
|
+
if (!statement || !ts.isExpressionStatement(statement)) {
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
const call = unwrapStaticExpression(statement.expression);
|
|
1124
|
+
if (!ts.isCallExpression(call) ||
|
|
1125
|
+
!ts.isPropertyAccessExpression(call.expression) ||
|
|
1126
|
+
!ts.isIdentifier(call.expression.expression) ||
|
|
1127
|
+
call.expression.expression.text !== 'console' ||
|
|
1128
|
+
call.expression.name.text !== 'log' ||
|
|
1129
|
+
call.arguments.length !== 1) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
const message = unwrapStaticExpression(call.arguments[0]);
|
|
1133
|
+
if (!ts.isConditionalExpression(message) ||
|
|
1134
|
+
!isIdentifierPropertyAccess(message.condition, optionsBinding, 'check')) {
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
const whenTrue = unwrapStaticExpression(message.whenTrue);
|
|
1138
|
+
const whenFalse = unwrapStaticExpression(message.whenFalse);
|
|
1139
|
+
return (ts.isStringLiteralLike(whenTrue) &&
|
|
1140
|
+
ts.isStringLiteralLike(whenFalse));
|
|
1141
|
+
}
|
|
1142
|
+
function hasCanonicalRestSyncCalls(sourceFile, sourceTypeName) {
|
|
1143
|
+
const syncTypeBindings = getNamedImportBindings(sourceFile, 'syncTypeSchemas');
|
|
1144
|
+
const syncOpenApiBindings = getNamedImportBindings(sourceFile, 'syncRestOpenApi');
|
|
1145
|
+
const syncClientBindings = getNamedImportBindings(sourceFile, 'syncEndpointClient');
|
|
1146
|
+
const bindingGroups = [
|
|
1147
|
+
syncTypeBindings,
|
|
1148
|
+
syncOpenApiBindings,
|
|
1149
|
+
syncClientBindings,
|
|
1150
|
+
];
|
|
1151
|
+
if (bindingGroups.some((bindings) => bindings.size === 0 || hasShadowedImportBinding(sourceFile, bindings))) {
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
const mainDeclaration = getTopLevelFunctionDeclaration(sourceFile, 'main');
|
|
1155
|
+
const mainBody = mainDeclaration?.body;
|
|
1156
|
+
if (!mainBody ||
|
|
1157
|
+
!mainDeclaration.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) ||
|
|
1158
|
+
hasImportedBinding(sourceFile, 'console') ||
|
|
1159
|
+
hasShadowedImportBinding(sourceFile, new Set(['console'])) ||
|
|
1160
|
+
!hasCanonicalRuntimeImports(sourceFile, REST_SYNC_RUNTIME_IMPORTS) ||
|
|
1161
|
+
!hasCanonicalRestCheckParser(sourceFile) ||
|
|
1162
|
+
!hasTopLevelMainInvocation(sourceFile)) {
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
const optionsDeclaration = getMainOptionsBinding(mainBody);
|
|
1166
|
+
if (!optionsDeclaration) {
|
|
1167
|
+
return false;
|
|
1168
|
+
}
|
|
1169
|
+
const optionsBinding = optionsDeclaration.binding;
|
|
1170
|
+
if (!hasCanonicalTypeArtifactPreflight(sourceFile, sourceTypeName)) {
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
const preflightCallIndexes = mainBody.statements.flatMap((statement, index) => {
|
|
1174
|
+
const call = getDirectAwaitedCall(statement);
|
|
1175
|
+
return call &&
|
|
1176
|
+
ts.isIdentifier(call.expression) &&
|
|
1177
|
+
call.expression.text === 'assertTypeArtifactsCurrent' &&
|
|
1178
|
+
call.arguments.length === 0
|
|
1179
|
+
? [index]
|
|
1180
|
+
: [];
|
|
1181
|
+
});
|
|
1182
|
+
if (preflightCallIndexes.length !== 1) {
|
|
1183
|
+
return false;
|
|
1184
|
+
}
|
|
1185
|
+
const [preflightCallIndex] = preflightCallIndexes;
|
|
1186
|
+
const schemaCallIndexes = [];
|
|
1187
|
+
mainBody.statements.forEach((statement, index) => {
|
|
1188
|
+
const loopBindings = getRestContractsLoopBindings(statement);
|
|
1189
|
+
if (!loopBindings || !ts.isForOfStatement(statement))
|
|
1190
|
+
return;
|
|
1191
|
+
const loopStatements = ts.isBlock(statement.statement)
|
|
1192
|
+
? statement.statement.statements
|
|
1193
|
+
: [statement.statement];
|
|
1194
|
+
if (loopStatements.length !== 1)
|
|
1195
|
+
return;
|
|
1196
|
+
const matchingCallIndexes = loopStatements.flatMap((loopStatement, loopStatementIndex) => isCanonicalRestSyncCall(getDirectAwaitedCall(loopStatement), syncTypeBindings, optionsBinding, (properties) => properties.size === 5 &&
|
|
1197
|
+
isTemplatePathValue(properties.get('jsonSchemaFile'), loopBindings.baseName, '.schema.json') &&
|
|
1198
|
+
isTemplatePathValue(properties.get('openApiFile'), loopBindings.baseName, '.openapi.json') &&
|
|
1199
|
+
hasCanonicalOpenApiInfo(properties.get('openApiInfo'), loopBindings.contract) &&
|
|
1200
|
+
isIdentifierPropertyAccess(properties.get('sourceTypeName'), loopBindings.contract, 'sourceTypeName') &&
|
|
1201
|
+
isStringValue(properties.get('typesFile'), 'src/api-types.ts'))
|
|
1202
|
+
? [loopStatementIndex]
|
|
1203
|
+
: []);
|
|
1204
|
+
if (matchingCallIndexes.length === 1)
|
|
1205
|
+
schemaCallIndexes.push(index);
|
|
1206
|
+
});
|
|
1207
|
+
const openApiCallIndexes = [];
|
|
1208
|
+
const clientCallIndexes = [];
|
|
1209
|
+
mainBody.statements.forEach((statement, index) => {
|
|
1210
|
+
const call = getDirectAwaitedCall(statement);
|
|
1211
|
+
if (isCanonicalRestSyncCall(call, syncOpenApiBindings, optionsBinding, (properties) => properties.size === 3 &&
|
|
1212
|
+
isIdentifierValue(properties.get('manifest'), 'REST_ENDPOINT_MANIFEST') &&
|
|
1213
|
+
isStringValue(properties.get('openApiFile'), 'src/api.openapi.json') &&
|
|
1214
|
+
isStringValue(properties.get('typesFile'), 'src/api-types.ts'))) {
|
|
1215
|
+
openApiCallIndexes.push(index);
|
|
1216
|
+
}
|
|
1217
|
+
if (isCanonicalRestSyncCall(call, syncClientBindings, optionsBinding, (properties) => properties.size === 3 &&
|
|
1218
|
+
isIdentifierValue(properties.get('manifest'), 'REST_ENDPOINT_MANIFEST') &&
|
|
1219
|
+
isStringValue(properties.get('clientFile'), 'src/api-client.ts') &&
|
|
1220
|
+
isStringValue(properties.get('typesFile'), 'src/api-types.ts'))) {
|
|
1221
|
+
clientCallIndexes.push(index);
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
if (schemaCallIndexes.length !== 1 ||
|
|
1225
|
+
openApiCallIndexes.length !== 1 ||
|
|
1226
|
+
clientCallIndexes.length !== 1) {
|
|
1227
|
+
return false;
|
|
1228
|
+
}
|
|
1229
|
+
const [schemaCallIndex] = schemaCallIndexes;
|
|
1230
|
+
const [openApiCallIndex] = openApiCallIndexes;
|
|
1231
|
+
const [clientCallIndex] = clientCallIndexes;
|
|
1232
|
+
return (optionsDeclaration.index === 0 &&
|
|
1233
|
+
preflightCallIndex === 1 &&
|
|
1234
|
+
schemaCallIndex === 2 &&
|
|
1235
|
+
openApiCallIndex === 3 &&
|
|
1236
|
+
clientCallIndex === 4 &&
|
|
1237
|
+
mainBody.statements.length === 6 &&
|
|
1238
|
+
hasCanonicalRestCompletionLog(mainBody.statements[clientCallIndex + 1], optionsBinding) &&
|
|
1239
|
+
!hasEarlierAbruptCompletion(mainBody.statements, clientCallIndex));
|
|
1240
|
+
}
|
|
1241
|
+
/** Parse the generated persistence REST contract without executing project code. */
|
|
1242
|
+
export function parseStandaloneRestConfig(projectDir, requiresRest, sourceTypeName) {
|
|
1243
|
+
const syncRestPath = path.join(projectDir, STANDALONE_SYNC_REST_SCRIPT);
|
|
1244
|
+
if (!fs.existsSync(syncRestPath)) {
|
|
1245
|
+
return {
|
|
1246
|
+
artifactPaths: [],
|
|
1247
|
+
manifest: null,
|
|
1248
|
+
problem: requiresRest
|
|
1249
|
+
? `Missing generated helper ${STANDALONE_SYNC_REST_SCRIPT}`
|
|
1250
|
+
: null,
|
|
1251
|
+
requiresRest,
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
let source;
|
|
1255
|
+
try {
|
|
1256
|
+
source = fs.readFileSync(syncRestPath, 'utf8');
|
|
1257
|
+
}
|
|
1258
|
+
catch {
|
|
1259
|
+
return {
|
|
1260
|
+
artifactPaths: [],
|
|
1261
|
+
manifest: null,
|
|
1262
|
+
problem: `Unable to read generated helper ${STANDALONE_SYNC_REST_SCRIPT}`,
|
|
1263
|
+
requiresRest,
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
if (hasTypeScriptSyntaxErrors(source, syncRestPath)) {
|
|
1267
|
+
return {
|
|
1268
|
+
artifactPaths: [],
|
|
1269
|
+
manifest: null,
|
|
1270
|
+
problem: `${STANDALONE_SYNC_REST_SCRIPT} contains TypeScript syntax errors.`,
|
|
1271
|
+
requiresRest,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
const sourceFile = ts.createSourceFile(syncRestPath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
1275
|
+
const manifestBindings = getNamedImportBindings(sourceFile, 'defineEndpointManifest');
|
|
1276
|
+
if (manifestBindings.size === 0 ||
|
|
1277
|
+
hasShadowedImportBinding(sourceFile, manifestBindings)) {
|
|
1278
|
+
return {
|
|
1279
|
+
artifactPaths: [],
|
|
1280
|
+
manifest: null,
|
|
1281
|
+
problem: `${STANDALONE_SYNC_REST_SCRIPT} must not shadow its canonical ` +
|
|
1282
|
+
'defineEndpointManifest() import binding.',
|
|
1283
|
+
requiresRest,
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
if (!hasCanonicalRestSyncCalls(sourceFile, sourceTypeName)) {
|
|
1287
|
+
return {
|
|
1288
|
+
artifactPaths: [],
|
|
1289
|
+
manifest: null,
|
|
1290
|
+
problem: `${STANDALONE_SYNC_REST_SCRIPT} must call syncTypeSchemas(), ` +
|
|
1291
|
+
'syncRestOpenApi(), and syncEndpointClient() through canonical metadata-core imports.',
|
|
1292
|
+
requiresRest,
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
const manifest = findStaticEndpointManifest(sourceFile, manifestBindings);
|
|
1296
|
+
if (!manifest ||
|
|
1297
|
+
countBindingDeclarations(sourceFile, 'REST_ENDPOINT_MANIFEST') !== 1) {
|
|
1298
|
+
return {
|
|
1299
|
+
artifactPaths: [],
|
|
1300
|
+
manifest: null,
|
|
1301
|
+
problem: `${STANDALONE_SYNC_REST_SCRIPT} must define a static endpoint ` +
|
|
1302
|
+
'manifest through defineEndpointManifest().',
|
|
1303
|
+
requiresRest,
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
const relativeArtifactPaths = getStandaloneRestArtifactPaths(manifest);
|
|
1307
|
+
const unsafeArtifactPath = relativeArtifactPaths.find((artifactPath) => !isSafeProjectRelativePath(projectDir, artifactPath));
|
|
1308
|
+
if (unsafeArtifactPath) {
|
|
1309
|
+
return {
|
|
1310
|
+
artifactPaths: [],
|
|
1311
|
+
manifest: null,
|
|
1312
|
+
problem: `${STANDALONE_SYNC_REST_SCRIPT} references an unsafe REST contract name.`,
|
|
1313
|
+
requiresRest,
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
return {
|
|
1317
|
+
artifactPaths: relativeArtifactPaths.map((artifactPath) => path.resolve(projectDir, artifactPath)),
|
|
1318
|
+
manifest,
|
|
1319
|
+
problem: null,
|
|
1320
|
+
requiresRest,
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
/** Check every persistence REST artifact through the installed project runtime. */
|
|
1324
|
+
export async function checkStandaloneRestArtifacts(projectDir, config, metadataCore) {
|
|
1325
|
+
if (!config.manifest) {
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
for (const [baseName, contract] of Object.entries(config.manifest.contracts)) {
|
|
1329
|
+
const { jsonSchemaFile, openApiFile } = getStandaloneRestContractArtifactPaths(baseName);
|
|
1330
|
+
await metadataCore.syncTypeSchemas({
|
|
1331
|
+
jsonSchemaFile,
|
|
1332
|
+
openApiFile,
|
|
1333
|
+
openApiInfo: {
|
|
1334
|
+
title: contract.sourceTypeName,
|
|
1335
|
+
version: '1.0.0',
|
|
1336
|
+
},
|
|
1337
|
+
projectRoot: projectDir,
|
|
1338
|
+
sourceTypeName: contract.sourceTypeName,
|
|
1339
|
+
typesFile: 'src/api-types.ts',
|
|
1340
|
+
}, { check: true });
|
|
1341
|
+
}
|
|
1342
|
+
await metadataCore.syncRestOpenApi({
|
|
1343
|
+
manifest: config.manifest,
|
|
1344
|
+
openApiFile: STANDALONE_REST_OPEN_API_FILE,
|
|
1345
|
+
projectRoot: projectDir,
|
|
1346
|
+
typesFile: 'src/api-types.ts',
|
|
1347
|
+
}, { check: true });
|
|
1348
|
+
await metadataCore.syncEndpointClient({
|
|
1349
|
+
clientFile: STANDALONE_REST_CLIENT_FILE,
|
|
1350
|
+
manifest: config.manifest,
|
|
1351
|
+
projectRoot: projectDir,
|
|
1352
|
+
typesFile: 'src/api-types.ts',
|
|
1353
|
+
}, { check: true });
|
|
1354
|
+
}
|