create-wp-typia 0.1.0
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/README.md +33 -0
- package/dist/cli.js +87837 -0
- package/dist/highlights-eq9cgrbb.scm +604 -0
- package/dist/highlights-ghv9g403.scm +205 -0
- package/dist/highlights-hk7bwhj4.scm +284 -0
- package/dist/highlights-r812a2qc.scm +150 -0
- package/dist/highlights-x6tmsnaa.scm +115 -0
- package/dist/injections-73j83es3.scm +27 -0
- package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
- package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
- package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
- package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
- package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
- package/lib/entry.js +29 -0
- package/lib/node-cli.js +326 -0
- package/lib/package-managers.d.ts +29 -0
- package/lib/package-managers.js +170 -0
- package/lib/scaffold.d.ts +64 -0
- package/lib/scaffold.js +565 -0
- package/lib/template-registry.d.ts +18 -0
- package/lib/template-registry.js +58 -0
- package/package.json +64 -0
- package/src/cli.ts +329 -0
- package/templates/advanced/README.md.mustache +70 -0
- package/templates/advanced/block.json.mustache +42 -0
- package/templates/advanced/index.js +21 -0
- package/templates/advanced/package.json.mustache +48 -0
- package/templates/advanced/scripts/generate-migrations.ts.mustache +267 -0
- package/templates/advanced/scripts/lib/typia-metadata-core.ts +806 -0
- package/templates/advanced/scripts/migration-cli.ts.mustache +260 -0
- package/templates/advanced/scripts/sync-types-to-block-json.ts.mustache +25 -0
- package/templates/advanced/src/admin/migration-dashboard.tsx.mustache +450 -0
- package/templates/advanced/src/components/ErrorBoundary.tsx.mustache +47 -0
- package/templates/advanced/src/deprecated.ts.mustache +184 -0
- package/templates/advanced/src/edit.tsx.mustache +93 -0
- package/templates/advanced/src/hooks/useDebounce.ts.mustache +20 -0
- package/templates/advanced/src/hooks/useLocalStorage.ts.mustache +31 -0
- package/templates/advanced/src/hooks.ts.mustache +56 -0
- package/templates/advanced/src/index.tsx.mustache +16 -0
- package/templates/advanced/src/migration-detector.ts.mustache +417 -0
- package/templates/advanced/src/migrations/index.ts.mustache +361 -0
- package/templates/advanced/src/save.tsx.mustache +40 -0
- package/templates/advanced/src/style.scss.mustache +84 -0
- package/templates/advanced/src/types/versions.ts.mustache +108 -0
- package/templates/advanced/src/types.ts.mustache +45 -0
- package/templates/advanced/src/utils/classnames.ts.mustache +51 -0
- package/templates/advanced/src/utils/debounce.ts.mustache +37 -0
- package/templates/advanced/src/utils/index.ts.mustache +7 -0
- package/templates/advanced/src/utils/uuid.ts.mustache +17 -0
- package/templates/advanced/src/validators.ts.mustache +39 -0
- package/templates/advanced/src/view.ts.mustache +59 -0
- package/templates/advanced/tsconfig.json.mustache +9 -0
- package/templates/advanced/webpack.config.js.mustache +85 -0
- package/templates/basic/package.json.mustache +39 -0
- package/templates/basic/scripts/lib/typia-metadata-core.ts +806 -0
- package/templates/basic/scripts/sync-types-to-block-json.ts +25 -0
- package/templates/basic/src/block.json +51 -0
- package/templates/basic/src/edit.tsx +85 -0
- package/templates/basic/src/hooks.ts +75 -0
- package/templates/basic/src/index.tsx +37 -0
- package/templates/basic/src/save.tsx +27 -0
- package/templates/basic/src/style.scss +42 -0
- package/templates/basic/src/types.ts +47 -0
- package/templates/basic/src/validators.ts +39 -0
- package/templates/basic/tsconfig.json +20 -0
- package/templates/basic/webpack.config.js +85 -0
- package/templates/full/package.json.mustache +40 -0
- package/templates/full/scripts/lib/typia-metadata-core.ts +806 -0
- package/templates/full/scripts/sync-types-to-block-json.ts.mustache +25 -0
- package/templates/full/src/block.json.mustache +121 -0
- package/templates/full/src/edit.tsx.mustache +300 -0
- package/templates/full/src/editor.scss.mustache +251 -0
- package/templates/full/src/hooks.ts.mustache +140 -0
- package/templates/full/src/index.tsx.mustache +27 -0
- package/templates/full/src/save.tsx.mustache +39 -0
- package/templates/full/src/style.scss.mustache +224 -0
- package/templates/full/src/types.ts.mustache +34 -0
- package/templates/full/src/validators.ts.mustache +84 -0
- package/templates/full/tsconfig.json.mustache +9 -0
- package/templates/full/webpack.config.js.mustache +85 -0
- package/templates/interactivity/package.json.mustache +41 -0
- package/templates/interactivity/scripts/lib/typia-metadata-core.ts +806 -0
- package/templates/interactivity/scripts/sync-types-to-block-json.ts.mustache +25 -0
- package/templates/interactivity/src/block.json.mustache +75 -0
- package/templates/interactivity/src/edit.tsx.mustache +206 -0
- package/templates/interactivity/src/interactivity.ts.mustache +183 -0
- package/templates/interactivity/src/save.tsx.mustache +87 -0
- package/templates/interactivity/src/types.ts.mustache +29 -0
- package/templates/interactivity/tsconfig.json.mustache +9 -0
- package/templates/interactivity/webpack.config.js.mustache +85 -0
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
|
|
5
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
6
|
+
type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
|
7
|
+
type AttributeKind = "string" | "number" | "boolean" | "array" | "object";
|
|
8
|
+
|
|
9
|
+
interface AttributeConstraints {
|
|
10
|
+
format: string | null;
|
|
11
|
+
maxLength: number | null;
|
|
12
|
+
maximum: number | null;
|
|
13
|
+
minLength: number | null;
|
|
14
|
+
minimum: number | null;
|
|
15
|
+
pattern: string | null;
|
|
16
|
+
typeTag: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface AttributeNode {
|
|
20
|
+
constraints: AttributeConstraints;
|
|
21
|
+
defaultValue?: JsonValue;
|
|
22
|
+
enumValues: Array<string | number | boolean> | null;
|
|
23
|
+
items?: AttributeNode;
|
|
24
|
+
kind: AttributeKind;
|
|
25
|
+
path: string;
|
|
26
|
+
properties?: Record<string, AttributeNode>;
|
|
27
|
+
required: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface BlockJsonAttribute {
|
|
31
|
+
default?: JsonValue;
|
|
32
|
+
enum?: Array<string | number | boolean>;
|
|
33
|
+
type: AttributeKind;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ManifestAttribute {
|
|
37
|
+
typia: {
|
|
38
|
+
constraints: AttributeConstraints;
|
|
39
|
+
default: JsonValue | null;
|
|
40
|
+
};
|
|
41
|
+
ts: {
|
|
42
|
+
items: ManifestAttribute | null;
|
|
43
|
+
kind: AttributeKind;
|
|
44
|
+
properties: Record<string, ManifestAttribute> | null;
|
|
45
|
+
required: boolean;
|
|
46
|
+
};
|
|
47
|
+
wp: {
|
|
48
|
+
default: JsonValue | null;
|
|
49
|
+
enum: Array<string | number | boolean> | null;
|
|
50
|
+
type: AttributeKind;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ManifestDocument {
|
|
55
|
+
attributes: Record<string, ManifestAttribute>;
|
|
56
|
+
manifestVersion: 1;
|
|
57
|
+
sourceType: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SyncBlockMetadataOptions {
|
|
61
|
+
blockJsonFile: string;
|
|
62
|
+
manifestFile?: string;
|
|
63
|
+
projectRoot?: string;
|
|
64
|
+
sourceTypeName: string;
|
|
65
|
+
typesFile: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SyncBlockMetadataResult {
|
|
69
|
+
attributeNames: string[];
|
|
70
|
+
blockJsonPath: string;
|
|
71
|
+
lossyProjectionWarnings: string[];
|
|
72
|
+
manifestPath: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface AnalysisContext {
|
|
76
|
+
checker: ts.TypeChecker;
|
|
77
|
+
projectRoot: string;
|
|
78
|
+
program: ts.Program;
|
|
79
|
+
recursionGuard: Set<string>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const SUPPORTED_TAGS = new Set([
|
|
83
|
+
"Default",
|
|
84
|
+
"Format",
|
|
85
|
+
"MaxLength",
|
|
86
|
+
"Maximum",
|
|
87
|
+
"MinLength",
|
|
88
|
+
"Minimum",
|
|
89
|
+
"Pattern",
|
|
90
|
+
"Type",
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
const DEFAULT_CONSTRAINTS = (): AttributeConstraints => ({
|
|
94
|
+
format: null,
|
|
95
|
+
maxLength: null,
|
|
96
|
+
maximum: null,
|
|
97
|
+
minLength: null,
|
|
98
|
+
minimum: null,
|
|
99
|
+
pattern: null,
|
|
100
|
+
typeTag: null,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export async function syncBlockMetadata(
|
|
104
|
+
options: SyncBlockMetadataOptions,
|
|
105
|
+
): Promise<SyncBlockMetadataResult> {
|
|
106
|
+
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
107
|
+
const typesFilePath = path.resolve(projectRoot, options.typesFile);
|
|
108
|
+
const blockJsonPath = path.resolve(projectRoot, options.blockJsonFile);
|
|
109
|
+
const manifestPath = path.resolve(
|
|
110
|
+
projectRoot,
|
|
111
|
+
options.manifestFile ?? path.join(path.dirname(options.blockJsonFile), "typia.manifest.json"),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const ctx = createAnalysisContext(projectRoot, typesFilePath);
|
|
115
|
+
const sourceFile = ctx.program.getSourceFile(typesFilePath);
|
|
116
|
+
if (sourceFile === undefined) {
|
|
117
|
+
throw new Error(`Unable to load types file: ${typesFilePath}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const declaration = findNamedDeclaration(sourceFile, options.sourceTypeName);
|
|
121
|
+
if (declaration === undefined) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Unable to find source type "${options.sourceTypeName}" in ${path.relative(projectRoot, typesFilePath)}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const rootNode = parseNamedDeclaration(declaration, ctx, options.sourceTypeName, true);
|
|
128
|
+
if (rootNode.kind !== "object" || rootNode.properties === undefined) {
|
|
129
|
+
throw new Error(`Source type "${options.sourceTypeName}" must resolve to an object shape`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const blockJson = JSON.parse(fs.readFileSync(blockJsonPath, "utf8")) as Record<string, unknown>;
|
|
133
|
+
const lossyProjectionWarnings: string[] = [];
|
|
134
|
+
|
|
135
|
+
blockJson.attributes = Object.fromEntries(
|
|
136
|
+
Object.entries(rootNode.properties).map(([key, node]) => [
|
|
137
|
+
key,
|
|
138
|
+
createBlockJsonAttribute(node, lossyProjectionWarnings),
|
|
139
|
+
]),
|
|
140
|
+
);
|
|
141
|
+
blockJson.example = {
|
|
142
|
+
attributes: Object.fromEntries(
|
|
143
|
+
Object.entries(rootNode.properties).map(([key, node]) => [key, createExampleValue(node, key)]),
|
|
144
|
+
),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const manifest: ManifestDocument = {
|
|
148
|
+
attributes: Object.fromEntries(
|
|
149
|
+
Object.entries(rootNode.properties).map(([key, node]) => [key, createManifestAttribute(node)]),
|
|
150
|
+
),
|
|
151
|
+
manifestVersion: 1,
|
|
152
|
+
sourceType: options.sourceTypeName,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
fs.writeFileSync(blockJsonPath, JSON.stringify(blockJson, null, "\t"));
|
|
156
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
157
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, "\t"));
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
attributeNames: Object.keys(rootNode.properties),
|
|
161
|
+
blockJsonPath,
|
|
162
|
+
lossyProjectionWarnings: [...new Set(lossyProjectionWarnings)].sort(),
|
|
163
|
+
manifestPath,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function createAnalysisContext(projectRoot: string, typesFilePath: string): AnalysisContext {
|
|
168
|
+
const configPath = ts.findConfigFile(projectRoot, ts.sys.fileExists, "tsconfig.json");
|
|
169
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
170
|
+
allowJs: false,
|
|
171
|
+
esModuleInterop: true,
|
|
172
|
+
module: ts.ModuleKind.NodeNext,
|
|
173
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
174
|
+
resolveJsonModule: true,
|
|
175
|
+
skipLibCheck: true,
|
|
176
|
+
target: ts.ScriptTarget.ES2022,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
let rootNames = [typesFilePath];
|
|
180
|
+
|
|
181
|
+
if (configPath !== undefined) {
|
|
182
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
183
|
+
if (configFile.error) {
|
|
184
|
+
throw formatDiagnosticError(configFile.error);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
188
|
+
configFile.config,
|
|
189
|
+
ts.sys,
|
|
190
|
+
path.dirname(configPath),
|
|
191
|
+
compilerOptions,
|
|
192
|
+
configPath,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (parsed.errors.length > 0) {
|
|
196
|
+
throw formatDiagnosticError(parsed.errors[0]);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
rootNames = parsed.fileNames.includes(typesFilePath)
|
|
200
|
+
? parsed.fileNames
|
|
201
|
+
: [...parsed.fileNames, typesFilePath];
|
|
202
|
+
Object.assign(compilerOptions, parsed.options);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const program = ts.createProgram({
|
|
206
|
+
options: compilerOptions,
|
|
207
|
+
rootNames,
|
|
208
|
+
});
|
|
209
|
+
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
210
|
+
const blockingDiagnostic = diagnostics.find(
|
|
211
|
+
(diagnostic) =>
|
|
212
|
+
diagnostic.category === ts.DiagnosticCategory.Error &&
|
|
213
|
+
diagnostic.file?.fileName === typesFilePath,
|
|
214
|
+
);
|
|
215
|
+
if (blockingDiagnostic) {
|
|
216
|
+
throw formatDiagnosticError(blockingDiagnostic);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
checker: program.getTypeChecker(),
|
|
221
|
+
projectRoot,
|
|
222
|
+
program,
|
|
223
|
+
recursionGuard: new Set<string>(),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function findNamedDeclaration(
|
|
228
|
+
sourceFile: ts.SourceFile,
|
|
229
|
+
name: string,
|
|
230
|
+
): ts.InterfaceDeclaration | ts.TypeAliasDeclaration | undefined {
|
|
231
|
+
for (const statement of sourceFile.statements) {
|
|
232
|
+
if ((ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) && statement.name.text === name) {
|
|
233
|
+
return statement;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function parseNamedDeclaration(
|
|
240
|
+
declaration: ts.InterfaceDeclaration | ts.TypeAliasDeclaration,
|
|
241
|
+
ctx: AnalysisContext,
|
|
242
|
+
pathLabel: string,
|
|
243
|
+
required: boolean,
|
|
244
|
+
): AttributeNode {
|
|
245
|
+
const recursionKey = `${declaration.getSourceFile().fileName}:${declaration.name.text}`;
|
|
246
|
+
if (ctx.recursionGuard.has(recursionKey)) {
|
|
247
|
+
throw new Error(`Recursive types are not supported: ${pathLabel}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
ctx.recursionGuard.add(recursionKey);
|
|
251
|
+
try {
|
|
252
|
+
if (ts.isInterfaceDeclaration(declaration)) {
|
|
253
|
+
return parseInterfaceDeclaration(declaration, ctx, pathLabel, required);
|
|
254
|
+
}
|
|
255
|
+
return withRequired(parseTypeNode(declaration.type, ctx, pathLabel), required);
|
|
256
|
+
} finally {
|
|
257
|
+
ctx.recursionGuard.delete(recursionKey);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function parseInterfaceDeclaration(
|
|
262
|
+
declaration: ts.InterfaceDeclaration,
|
|
263
|
+
ctx: AnalysisContext,
|
|
264
|
+
pathLabel: string,
|
|
265
|
+
required: boolean,
|
|
266
|
+
): AttributeNode {
|
|
267
|
+
const properties: Record<string, AttributeNode> = {};
|
|
268
|
+
|
|
269
|
+
for (const heritageClause of declaration.heritageClauses ?? []) {
|
|
270
|
+
if (heritageClause.token !== ts.SyntaxKind.ExtendsKeyword) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const baseType of heritageClause.types) {
|
|
275
|
+
const baseNode = parseTypeReference(baseType, ctx, `${pathLabel}<extends>`);
|
|
276
|
+
if (baseNode.kind !== "object" || baseNode.properties === undefined) {
|
|
277
|
+
throw new Error(`Only object-like interface extensions are supported: ${pathLabel}`);
|
|
278
|
+
}
|
|
279
|
+
Object.assign(properties, cloneProperties(baseNode.properties));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
for (const member of declaration.members) {
|
|
284
|
+
if (!ts.isPropertySignature(member) || member.type === undefined) {
|
|
285
|
+
throw new Error(`Unsupported member in ${pathLabel}; only typed properties are supported`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const propertyName = getPropertyName(member.name);
|
|
289
|
+
properties[propertyName] = withRequired(
|
|
290
|
+
parseTypeNode(member.type, ctx, `${pathLabel}.${propertyName}`),
|
|
291
|
+
member.questionToken === undefined,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
constraints: DEFAULT_CONSTRAINTS(),
|
|
297
|
+
enumValues: null,
|
|
298
|
+
kind: "object",
|
|
299
|
+
path: pathLabel,
|
|
300
|
+
properties,
|
|
301
|
+
required,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function parseTypeNode(node: ts.TypeNode, ctx: AnalysisContext, pathLabel: string): AttributeNode {
|
|
306
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
307
|
+
return parseTypeNode(node.type, ctx, pathLabel);
|
|
308
|
+
}
|
|
309
|
+
if (ts.isIntersectionTypeNode(node)) {
|
|
310
|
+
return parseIntersectionType(node, ctx, pathLabel);
|
|
311
|
+
}
|
|
312
|
+
if (ts.isUnionTypeNode(node)) {
|
|
313
|
+
return parseUnionType(node, ctx, pathLabel);
|
|
314
|
+
}
|
|
315
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
316
|
+
return parseTypeLiteral(node, ctx, pathLabel);
|
|
317
|
+
}
|
|
318
|
+
if (ts.isArrayTypeNode(node)) {
|
|
319
|
+
return {
|
|
320
|
+
constraints: DEFAULT_CONSTRAINTS(),
|
|
321
|
+
enumValues: null,
|
|
322
|
+
items: withRequired(parseTypeNode(node.elementType, ctx, `${pathLabel}[]`), true),
|
|
323
|
+
kind: "array",
|
|
324
|
+
path: pathLabel,
|
|
325
|
+
required: true,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (ts.isLiteralTypeNode(node)) {
|
|
329
|
+
return parseLiteralType(node, pathLabel);
|
|
330
|
+
}
|
|
331
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
332
|
+
return parseTypeReference(node, ctx, pathLabel);
|
|
333
|
+
}
|
|
334
|
+
if (node.kind === ts.SyntaxKind.StringKeyword) {
|
|
335
|
+
return baseNode("string", pathLabel);
|
|
336
|
+
}
|
|
337
|
+
if (node.kind === ts.SyntaxKind.NumberKeyword || node.kind === ts.SyntaxKind.BigIntKeyword) {
|
|
338
|
+
return baseNode("number", pathLabel);
|
|
339
|
+
}
|
|
340
|
+
if (node.kind === ts.SyntaxKind.BooleanKeyword) {
|
|
341
|
+
return baseNode("boolean", pathLabel);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
throw new Error(`Unsupported type node at ${pathLabel}: ${node.getText()}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function parseIntersectionType(
|
|
348
|
+
node: ts.IntersectionTypeNode,
|
|
349
|
+
ctx: AnalysisContext,
|
|
350
|
+
pathLabel: string,
|
|
351
|
+
): AttributeNode {
|
|
352
|
+
const tagNodes: ts.TypeReferenceNode[] = [];
|
|
353
|
+
const valueNodes: ts.TypeNode[] = [];
|
|
354
|
+
|
|
355
|
+
for (const typeNode of node.types) {
|
|
356
|
+
if (ts.isTypeReferenceNode(typeNode) && getSupportedTagName(typeNode) !== null) {
|
|
357
|
+
tagNodes.push(typeNode);
|
|
358
|
+
} else {
|
|
359
|
+
valueNodes.push(typeNode);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (valueNodes.length === 0) {
|
|
364
|
+
throw new Error(`Intersection at ${pathLabel} does not contain a value type`);
|
|
365
|
+
}
|
|
366
|
+
if (valueNodes.length > 1) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Unsupported intersection at ${pathLabel}; only a single value type plus typia tags is supported`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const parsed = parseTypeNode(valueNodes[0], ctx, pathLabel);
|
|
373
|
+
for (const tagNode of tagNodes) {
|
|
374
|
+
applyTag(parsed, tagNode, pathLabel);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return parsed;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function parseUnionType(node: ts.UnionTypeNode, ctx: AnalysisContext, pathLabel: string): AttributeNode {
|
|
381
|
+
const literalValues = node.types
|
|
382
|
+
.map((typeNode) => extractLiteralValue(typeNode))
|
|
383
|
+
.filter((value): value is string | number | boolean => value !== undefined);
|
|
384
|
+
|
|
385
|
+
if (literalValues.length === node.types.length && literalValues.length > 0) {
|
|
386
|
+
const uniqueKinds = new Set(literalValues.map((value) => typeof value));
|
|
387
|
+
if (uniqueKinds.size !== 1) {
|
|
388
|
+
throw new Error(`Mixed primitive enums are not supported at ${pathLabel}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const kind = [...uniqueKinds][0] as "string" | "number" | "boolean";
|
|
392
|
+
return {
|
|
393
|
+
constraints: DEFAULT_CONSTRAINTS(),
|
|
394
|
+
enumValues: literalValues,
|
|
395
|
+
kind,
|
|
396
|
+
path: pathLabel,
|
|
397
|
+
required: true,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const withoutUndefined = node.types.filter(
|
|
402
|
+
(typeNode) => typeNode.kind !== ts.SyntaxKind.UndefinedKeyword && typeNode.kind !== ts.SyntaxKind.NullKeyword,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
if (withoutUndefined.length === 1) {
|
|
406
|
+
return parseTypeNode(withoutUndefined[0], ctx, pathLabel);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
throw new Error(`Unsupported union type at ${pathLabel}: ${node.getText()}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function parseTypeLiteral(node: ts.TypeLiteralNode, ctx: AnalysisContext, pathLabel: string): AttributeNode {
|
|
413
|
+
const properties: Record<string, AttributeNode> = {};
|
|
414
|
+
|
|
415
|
+
for (const member of node.members) {
|
|
416
|
+
if (!ts.isPropertySignature(member) || member.type === undefined) {
|
|
417
|
+
throw new Error(`Unsupported inline object member at ${pathLabel}`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const propertyName = getPropertyName(member.name);
|
|
421
|
+
properties[propertyName] = withRequired(
|
|
422
|
+
parseTypeNode(member.type, ctx, `${pathLabel}.${propertyName}`),
|
|
423
|
+
member.questionToken === undefined,
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
constraints: DEFAULT_CONSTRAINTS(),
|
|
429
|
+
enumValues: null,
|
|
430
|
+
kind: "object",
|
|
431
|
+
path: pathLabel,
|
|
432
|
+
properties,
|
|
433
|
+
required: true,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function parseLiteralType(node: ts.LiteralTypeNode, pathLabel: string): AttributeNode {
|
|
438
|
+
const literal = extractLiteralValue(node);
|
|
439
|
+
if (literal === undefined) {
|
|
440
|
+
throw new Error(`Unsupported literal type at ${pathLabel}: ${node.getText()}`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
constraints: DEFAULT_CONSTRAINTS(),
|
|
445
|
+
enumValues: [literal],
|
|
446
|
+
kind: typeof literal as "string" | "number" | "boolean",
|
|
447
|
+
path: pathLabel,
|
|
448
|
+
required: true,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function parseTypeReference(
|
|
453
|
+
node: ts.TypeReferenceNode | ts.ExpressionWithTypeArguments,
|
|
454
|
+
ctx: AnalysisContext,
|
|
455
|
+
pathLabel: string,
|
|
456
|
+
): AttributeNode {
|
|
457
|
+
const typeName = getReferenceName(node);
|
|
458
|
+
const typeArguments = node.typeArguments ?? [];
|
|
459
|
+
|
|
460
|
+
if (typeName === "Array" || typeName === "ReadonlyArray") {
|
|
461
|
+
const [itemNode] = typeArguments;
|
|
462
|
+
if (itemNode === undefined) {
|
|
463
|
+
throw new Error(`Array type is missing an item type at ${pathLabel}`);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
constraints: DEFAULT_CONSTRAINTS(),
|
|
468
|
+
enumValues: null,
|
|
469
|
+
items: withRequired(parseTypeNode(itemNode, ctx, `${pathLabel}[]`), true),
|
|
470
|
+
kind: "array",
|
|
471
|
+
path: pathLabel,
|
|
472
|
+
required: true,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (typeArguments.length > 0) {
|
|
476
|
+
throw new Error(`Generic type references are not supported at ${pathLabel}: ${typeName}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const symbol = resolveSymbol(node, ctx.checker);
|
|
480
|
+
if (symbol === undefined) {
|
|
481
|
+
throw new Error(`Unable to resolve type reference "${typeName}" at ${pathLabel}`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const declaration = symbol.declarations?.find(
|
|
485
|
+
(candidate) =>
|
|
486
|
+
ts.isInterfaceDeclaration(candidate) ||
|
|
487
|
+
ts.isTypeAliasDeclaration(candidate) ||
|
|
488
|
+
ts.isEnumDeclaration(candidate) ||
|
|
489
|
+
ts.isClassDeclaration(candidate),
|
|
490
|
+
);
|
|
491
|
+
if (declaration === undefined) {
|
|
492
|
+
throw new Error(`Unsupported referenced type "${typeName}" at ${pathLabel}`);
|
|
493
|
+
}
|
|
494
|
+
if (!isProjectLocalDeclaration(declaration, ctx.projectRoot)) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
`External or non-serializable referenced type "${typeName}" is not supported at ${pathLabel}`,
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (ts.isClassDeclaration(declaration) || ts.isEnumDeclaration(declaration)) {
|
|
500
|
+
throw new Error(`Class and enum references are not supported at ${pathLabel}`);
|
|
501
|
+
}
|
|
502
|
+
if ((declaration.typeParameters?.length ?? 0) > 0) {
|
|
503
|
+
throw new Error(`Generic type declarations are not supported at ${pathLabel}: ${typeName}`);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return parseNamedDeclaration(declaration, ctx, pathLabel, true);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function applyTag(node: AttributeNode, tagNode: ts.TypeReferenceNode, pathLabel: string): void {
|
|
510
|
+
const tagName = getSupportedTagName(tagNode);
|
|
511
|
+
if (tagName === null) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const [arg] = tagNode.typeArguments ?? [];
|
|
516
|
+
if (arg === undefined) {
|
|
517
|
+
throw new Error(`Tag "${tagName}" is missing its generic argument at ${pathLabel}`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
switch (tagName) {
|
|
521
|
+
case "Default": {
|
|
522
|
+
const value = parseDefaultValue(arg, pathLabel);
|
|
523
|
+
if (value === undefined) {
|
|
524
|
+
throw new Error(`Unsupported Default value at ${pathLabel}: ${arg.getText()}`);
|
|
525
|
+
}
|
|
526
|
+
node.defaultValue = value;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
case "Format":
|
|
530
|
+
node.constraints.format = parseStringLikeArgument(arg, tagName, pathLabel);
|
|
531
|
+
return;
|
|
532
|
+
case "Pattern":
|
|
533
|
+
node.constraints.pattern = parseStringLikeArgument(arg, tagName, pathLabel);
|
|
534
|
+
return;
|
|
535
|
+
case "Type":
|
|
536
|
+
node.constraints.typeTag = parseStringLikeArgument(arg, tagName, pathLabel);
|
|
537
|
+
return;
|
|
538
|
+
case "MinLength":
|
|
539
|
+
node.constraints.minLength = parseNumericArgument(arg, tagName, pathLabel);
|
|
540
|
+
return;
|
|
541
|
+
case "MaxLength":
|
|
542
|
+
node.constraints.maxLength = parseNumericArgument(arg, tagName, pathLabel);
|
|
543
|
+
return;
|
|
544
|
+
case "Minimum":
|
|
545
|
+
node.constraints.minimum = parseNumericArgument(arg, tagName, pathLabel);
|
|
546
|
+
return;
|
|
547
|
+
case "Maximum":
|
|
548
|
+
node.constraints.maximum = parseNumericArgument(arg, tagName, pathLabel);
|
|
549
|
+
return;
|
|
550
|
+
default:
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function parseDefaultValue(node: ts.TypeNode, pathLabel: string): JsonValue | undefined {
|
|
556
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
557
|
+
return parseDefaultValue(node.type, pathLabel);
|
|
558
|
+
}
|
|
559
|
+
if (ts.isLiteralTypeNode(node)) {
|
|
560
|
+
const literal = extractLiteralValue(node);
|
|
561
|
+
return literal === undefined ? undefined : literal;
|
|
562
|
+
}
|
|
563
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
564
|
+
const objectValue: Record<string, JsonValue> = {};
|
|
565
|
+
for (const member of node.members) {
|
|
566
|
+
if (!ts.isPropertySignature(member) || member.type === undefined) {
|
|
567
|
+
throw new Error(`Unsupported object Default value at ${pathLabel}`);
|
|
568
|
+
}
|
|
569
|
+
const propertyName = getPropertyName(member.name);
|
|
570
|
+
const value = parseDefaultValue(member.type, `${pathLabel}.${propertyName}`);
|
|
571
|
+
if (value === undefined) {
|
|
572
|
+
throw new Error(`Unsupported object Default value at ${pathLabel}.${propertyName}`);
|
|
573
|
+
}
|
|
574
|
+
objectValue[propertyName] = value;
|
|
575
|
+
}
|
|
576
|
+
return objectValue;
|
|
577
|
+
}
|
|
578
|
+
if (ts.isTupleTypeNode(node)) {
|
|
579
|
+
return node.elements.map((element, index) => {
|
|
580
|
+
const value = parseDefaultValue(element, `${pathLabel}[${index}]`);
|
|
581
|
+
if (value === undefined) {
|
|
582
|
+
throw new Error(`Unsupported array Default value at ${pathLabel}[${index}]`);
|
|
583
|
+
}
|
|
584
|
+
return value;
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function parseNumericArgument(node: ts.TypeNode, tagName: string, pathLabel: string): number {
|
|
594
|
+
const value = extractLiteralValue(node);
|
|
595
|
+
if (typeof value !== "number") {
|
|
596
|
+
throw new Error(`Tag "${tagName}" expects a numeric literal at ${pathLabel}`);
|
|
597
|
+
}
|
|
598
|
+
return value;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function parseStringLikeArgument(node: ts.TypeNode, tagName: string, pathLabel: string): string {
|
|
602
|
+
const value = extractLiteralValue(node);
|
|
603
|
+
if (typeof value !== "string") {
|
|
604
|
+
throw new Error(`Tag "${tagName}" expects a string literal at ${pathLabel}`);
|
|
605
|
+
}
|
|
606
|
+
return value;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function extractLiteralValue(node: ts.TypeNode): string | number | boolean | undefined {
|
|
610
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
611
|
+
return extractLiteralValue(node.type);
|
|
612
|
+
}
|
|
613
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
if (!ts.isLiteralTypeNode(node)) {
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (ts.isStringLiteral(node.literal)) {
|
|
624
|
+
return node.literal.text;
|
|
625
|
+
}
|
|
626
|
+
if (ts.isNumericLiteral(node.literal)) {
|
|
627
|
+
return Number(node.literal.text);
|
|
628
|
+
}
|
|
629
|
+
if (node.literal.kind === ts.SyntaxKind.TrueKeyword) {
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
if (node.literal.kind === ts.SyntaxKind.FalseKeyword) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
return undefined;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function createBlockJsonAttribute(
|
|
639
|
+
node: AttributeNode,
|
|
640
|
+
warnings: string[],
|
|
641
|
+
): BlockJsonAttribute {
|
|
642
|
+
const attribute: BlockJsonAttribute = {
|
|
643
|
+
type: node.kind,
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
if (node.defaultValue !== undefined) {
|
|
647
|
+
attribute.default = cloneJson(node.defaultValue);
|
|
648
|
+
}
|
|
649
|
+
if (node.enumValues !== null && node.enumValues.length > 0) {
|
|
650
|
+
attribute.enum = [...node.enumValues];
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const reasons: string[] = [];
|
|
654
|
+
if (node.constraints.format !== null) reasons.push("format");
|
|
655
|
+
if (node.constraints.maxLength !== null) reasons.push("maxLength");
|
|
656
|
+
if (node.constraints.maximum !== null) reasons.push("maximum");
|
|
657
|
+
if (node.constraints.minLength !== null) reasons.push("minLength");
|
|
658
|
+
if (node.constraints.minimum !== null) reasons.push("minimum");
|
|
659
|
+
if (node.constraints.pattern !== null) reasons.push("pattern");
|
|
660
|
+
if (node.constraints.typeTag !== null) reasons.push("typeTag");
|
|
661
|
+
if (node.kind === "array" && node.items !== undefined) reasons.push("items");
|
|
662
|
+
if (node.kind === "object" && node.properties !== undefined) reasons.push("properties");
|
|
663
|
+
|
|
664
|
+
if (reasons.length > 0) {
|
|
665
|
+
warnings.push(`${node.path}: ${reasons.join(", ")}`);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return attribute;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function createManifestAttribute(node: AttributeNode): ManifestAttribute {
|
|
672
|
+
return {
|
|
673
|
+
typia: {
|
|
674
|
+
constraints: { ...node.constraints },
|
|
675
|
+
default: node.defaultValue === undefined ? null : cloneJson(node.defaultValue),
|
|
676
|
+
},
|
|
677
|
+
ts: {
|
|
678
|
+
items: node.items ? createManifestAttribute(node.items) : null,
|
|
679
|
+
kind: node.kind,
|
|
680
|
+
properties: node.properties
|
|
681
|
+
? Object.fromEntries(
|
|
682
|
+
Object.entries(node.properties).map(([key, property]) => [key, createManifestAttribute(property)]),
|
|
683
|
+
)
|
|
684
|
+
: null,
|
|
685
|
+
required: node.required,
|
|
686
|
+
},
|
|
687
|
+
wp: {
|
|
688
|
+
default: node.defaultValue === undefined ? null : cloneJson(node.defaultValue),
|
|
689
|
+
enum: node.enumValues ? [...node.enumValues] : null,
|
|
690
|
+
type: node.kind,
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function createExampleValue(node: AttributeNode, key: string): JsonValue {
|
|
696
|
+
if (node.defaultValue !== undefined) {
|
|
697
|
+
return cloneJson(node.defaultValue);
|
|
698
|
+
}
|
|
699
|
+
if (node.enumValues !== null && node.enumValues.length > 0) {
|
|
700
|
+
return cloneJson(node.enumValues[0]);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
switch (node.kind) {
|
|
704
|
+
case "string":
|
|
705
|
+
if (node.constraints.format === "uuid") {
|
|
706
|
+
return "00000000-0000-4000-8000-000000000000";
|
|
707
|
+
}
|
|
708
|
+
return `Example ${key}`;
|
|
709
|
+
case "number":
|
|
710
|
+
return node.constraints.minimum ?? 42;
|
|
711
|
+
case "boolean":
|
|
712
|
+
return true;
|
|
713
|
+
case "array":
|
|
714
|
+
return [];
|
|
715
|
+
case "object":
|
|
716
|
+
return Object.fromEntries(
|
|
717
|
+
Object.entries(node.properties ?? {}).map(([propertyKey, propertyNode]) => [
|
|
718
|
+
propertyKey,
|
|
719
|
+
createExampleValue(propertyNode, propertyKey),
|
|
720
|
+
]),
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function baseNode(kind: AttributeKind, pathLabel: string): AttributeNode {
|
|
726
|
+
return {
|
|
727
|
+
constraints: DEFAULT_CONSTRAINTS(),
|
|
728
|
+
enumValues: null,
|
|
729
|
+
kind,
|
|
730
|
+
path: pathLabel,
|
|
731
|
+
required: true,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function withRequired(node: AttributeNode, required: boolean): AttributeNode {
|
|
736
|
+
return {
|
|
737
|
+
...node,
|
|
738
|
+
items: node.items ? withRequired(node.items, node.items.required) : undefined,
|
|
739
|
+
properties: node.properties ? cloneProperties(node.properties) : undefined,
|
|
740
|
+
required,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function cloneProperties(properties: Record<string, AttributeNode>): Record<string, AttributeNode> {
|
|
745
|
+
return Object.fromEntries(
|
|
746
|
+
Object.entries(properties).map(([key, node]) => [key, withRequired(node, node.required)]),
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function cloneJson<T extends JsonValue | Array<string | number | boolean>>(value: T): T {
|
|
751
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function getPropertyName(name: ts.PropertyName): string {
|
|
755
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
|
756
|
+
return name.text;
|
|
757
|
+
}
|
|
758
|
+
throw new Error(`Unsupported property name: ${name.getText()}`);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function getSupportedTagName(node: ts.TypeReferenceNode): string | null {
|
|
762
|
+
const typeName = getEntityNameText(node.typeName);
|
|
763
|
+
const [, tagName] = typeName.split(".");
|
|
764
|
+
if (!typeName.startsWith("tags.") || tagName === undefined || !SUPPORTED_TAGS.has(tagName)) {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
return tagName;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function getEntityNameText(name: ts.EntityName): string {
|
|
771
|
+
if (ts.isIdentifier(name)) {
|
|
772
|
+
return name.text;
|
|
773
|
+
}
|
|
774
|
+
return `${getEntityNameText(name.left)}.${name.right.text}`;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function resolveSymbol(
|
|
778
|
+
node: ts.TypeReferenceNode | ts.ExpressionWithTypeArguments,
|
|
779
|
+
checker: ts.TypeChecker,
|
|
780
|
+
): ts.Symbol | undefined {
|
|
781
|
+
const symbol = checker.getSymbolAtLocation(
|
|
782
|
+
ts.isTypeReferenceNode(node) ? node.typeName : node.expression,
|
|
783
|
+
);
|
|
784
|
+
if (symbol === undefined) {
|
|
785
|
+
return undefined;
|
|
786
|
+
}
|
|
787
|
+
return symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function getReferenceName(node: ts.TypeReferenceNode | ts.ExpressionWithTypeArguments): string {
|
|
791
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
792
|
+
return getEntityNameText(node.typeName);
|
|
793
|
+
}
|
|
794
|
+
return node.expression.getText();
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function isProjectLocalDeclaration(declaration: ts.Declaration, projectRoot: string): boolean {
|
|
798
|
+
const fileName = declaration.getSourceFile().fileName;
|
|
799
|
+
return !fileName.includes("node_modules") && !path.relative(projectRoot, fileName).startsWith("..");
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function formatDiagnosticError(diagnostic: ts.Diagnostic): Error {
|
|
803
|
+
return new Error(
|
|
804
|
+
ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
|
|
805
|
+
);
|
|
806
|
+
}
|