@cocoar/scenar-tooling 0.1.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +11 -0
- package/package.json +15 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +9 -0
- package/src/index.js.map +1 -0
- package/src/lib/check-scenario-metadata-imports.d.ts +8 -0
- package/src/lib/check-scenario-metadata-imports.js +11 -0
- package/src/lib/check-scenario-metadata-imports.js.map +1 -0
- package/src/lib/config/config.fn.d.ts +5 -0
- package/src/lib/config/config.fn.js +45 -0
- package/src/lib/config/config.fn.js.map +1 -0
- package/src/lib/config/config.types.d.ts +17 -0
- package/src/lib/config/config.types.js +3 -0
- package/src/lib/config/config.types.js.map +1 -0
- package/src/lib/config/index.d.ts +2 -0
- package/src/lib/config/index.js +6 -0
- package/src/lib/config/index.js.map +1 -0
- package/src/lib/fs-walk.d.ts +3 -0
- package/src/lib/fs-walk.js +23 -0
- package/src/lib/fs-walk.js.map +1 -0
- package/src/lib/generate-scenario-registry/generate-scenario-registry.fn.d.ts +10 -0
- package/src/lib/generate-scenario-registry/generate-scenario-registry.fn.js +699 -0
- package/src/lib/generate-scenario-registry/generate-scenario-registry.fn.js.map +1 -0
- package/src/lib/generate-scenario-registry/generate-scenario-registry.types.d.ts +15 -0
- package/src/lib/generate-scenario-registry/generate-scenario-registry.types.js +3 -0
- package/src/lib/generate-scenario-registry/generate-scenario-registry.types.js.map +1 -0
- package/src/lib/generate-scenario-registry/index.d.ts +2 -0
- package/src/lib/generate-scenario-registry/index.js +6 -0
- package/src/lib/generate-scenario-registry/index.js.map +1 -0
- package/src/lib/logging/index.d.ts +2 -0
- package/src/lib/logging/index.js +6 -0
- package/src/lib/logging/index.js.map +1 -0
- package/src/lib/logging/logging.fn.d.ts +7 -0
- package/src/lib/logging/logging.fn.js +51 -0
- package/src/lib/logging/logging.fn.js.map +1 -0
- package/src/lib/logging/logging.types.d.ts +8 -0
- package/src/lib/logging/logging.types.js +3 -0
- package/src/lib/logging/logging.types.js.map +1 -0
- package/src/lib/path-utils.d.ts +2 -0
- package/src/lib/path-utils.js +20 -0
- package/src/lib/path-utils.js.map +1 -0
- package/src/lib/scenar-api/index.d.ts +2 -0
- package/src/lib/scenar-api/index.js +6 -0
- package/src/lib/scenar-api/index.js.map +1 -0
- package/src/lib/scenar-api/scenar-api.fn.d.ts +8 -0
- package/src/lib/scenar-api/scenar-api.fn.js +52 -0
- package/src/lib/scenar-api/scenar-api.fn.js.map +1 -0
- package/src/lib/scenar-api/scenar-api.types.d.ts +14 -0
- package/src/lib/scenar-api/scenar-api.types.js +5 -0
- package/src/lib/scenar-api/scenar-api.types.js.map +1 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateScenarioRegistry = generateScenarioRegistry;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const tinyglobby_1 = require("tinyglobby");
|
|
9
|
+
const ts_morph_1 = require("ts-morph");
|
|
10
|
+
const scenar_registry_1 = require("@cocoar/scenar-registry");
|
|
11
|
+
const node_1 = require("@cocoar/scenar-registry/node");
|
|
12
|
+
const path_utils_1 = require("../path-utils");
|
|
13
|
+
function generateScenarioRegistry(config, logger, options) {
|
|
14
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
const workspaceRoot = process.cwd();
|
|
16
|
+
const outputFilePath = (0, node_1.resolveGeneratedRegistryPath)({
|
|
17
|
+
workspaceRoot,
|
|
18
|
+
outputFilePath: options === null || options === void 0 ? void 0 : options.outputFilePath,
|
|
19
|
+
});
|
|
20
|
+
const ignoredDirNames = ['node_modules', 'dist', '.git'];
|
|
21
|
+
const scenarioFileAbsPaths = [];
|
|
22
|
+
const ignoredGlobs = ignoredDirNames.map((d) => `**/${d}/**`);
|
|
23
|
+
const matches = yield (0, tinyglobby_1.glob)(config.scenarios, {
|
|
24
|
+
cwd: workspaceRoot,
|
|
25
|
+
absolute: true,
|
|
26
|
+
onlyFiles: true,
|
|
27
|
+
dot: false,
|
|
28
|
+
ignore: ignoredGlobs,
|
|
29
|
+
});
|
|
30
|
+
for (const filePath of matches) {
|
|
31
|
+
if (!filePath.endsWith('.scenario.ts'))
|
|
32
|
+
continue;
|
|
33
|
+
scenarioFileAbsPaths.push((0, node_path_1.resolve)(filePath));
|
|
34
|
+
}
|
|
35
|
+
logger === null || logger === void 0 ? void 0 : logger.debug(`Found ${scenarioFileAbsPaths.length} *.scenario.ts files`);
|
|
36
|
+
const project = new ts_morph_1.Project({
|
|
37
|
+
tsConfigFilePath: (0, node_path_1.resolve)(workspaceRoot, '.scenar/tsconfig.scenar.json'),
|
|
38
|
+
skipAddingFilesFromTsConfig: true,
|
|
39
|
+
});
|
|
40
|
+
const issues = [];
|
|
41
|
+
const extracted = [];
|
|
42
|
+
const successfullyParsedScenarioFiles = new Set();
|
|
43
|
+
for (const absPath of scenarioFileAbsPaths) {
|
|
44
|
+
try {
|
|
45
|
+
const found = extractScenarioExports(project, absPath);
|
|
46
|
+
for (const s of found)
|
|
47
|
+
extracted.push(s);
|
|
48
|
+
successfullyParsedScenarioFiles.add(absPath);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
+
issues.push({ fileAbsPath: absPath, message });
|
|
53
|
+
logger === null || logger === void 0 ? void 0 : logger.warn(`Skipping scenario file: ${absPath}`);
|
|
54
|
+
logger === null || logger === void 0 ? void 0 : logger.debug(message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
extracted.sort((a, b) => a.id.localeCompare(b.id));
|
|
58
|
+
const seenIds = new Set();
|
|
59
|
+
const scenarios = [];
|
|
60
|
+
for (const s of extracted) {
|
|
61
|
+
if (seenIds.has(s.id)) {
|
|
62
|
+
issues.push({
|
|
63
|
+
fileAbsPath: s.fileAbsPath,
|
|
64
|
+
message: `Duplicate scenario id '${s.id}'. This file will be skipped.`,
|
|
65
|
+
});
|
|
66
|
+
logger === null || logger === void 0 ? void 0 : logger.warn(`Skipping duplicate scenario id '${s.id}' in ${s.fileAbsPath}`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
seenIds.add(s.id);
|
|
70
|
+
scenarios.push(s);
|
|
71
|
+
}
|
|
72
|
+
const scenariosWithRuntimeIndex = scenarios.map((s) => (Object.assign(Object.assign({}, s), { scenarioImportPath: (0, path_utils_1.toRelativeImport)(outputFilePath, s.fileAbsPath) })));
|
|
73
|
+
const parserBlocks = scenariosWithRuntimeIndex
|
|
74
|
+
.map((s, index) => {
|
|
75
|
+
const defaultInputEntries = s.defaultInputs
|
|
76
|
+
.map((p) => ` ${JSON.stringify(p.inputName)}: ${p.valueExpression},`)
|
|
77
|
+
.join('\n');
|
|
78
|
+
const requiredInputEntries = s.requiredInputs
|
|
79
|
+
.map((p) => ` ${JSON.stringify(p.inputName)},`)
|
|
80
|
+
.join('\n');
|
|
81
|
+
const entries = s.inputParsers
|
|
82
|
+
.map((p) => ` ${JSON.stringify(p.inputName)}: ${p.parserExpression},`)
|
|
83
|
+
.join('\n');
|
|
84
|
+
const codecDescriptorEntries = s.inputCodecDescriptors
|
|
85
|
+
.map((d) => ` ${JSON.stringify(d.inputName)}: ${d.descriptorExpression},`)
|
|
86
|
+
.join('\n');
|
|
87
|
+
const canAutoImport = !!s.componentFileAbsPath && !!s.componentExportName;
|
|
88
|
+
const componentImportPath = canAutoImport
|
|
89
|
+
? (0, path_utils_1.toRelativeImport)(outputFilePath, s.componentFileAbsPath)
|
|
90
|
+
: null;
|
|
91
|
+
const loadComponentExpression = canAutoImport
|
|
92
|
+
? `(async () => {\n const mod = await import('${componentImportPath}');\n return mod.${s.componentExportName};\n })`
|
|
93
|
+
: `(async () => {\n throw new Error('Scenario is missing component information. Provide component/loadComponent() in the scenario module, or defineScenario<TComponent>() with an importable component type.');\n })`;
|
|
94
|
+
return `const autoInputs_${index}: Record<string, unknown> = {\n${defaultInputEntries}\n};\n\nconst requiredInputs_${index}: readonly string[] = [\n${requiredInputEntries}\n];\n\nconst autoInputParsers_${index}: Record<string, ScenarioInputParser> = {\n${entries}\n};\n\nconst autoInputCodecDescriptors_${index}: Record<string, ScenarioInputCodecDescriptor> = {\n${codecDescriptorEntries}\n};\n\nconst loadComponent_${index} = ${loadComponentExpression};`;
|
|
95
|
+
})
|
|
96
|
+
.join('\n\n');
|
|
97
|
+
const indexEntries = scenariosWithRuntimeIndex
|
|
98
|
+
.map((s, index) => {
|
|
99
|
+
const idLiteral = JSON.stringify(s.id);
|
|
100
|
+
const exportNameLiteral = JSON.stringify(s.exportName);
|
|
101
|
+
return ` ${idLiteral}: {\n id: ${idLiteral},\n exportName: ${exportNameLiteral},\n loadScenarioModule: () => import('${s.scenarioImportPath}'),\n autoInputs: autoInputs_${index},\n requiredInputs: requiredInputs_${index},\n inputParsers: autoInputParsers_${index},\n inputCodecDescriptors: autoInputCodecDescriptors_${index},\n loadComponent: loadComponent_${index},\n },`;
|
|
102
|
+
})
|
|
103
|
+
.join('\n');
|
|
104
|
+
const content = `/* eslint-disable */\n/*\n * Generated by Scenar Backstage tools.\n * Do not edit by hand.\n */\n\nexport const SCENAR_REGISTRY_SCHEMA_VERSION = ${scenar_registry_1.SCENAR_REGISTRY_SCHEMA_VERSION};\n\nimport {\n type ScenarioInputCodecDescriptor,\n type ScenarioInputParser,\n scenarioParseBoolean,\n scenarioParseIsoDate,\n scenarioParseJsonArray,\n scenarioParseJsonObject,\n scenarioParseNumber,\n scenarioParseString,\n} from '@cocoar/scenar-core';\n\n${parserBlocks}\n\nexport const SCENAR_REGISTRY_INDEX = {\n${indexEntries}\n} as const;\n\nexport const SCENAR_REGISTRY_IDS = Object.keys(SCENAR_REGISTRY_INDEX).sort();\n`;
|
|
105
|
+
yield (0, promises_1.mkdir)((0, node_path_1.dirname)(outputFilePath), { recursive: true });
|
|
106
|
+
yield (0, promises_1.writeFile)(outputFilePath, content, 'utf-8');
|
|
107
|
+
const watchedFileAbsPaths = Array.from(new Set([
|
|
108
|
+
...successfullyParsedScenarioFiles,
|
|
109
|
+
...scenarios.flatMap((s) => [s.fileAbsPath, s.componentFileAbsPath].filter(Boolean)),
|
|
110
|
+
].map((p) => (0, node_path_1.resolve)(p)))).sort((a, b) => a.localeCompare(b));
|
|
111
|
+
return {
|
|
112
|
+
outputFilePath,
|
|
113
|
+
scenarioCount: scenarios.length,
|
|
114
|
+
skippedScenarioFileCount: scenarioFileAbsPaths.length - successfullyParsedScenarioFiles.size,
|
|
115
|
+
issues,
|
|
116
|
+
watchedFileAbsPaths,
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function extractScenarioExports(project, fileAbsPath) {
|
|
121
|
+
var _a;
|
|
122
|
+
const sourceFile = project.addSourceFileAtPath(fileAbsPath);
|
|
123
|
+
const results = [];
|
|
124
|
+
for (const statement of sourceFile.getVariableStatements()) {
|
|
125
|
+
if (!statement.isExported())
|
|
126
|
+
continue;
|
|
127
|
+
for (const decl of statement.getDeclarations()) {
|
|
128
|
+
const exportName = decl.getName();
|
|
129
|
+
const initializer = decl.getInitializer();
|
|
130
|
+
if (!initializer)
|
|
131
|
+
continue;
|
|
132
|
+
// Primary supported authoring form: export const X = defineScenario({ ... })
|
|
133
|
+
const isDefineScenarioCall = isDefineScenarioCallExpression(sourceFile, initializer);
|
|
134
|
+
if (!isDefineScenarioCall)
|
|
135
|
+
continue;
|
|
136
|
+
const objectLiteral = (_a = initializer
|
|
137
|
+
.asKindOrThrow(ts_morph_1.SyntaxKind.CallExpression)
|
|
138
|
+
.getArguments()[0]) === null || _a === void 0 ? void 0 : _a.asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
|
|
139
|
+
if (!objectLiteral) {
|
|
140
|
+
throw new Error(`Scenario initializer in ${fileAbsPath} must be defineScenario({ ... }).`);
|
|
141
|
+
}
|
|
142
|
+
const idProp = objectLiteral.getProperty('id');
|
|
143
|
+
if (!idProp || !idProp.isKind(ts_morph_1.SyntaxKind.PropertyAssignment)) {
|
|
144
|
+
throw new Error(`Scenario '${exportName}' in ${fileAbsPath} must define an 'id' property (string literal).`);
|
|
145
|
+
}
|
|
146
|
+
const idInitializer = idProp.getInitializer();
|
|
147
|
+
const id = (idInitializer === null || idInitializer === void 0 ? void 0 : idInitializer.isKind(ts_morph_1.SyntaxKind.StringLiteral))
|
|
148
|
+
? idInitializer.getLiteralValue()
|
|
149
|
+
: undefined;
|
|
150
|
+
if (!id) {
|
|
151
|
+
throw new Error(`Scenario id in ${fileAbsPath} must be a string literal (e.g. id: 'demo/hello').`);
|
|
152
|
+
}
|
|
153
|
+
const componentInfo = resolveScenarioComponentInfo(sourceFile, initializer, objectLiteral, fileAbsPath);
|
|
154
|
+
const defaultInputs = componentInfo.componentFileAbsPath
|
|
155
|
+
? extractComponentDefaultInputs(project, componentInfo.componentFileAbsPath)
|
|
156
|
+
: [];
|
|
157
|
+
const requiredInputs = componentInfo.componentFileAbsPath
|
|
158
|
+
? extractComponentRequiredInputs(project, componentInfo.componentFileAbsPath)
|
|
159
|
+
: [];
|
|
160
|
+
const inputParsers = componentInfo.componentFileAbsPath
|
|
161
|
+
? extractComponentInputParsers(project, componentInfo.componentFileAbsPath)
|
|
162
|
+
: [];
|
|
163
|
+
const inputCodecDescriptors = componentInfo.componentFileAbsPath
|
|
164
|
+
? extractComponentInputCodecDescriptors(project, componentInfo.componentFileAbsPath)
|
|
165
|
+
: [];
|
|
166
|
+
results.push({
|
|
167
|
+
fileAbsPath,
|
|
168
|
+
exportName,
|
|
169
|
+
id,
|
|
170
|
+
componentFileAbsPath: componentInfo.componentFileAbsPath,
|
|
171
|
+
componentExportName: componentInfo.componentExportName,
|
|
172
|
+
defaultInputs,
|
|
173
|
+
requiredInputs,
|
|
174
|
+
inputParsers,
|
|
175
|
+
inputCodecDescriptors,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (results.length === 0) {
|
|
180
|
+
throw new Error(`Expected at least one exported defineScenario(...) in ${fileAbsPath} (e.g. export const myScenario = defineScenario({ id: '...' })).`);
|
|
181
|
+
}
|
|
182
|
+
// Stable output (important when multiple scenarios exist in one file).
|
|
183
|
+
results.sort((a, b) => a.id.localeCompare(b.id));
|
|
184
|
+
return results;
|
|
185
|
+
}
|
|
186
|
+
function isDefineScenarioCallExpression(sourceFile, expr) {
|
|
187
|
+
var _a, _b;
|
|
188
|
+
if (!expr.isKind(ts_morph_1.SyntaxKind.CallExpression))
|
|
189
|
+
return false;
|
|
190
|
+
const call = expr.asKindOrThrow(ts_morph_1.SyntaxKind.CallExpression);
|
|
191
|
+
const callee = call.getExpression();
|
|
192
|
+
// Supports:
|
|
193
|
+
// - import { defineScenario } from '@cocoar/scenar-core'; defineScenario(...)
|
|
194
|
+
// - import { defineScenario as def } from '@cocoar/scenar-core'; def(...)
|
|
195
|
+
if (callee.isKind(ts_morph_1.SyntaxKind.Identifier)) {
|
|
196
|
+
const localName = callee.getText();
|
|
197
|
+
for (const imp of sourceFile.getImportDeclarations()) {
|
|
198
|
+
if (imp.getModuleSpecifierValue() !== '@cocoar/scenar-core')
|
|
199
|
+
continue;
|
|
200
|
+
for (const spec of imp.getNamedImports()) {
|
|
201
|
+
const importedName = spec.getName();
|
|
202
|
+
const alias = (_b = (_a = spec.getAliasNode()) === null || _a === void 0 ? void 0 : _a.getText()) !== null && _b !== void 0 ? _b : null;
|
|
203
|
+
if (importedName === 'defineScenario' &&
|
|
204
|
+
(alias ? alias === localName : localName === 'defineScenario')) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
// Supports:
|
|
212
|
+
// - import * as scenar from '@cocoar/scenar-core'; scenar.defineScenario(...)
|
|
213
|
+
if (callee.isKind(ts_morph_1.SyntaxKind.PropertyAccessExpression)) {
|
|
214
|
+
const pa = callee.asKindOrThrow(ts_morph_1.SyntaxKind.PropertyAccessExpression);
|
|
215
|
+
const lhs = pa.getExpression();
|
|
216
|
+
const rhs = pa.getName();
|
|
217
|
+
if (!lhs.isKind(ts_morph_1.SyntaxKind.Identifier))
|
|
218
|
+
return false;
|
|
219
|
+
if (rhs !== 'defineScenario')
|
|
220
|
+
return false;
|
|
221
|
+
const nsLocalName = lhs.getText();
|
|
222
|
+
for (const imp of sourceFile.getImportDeclarations()) {
|
|
223
|
+
if (imp.getModuleSpecifierValue() !== '@cocoar/scenar-core')
|
|
224
|
+
continue;
|
|
225
|
+
const ns = imp.getNamespaceImport();
|
|
226
|
+
if ((ns === null || ns === void 0 ? void 0 : ns.getText()) === nsLocalName)
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
function resolveScenarioComponentInfo(scenarioSourceFile, scenarioInitializer, scenarioObjectLiteral, scenarioFileAbsPath) {
|
|
234
|
+
var _a, _b, _c, _d, _e;
|
|
235
|
+
const componentProp = scenarioObjectLiteral.getProperty('component');
|
|
236
|
+
if (componentProp === null || componentProp === void 0 ? void 0 : componentProp.isKind(ts_morph_1.SyntaxKind.PropertyAssignment)) {
|
|
237
|
+
const initializer = componentProp.getInitializer();
|
|
238
|
+
if (!initializer)
|
|
239
|
+
return { componentFileAbsPath: null, componentExportName: null };
|
|
240
|
+
// Prefer symbol resolution if possible.
|
|
241
|
+
try {
|
|
242
|
+
const symbol = initializer.getType().getSymbol();
|
|
243
|
+
const decls = (_a = symbol === null || symbol === void 0 ? void 0 : symbol.getDeclarations()) !== null && _a !== void 0 ? _a : [];
|
|
244
|
+
for (const d of decls) {
|
|
245
|
+
const sf = d.getSourceFile();
|
|
246
|
+
const abs = sf.getFilePath();
|
|
247
|
+
if (!abs)
|
|
248
|
+
continue;
|
|
249
|
+
return {
|
|
250
|
+
componentFileAbsPath: abs,
|
|
251
|
+
componentExportName: initializer.getText(),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch (_f) {
|
|
256
|
+
// ignore
|
|
257
|
+
}
|
|
258
|
+
// Fallback: resolve from import declarations in the scenario file.
|
|
259
|
+
const name = initializer.getText();
|
|
260
|
+
for (const imp of scenarioSourceFile.getImportDeclarations()) {
|
|
261
|
+
const moduleSpec = imp.getModuleSpecifierValue();
|
|
262
|
+
if (!moduleSpec.startsWith('.'))
|
|
263
|
+
continue;
|
|
264
|
+
for (const spec of imp.getNamedImports()) {
|
|
265
|
+
const importedName = spec.getName();
|
|
266
|
+
const alias = (_c = (_b = spec.getAliasNode()) === null || _b === void 0 ? void 0 : _b.getText()) !== null && _c !== void 0 ? _c : null;
|
|
267
|
+
if (alias ? alias !== name : importedName !== name)
|
|
268
|
+
continue;
|
|
269
|
+
const resolved = resolveModuleToTsFile(scenarioFileAbsPath, moduleSpec);
|
|
270
|
+
return {
|
|
271
|
+
componentFileAbsPath: resolved,
|
|
272
|
+
componentExportName: importedName,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return { componentFileAbsPath: null, componentExportName: name };
|
|
277
|
+
}
|
|
278
|
+
const loadComponentProp = scenarioObjectLiteral.getProperty('loadComponent');
|
|
279
|
+
if (loadComponentProp === null || loadComponentProp === void 0 ? void 0 : loadComponentProp.isKind(ts_morph_1.SyntaxKind.PropertyAssignment)) {
|
|
280
|
+
const initializer = loadComponentProp.getInitializer();
|
|
281
|
+
if (!initializer)
|
|
282
|
+
return { componentFileAbsPath: null, componentExportName: null };
|
|
283
|
+
const importCall = initializer
|
|
284
|
+
.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
|
|
285
|
+
.find((ce) => ce.getExpression().getKind() === ts_morph_1.SyntaxKind.ImportKeyword);
|
|
286
|
+
const moduleArg = importCall === null || importCall === void 0 ? void 0 : importCall.getArguments()[0];
|
|
287
|
+
if (!moduleArg || !moduleArg.isKind(ts_morph_1.SyntaxKind.StringLiteral)) {
|
|
288
|
+
return { componentFileAbsPath: null, componentExportName: null };
|
|
289
|
+
}
|
|
290
|
+
const moduleSpec = moduleArg.getLiteralValue();
|
|
291
|
+
return {
|
|
292
|
+
componentFileAbsPath: resolveModuleToTsFile(scenarioFileAbsPath, moduleSpec),
|
|
293
|
+
componentExportName: null,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
// Fallback: defineScenario<TComponent>({ id: '...' })
|
|
297
|
+
if (scenarioInitializer.isKind(ts_morph_1.SyntaxKind.CallExpression)) {
|
|
298
|
+
const call = scenarioInitializer.asKindOrThrow(ts_morph_1.SyntaxKind.CallExpression);
|
|
299
|
+
const typeArg = (_d = call.getTypeArguments()) === null || _d === void 0 ? void 0 : _d[0];
|
|
300
|
+
if (typeArg) {
|
|
301
|
+
const type = typeArg.getType();
|
|
302
|
+
const symbol = type.getSymbol();
|
|
303
|
+
const decls = (_e = symbol === null || symbol === void 0 ? void 0 : symbol.getDeclarations()) !== null && _e !== void 0 ? _e : [];
|
|
304
|
+
for (const d of decls) {
|
|
305
|
+
const sf = d.getSourceFile();
|
|
306
|
+
const abs = sf.getFilePath();
|
|
307
|
+
if (!abs)
|
|
308
|
+
continue;
|
|
309
|
+
return {
|
|
310
|
+
componentFileAbsPath: abs,
|
|
311
|
+
componentExportName: typeArg.getText(),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return { componentFileAbsPath: null, componentExportName: null };
|
|
317
|
+
}
|
|
318
|
+
function resolveModuleToTsFile(fromFileAbsPath, moduleSpecifier) {
|
|
319
|
+
if (!moduleSpecifier.startsWith('.')) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
const fromDir = (0, node_path_1.dirname)(fromFileAbsPath);
|
|
323
|
+
const base = (0, node_path_1.resolve)(fromDir, moduleSpecifier);
|
|
324
|
+
// Most Angular code imports without extension.
|
|
325
|
+
const candidates = [
|
|
326
|
+
base,
|
|
327
|
+
`${base}.ts`,
|
|
328
|
+
`${base}.tsx`,
|
|
329
|
+
(0, node_path_1.resolve)(base, 'index.ts'),
|
|
330
|
+
];
|
|
331
|
+
for (const c of candidates) {
|
|
332
|
+
try {
|
|
333
|
+
const stat = (0, node_fs_1.statSync)(c);
|
|
334
|
+
if (stat.isFile())
|
|
335
|
+
return c;
|
|
336
|
+
}
|
|
337
|
+
catch (_a) {
|
|
338
|
+
// ignore
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
function extractComponentInputParsers(project, componentFileAbsPath) {
|
|
344
|
+
const sourceFile = project.addSourceFileAtPath(componentFileAbsPath);
|
|
345
|
+
const parsers = [];
|
|
346
|
+
for (const cls of sourceFile.getClasses()) {
|
|
347
|
+
for (const prop of cls.getProperties()) {
|
|
348
|
+
if (!isSignalInputProperty(prop))
|
|
349
|
+
continue;
|
|
350
|
+
const inputName = prop.getName();
|
|
351
|
+
if (!inputName)
|
|
352
|
+
continue;
|
|
353
|
+
const parserExpression = inferParserExpression(prop);
|
|
354
|
+
parsers.push({ inputName, parserExpression });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Stable output.
|
|
358
|
+
parsers.sort((a, b) => a.inputName.localeCompare(b.inputName));
|
|
359
|
+
return parsers;
|
|
360
|
+
}
|
|
361
|
+
function extractComponentInputCodecDescriptors(project, componentFileAbsPath) {
|
|
362
|
+
const sourceFile = project.addSourceFileAtPath(componentFileAbsPath);
|
|
363
|
+
const descriptors = [];
|
|
364
|
+
for (const cls of sourceFile.getClasses()) {
|
|
365
|
+
for (const prop of cls.getProperties()) {
|
|
366
|
+
if (!isSignalInputProperty(prop))
|
|
367
|
+
continue;
|
|
368
|
+
const inputName = prop.getName();
|
|
369
|
+
if (!inputName)
|
|
370
|
+
continue;
|
|
371
|
+
const descriptorExpression = inferCodecDescriptorExpression(prop);
|
|
372
|
+
descriptors.push({ inputName, descriptorExpression });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Stable output.
|
|
376
|
+
descriptors.sort((a, b) => a.inputName.localeCompare(b.inputName));
|
|
377
|
+
return descriptors;
|
|
378
|
+
}
|
|
379
|
+
function extractComponentDefaultInputs(project, componentFileAbsPath) {
|
|
380
|
+
const sourceFile = project.addSourceFileAtPath(componentFileAbsPath);
|
|
381
|
+
const defaults = [];
|
|
382
|
+
for (const cls of sourceFile.getClasses()) {
|
|
383
|
+
for (const prop of cls.getProperties()) {
|
|
384
|
+
if (!isSignalInputProperty(prop))
|
|
385
|
+
continue;
|
|
386
|
+
const inputName = prop.getName();
|
|
387
|
+
if (!inputName)
|
|
388
|
+
continue;
|
|
389
|
+
// Signal input: name = input('x')
|
|
390
|
+
const signalDefault = extractDefaultFromSignalInputInitializer(prop.getInitializer());
|
|
391
|
+
if (signalDefault !== null) {
|
|
392
|
+
defaults.push({ inputName, valueExpression: signalDefault });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
defaults.sort((a, b) => a.inputName.localeCompare(b.inputName));
|
|
398
|
+
return defaults;
|
|
399
|
+
}
|
|
400
|
+
function extractComponentRequiredInputs(project, componentFileAbsPath) {
|
|
401
|
+
const sourceFile = project.addSourceFileAtPath(componentFileAbsPath);
|
|
402
|
+
const requiredInputs = [];
|
|
403
|
+
for (const cls of sourceFile.getClasses()) {
|
|
404
|
+
for (const prop of cls.getProperties()) {
|
|
405
|
+
if (!isRequiredSignalInputOrModelProperty(prop))
|
|
406
|
+
continue;
|
|
407
|
+
const inputName = prop.getName();
|
|
408
|
+
if (!inputName)
|
|
409
|
+
continue;
|
|
410
|
+
requiredInputs.push({ inputName });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
requiredInputs.sort((a, b) => a.inputName.localeCompare(b.inputName));
|
|
414
|
+
return requiredInputs;
|
|
415
|
+
}
|
|
416
|
+
function extractDefaultFromSignalInputInitializer(initializer) {
|
|
417
|
+
if (!(initializer === null || initializer === void 0 ? void 0 : initializer.isKind(ts_morph_1.SyntaxKind.CallExpression)))
|
|
418
|
+
return null;
|
|
419
|
+
const call = initializer.asKindOrThrow(ts_morph_1.SyntaxKind.CallExpression);
|
|
420
|
+
// input.required() / model.required() have no default.
|
|
421
|
+
const exprText = call.getExpression().getText();
|
|
422
|
+
if (exprText !== 'input' && exprText !== 'model')
|
|
423
|
+
return null;
|
|
424
|
+
const firstArg = call.getArguments()[0];
|
|
425
|
+
if (!firstArg)
|
|
426
|
+
return null;
|
|
427
|
+
const expr = unwrapTrivialExpression(firstArg);
|
|
428
|
+
if (!isSafeLiteralExpression(expr))
|
|
429
|
+
return null;
|
|
430
|
+
return expr.getText();
|
|
431
|
+
}
|
|
432
|
+
function unwrapTrivialExpression(expr) {
|
|
433
|
+
let current = expr;
|
|
434
|
+
while (true) {
|
|
435
|
+
const paren = current.asKind(ts_morph_1.SyntaxKind.ParenthesizedExpression);
|
|
436
|
+
if (paren) {
|
|
437
|
+
current = paren.getExpression();
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const asExpr = current.asKind(ts_morph_1.SyntaxKind.AsExpression);
|
|
441
|
+
if (asExpr) {
|
|
442
|
+
current = asExpr.getExpression();
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const typeAssert = current.asKind(ts_morph_1.SyntaxKind.TypeAssertionExpression);
|
|
446
|
+
if (typeAssert) {
|
|
447
|
+
current = typeAssert.getExpression();
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
return current;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function isSafeLiteralExpression(expr) {
|
|
454
|
+
const unwrapped = unwrapTrivialExpression(expr);
|
|
455
|
+
if (unwrapped.isKind(ts_morph_1.SyntaxKind.StringLiteral) ||
|
|
456
|
+
unwrapped.isKind(ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) ||
|
|
457
|
+
unwrapped.isKind(ts_morph_1.SyntaxKind.NumericLiteral) ||
|
|
458
|
+
unwrapped.isKind(ts_morph_1.SyntaxKind.TrueKeyword) ||
|
|
459
|
+
unwrapped.isKind(ts_morph_1.SyntaxKind.FalseKeyword) ||
|
|
460
|
+
unwrapped.isKind(ts_morph_1.SyntaxKind.NullKeyword)) {
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
if (unwrapped.isKind(ts_morph_1.SyntaxKind.PrefixUnaryExpression)) {
|
|
464
|
+
const p = unwrapped.asKindOrThrow(ts_morph_1.SyntaxKind.PrefixUnaryExpression);
|
|
465
|
+
const op = p.getOperatorToken();
|
|
466
|
+
const operand = p.getOperand();
|
|
467
|
+
return (op === ts_morph_1.SyntaxKind.MinusToken && operand.isKind(ts_morph_1.SyntaxKind.NumericLiteral));
|
|
468
|
+
}
|
|
469
|
+
if (unwrapped.isKind(ts_morph_1.SyntaxKind.ArrayLiteralExpression)) {
|
|
470
|
+
const arr = unwrapped.asKindOrThrow(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
|
|
471
|
+
for (const el of arr.getElements()) {
|
|
472
|
+
// Disallow spreads/holes since they are not safe literals.
|
|
473
|
+
if (el.isKind(ts_morph_1.SyntaxKind.SpreadElement) ||
|
|
474
|
+
el.isKind(ts_morph_1.SyntaxKind.OmittedExpression)) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
const elementExpression = el;
|
|
478
|
+
if (!isSafeLiteralExpression(elementExpression))
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
if (unwrapped.isKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression)) {
|
|
484
|
+
const obj = unwrapped.asKindOrThrow(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
|
|
485
|
+
for (const prop of obj.getProperties()) {
|
|
486
|
+
if (prop.isKind(ts_morph_1.SyntaxKind.PropertyAssignment)) {
|
|
487
|
+
const init = prop.getInitializer();
|
|
488
|
+
if (!init)
|
|
489
|
+
return false;
|
|
490
|
+
if (!isSafeLiteralExpression(init))
|
|
491
|
+
return false;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
function inferParserExpression(prop) {
|
|
501
|
+
var _a;
|
|
502
|
+
const initializer = prop.getInitializer();
|
|
503
|
+
// If using Angular's input<T>() / input.required<T>() / model<T>() / model.required<T>(),
|
|
504
|
+
// prefer the generic argument type.
|
|
505
|
+
if ((initializer === null || initializer === void 0 ? void 0 : initializer.isKind(ts_morph_1.SyntaxKind.CallExpression)) &&
|
|
506
|
+
isSignalInputOrModelCallExpressionText(initializer.getExpression().getText())) {
|
|
507
|
+
const typeArg = (_a = initializer.getTypeArguments()) === null || _a === void 0 ? void 0 : _a[0];
|
|
508
|
+
if (typeArg) {
|
|
509
|
+
return inferParserExpressionFromTypeText(typeArg.getText());
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
let type = prop.getType();
|
|
513
|
+
type = unwrapNullableUnionType(type);
|
|
514
|
+
if (type.isBoolean())
|
|
515
|
+
return '(raw: string) => scenarioParseBoolean(raw)';
|
|
516
|
+
if (type.isNumber())
|
|
517
|
+
return '(raw: string) => scenarioParseNumber(raw)';
|
|
518
|
+
if (type.isString())
|
|
519
|
+
return '(raw: string) => scenarioParseString(raw)';
|
|
520
|
+
// Common class type: Date
|
|
521
|
+
// Canonical wire format: ISO 8601 via Date.toISOString()
|
|
522
|
+
if (type.getText() === 'Date')
|
|
523
|
+
return '(raw: string) => scenarioParseIsoDate(raw)';
|
|
524
|
+
// Structured values are transported as JSON in a single query param.
|
|
525
|
+
if (isArrayLikeType(type)) {
|
|
526
|
+
const element = getArrayLikeElementType(type);
|
|
527
|
+
if (element && isNonDateClassType(element)) {
|
|
528
|
+
return '(raw: string) => scenarioParseString(raw)';
|
|
529
|
+
}
|
|
530
|
+
return '(raw: string) => scenarioParseJsonArray(raw)';
|
|
531
|
+
}
|
|
532
|
+
if (type.isObject() && type.getText() !== 'Date') {
|
|
533
|
+
// Avoid pretending we can rebuild class instances from query params.
|
|
534
|
+
if (isNonDateClassType(type)) {
|
|
535
|
+
return '(raw: string) => scenarioParseString(raw)';
|
|
536
|
+
}
|
|
537
|
+
return '(raw: string) => scenarioParseJsonObject(raw)';
|
|
538
|
+
}
|
|
539
|
+
// Minimal fallback: keep it as a string.
|
|
540
|
+
return '(raw: string) => scenarioParseString(raw)';
|
|
541
|
+
}
|
|
542
|
+
function inferCodecDescriptorExpression(prop) {
|
|
543
|
+
var _a;
|
|
544
|
+
const initializer = prop.getInitializer();
|
|
545
|
+
// If using Angular's input<T>() / input.required<T>() / model<T>() / model.required<T>(),
|
|
546
|
+
// prefer the generic argument type.
|
|
547
|
+
if ((initializer === null || initializer === void 0 ? void 0 : initializer.isKind(ts_morph_1.SyntaxKind.CallExpression)) &&
|
|
548
|
+
isSignalInputOrModelCallExpressionText(initializer.getExpression().getText())) {
|
|
549
|
+
const typeArg = (_a = initializer.getTypeArguments()) === null || _a === void 0 ? void 0 : _a[0];
|
|
550
|
+
if (typeArg) {
|
|
551
|
+
return inferCodecDescriptorExpressionFromTypeText(typeArg.getText());
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
let type = prop.getType();
|
|
555
|
+
type = unwrapNullableUnionType(type);
|
|
556
|
+
if (type.isBoolean())
|
|
557
|
+
return "{ kind: 'boolean' }";
|
|
558
|
+
if (type.isNumber())
|
|
559
|
+
return "{ kind: 'number' }";
|
|
560
|
+
if (type.isString())
|
|
561
|
+
return "{ kind: 'string' }";
|
|
562
|
+
// Common class type: Date
|
|
563
|
+
if (type.getText() === 'Date')
|
|
564
|
+
return "{ kind: 'isoDate' }";
|
|
565
|
+
if (isArrayLikeType(type)) {
|
|
566
|
+
const element = getArrayLikeElementType(type);
|
|
567
|
+
if (element && isNonDateClassType(element))
|
|
568
|
+
return "{ kind: 'string' }";
|
|
569
|
+
return "{ kind: 'jsonArray' }";
|
|
570
|
+
}
|
|
571
|
+
if (type.isObject() && type.getText() !== 'Date') {
|
|
572
|
+
if (isNonDateClassType(type))
|
|
573
|
+
return "{ kind: 'string' }";
|
|
574
|
+
return "{ kind: 'jsonObject' }";
|
|
575
|
+
}
|
|
576
|
+
return "{ kind: 'string' }";
|
|
577
|
+
}
|
|
578
|
+
function inferParserExpressionFromTypeText(typeText) {
|
|
579
|
+
const normalized = stripNullableTypeText(typeText);
|
|
580
|
+
if (normalized === 'boolean')
|
|
581
|
+
return '(raw: string) => scenarioParseBoolean(raw)';
|
|
582
|
+
if (normalized === 'number')
|
|
583
|
+
return '(raw: string) => scenarioParseNumber(raw)';
|
|
584
|
+
if (normalized === 'string')
|
|
585
|
+
return '(raw: string) => scenarioParseString(raw)';
|
|
586
|
+
if (normalized === 'Date')
|
|
587
|
+
return '(raw: string) => scenarioParseIsoDate(raw)';
|
|
588
|
+
if (normalized.endsWith('[]') ||
|
|
589
|
+
normalized.startsWith('Array<') ||
|
|
590
|
+
normalized.startsWith('ReadonlyArray<') ||
|
|
591
|
+
normalized.startsWith('readonly ')) {
|
|
592
|
+
return '(raw: string) => scenarioParseJsonArray(raw)';
|
|
593
|
+
}
|
|
594
|
+
if (normalized === 'object' ||
|
|
595
|
+
normalized.startsWith('Record<') ||
|
|
596
|
+
normalized.startsWith('{')) {
|
|
597
|
+
return '(raw: string) => scenarioParseJsonObject(raw)';
|
|
598
|
+
}
|
|
599
|
+
return '(raw: string) => scenarioParseString(raw)';
|
|
600
|
+
}
|
|
601
|
+
function inferCodecDescriptorExpressionFromTypeText(typeText) {
|
|
602
|
+
const normalized = stripNullableTypeText(typeText);
|
|
603
|
+
if (normalized === 'boolean')
|
|
604
|
+
return "{ kind: 'boolean' }";
|
|
605
|
+
if (normalized === 'number')
|
|
606
|
+
return "{ kind: 'number' }";
|
|
607
|
+
if (normalized === 'string')
|
|
608
|
+
return "{ kind: 'string' }";
|
|
609
|
+
if (normalized === 'Date')
|
|
610
|
+
return "{ kind: 'isoDate' }";
|
|
611
|
+
if (normalized.endsWith('[]') ||
|
|
612
|
+
normalized.startsWith('Array<') ||
|
|
613
|
+
normalized.startsWith('ReadonlyArray<') ||
|
|
614
|
+
normalized.startsWith('readonly ')) {
|
|
615
|
+
return "{ kind: 'jsonArray' }";
|
|
616
|
+
}
|
|
617
|
+
if (normalized === 'object' ||
|
|
618
|
+
normalized.startsWith('Record<') ||
|
|
619
|
+
normalized.startsWith('{')) {
|
|
620
|
+
return "{ kind: 'jsonObject' }";
|
|
621
|
+
}
|
|
622
|
+
return "{ kind: 'string' }";
|
|
623
|
+
}
|
|
624
|
+
function unwrapNullableUnionType(type) {
|
|
625
|
+
if (!type.isUnion())
|
|
626
|
+
return type;
|
|
627
|
+
const parts = type.getUnionTypes();
|
|
628
|
+
const nonNullable = parts.filter((t) => !t.isNull() && !t.isUndefined());
|
|
629
|
+
if (nonNullable.length === 1)
|
|
630
|
+
return nonNullable[0];
|
|
631
|
+
// Old enum inference became a plain string under the fixed codec set.
|
|
632
|
+
const stringLiterals = nonNullable
|
|
633
|
+
.map((t) => t.getLiteralValue())
|
|
634
|
+
.filter((v) => typeof v === 'string');
|
|
635
|
+
if (stringLiterals.length === nonNullable.length &&
|
|
636
|
+
stringLiterals.length > 0) {
|
|
637
|
+
return nonNullable[0];
|
|
638
|
+
}
|
|
639
|
+
return type;
|
|
640
|
+
}
|
|
641
|
+
function stripNullableTypeText(typeText) {
|
|
642
|
+
// Lightweight normalization for input<T>() / model<T>() generic arguments.
|
|
643
|
+
return typeText
|
|
644
|
+
.replace(/\s+/g, ' ')
|
|
645
|
+
.replace(/\s*\|\s*null\b/g, '')
|
|
646
|
+
.replace(/\s*\|\s*undefined\b/g, '')
|
|
647
|
+
.trim();
|
|
648
|
+
}
|
|
649
|
+
function isArrayLikeType(type) {
|
|
650
|
+
var _a, _b;
|
|
651
|
+
const anyType = type;
|
|
652
|
+
if (((_a = anyType.isArray) === null || _a === void 0 ? void 0 : _a.call(anyType)) === true)
|
|
653
|
+
return true;
|
|
654
|
+
if (((_b = anyType.isTuple) === null || _b === void 0 ? void 0 : _b.call(anyType)) === true)
|
|
655
|
+
return true;
|
|
656
|
+
const text = type.getText();
|
|
657
|
+
return (text.endsWith('[]') ||
|
|
658
|
+
text.startsWith('Array<') ||
|
|
659
|
+
text.startsWith('ReadonlyArray<') ||
|
|
660
|
+
text.startsWith('readonly '));
|
|
661
|
+
}
|
|
662
|
+
function getArrayLikeElementType(type) {
|
|
663
|
+
var _a;
|
|
664
|
+
const anyType = type;
|
|
665
|
+
const el = (_a = anyType.getArrayElementType) === null || _a === void 0 ? void 0 : _a.call(anyType);
|
|
666
|
+
return el !== null && el !== void 0 ? el : null;
|
|
667
|
+
}
|
|
668
|
+
function isNonDateClassType(type) {
|
|
669
|
+
if (type.getText() === 'Date')
|
|
670
|
+
return false;
|
|
671
|
+
const symbol = type.getSymbol();
|
|
672
|
+
if (!symbol)
|
|
673
|
+
return false;
|
|
674
|
+
return symbol
|
|
675
|
+
.getDeclarations()
|
|
676
|
+
.some((d) => d.getKind() === ts_morph_1.SyntaxKind.ClassDeclaration);
|
|
677
|
+
}
|
|
678
|
+
function isSignalInputOrModelCallExpressionText(exprText) {
|
|
679
|
+
return (exprText === 'input' ||
|
|
680
|
+
exprText === 'input.required' ||
|
|
681
|
+
exprText === 'model' ||
|
|
682
|
+
exprText === 'model.required');
|
|
683
|
+
}
|
|
684
|
+
function isSignalInputProperty(prop) {
|
|
685
|
+
const initializer = prop.getInitializer();
|
|
686
|
+
if (!(initializer === null || initializer === void 0 ? void 0 : initializer.isKind(ts_morph_1.SyntaxKind.CallExpression)))
|
|
687
|
+
return false;
|
|
688
|
+
const call = initializer.asKindOrThrow(ts_morph_1.SyntaxKind.CallExpression);
|
|
689
|
+
return isSignalInputOrModelCallExpressionText(call.getExpression().getText());
|
|
690
|
+
}
|
|
691
|
+
function isRequiredSignalInputOrModelProperty(prop) {
|
|
692
|
+
const initializer = prop.getInitializer();
|
|
693
|
+
if (!(initializer === null || initializer === void 0 ? void 0 : initializer.isKind(ts_morph_1.SyntaxKind.CallExpression)))
|
|
694
|
+
return false;
|
|
695
|
+
const call = initializer.asKindOrThrow(ts_morph_1.SyntaxKind.CallExpression);
|
|
696
|
+
const exprText = call.getExpression().getText();
|
|
697
|
+
return exprText === 'input.required' || exprText === 'model.required';
|
|
698
|
+
}
|
|
699
|
+
//# sourceMappingURL=generate-scenario-registry.fn.js.map
|