@saptools/service-flow 0.1.48 → 0.1.50
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/CHANGELOG.md +14 -0
- package/README.md +11 -1
- package/dist/{chunk-EGY2A4AT.js → chunk-52OUS3MO.js} +878 -333
- package/dist/chunk-52OUS3MO.js.map +1 -0
- package/dist/cli.js +330 -38
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +187 -3
- package/src/db/migrations.ts +4 -1
- package/src/db/repositories.ts +188 -11
- package/src/db/schema.ts +1 -1
- package/src/linker/cross-repo-linker.ts +172 -6
- package/src/linker/operation-decorator-normalizer.ts +3 -3
- package/src/output/table-output.ts +18 -8
- package/src/parsers/decorator-parser.ts +128 -22
- package/src/parsers/imported-wrapper-parser.ts +20 -46
- package/src/parsers/operation-path-analysis.ts +350 -0
- package/src/parsers/outbound-call-parser.ts +63 -53
- package/src/trace/evidence.ts +18 -3
- package/src/trace/selectors.ts +36 -0
- package/src/trace/trace-engine.ts +99 -25
- package/src/types.ts +12 -0
- package/dist/chunk-EGY2A4AT.js.map +0 -1
|
@@ -5,13 +5,14 @@ import { resolveOperation } from '../linker/service-resolver.js';
|
|
|
5
5
|
import type { ImplementationHint, TraceEdge, TraceResult, TraceStart } from '../types.js';
|
|
6
6
|
import { baseTraceEvidence, edgeTarget, runtimeResolution, runtimeVariableDiagnostic, type TraceGraphRow } from './evidence.js';
|
|
7
7
|
import { implementationHintDiagnostic, selectImplementation, type ImplementationSelection } from './implementation-hints.js';
|
|
8
|
-
|
|
8
|
+
import { ambiguousStartDiagnostic } from './selectors.js';
|
|
9
9
|
interface RepoRef {
|
|
10
10
|
id: number;
|
|
11
11
|
name: string;
|
|
12
12
|
}
|
|
13
13
|
interface StartScope {
|
|
14
14
|
repo?: RepoRef;
|
|
15
|
+
executionRepoId?: number;
|
|
15
16
|
sourceFiles?: Set<string>;
|
|
16
17
|
symbolIds?: Set<number>;
|
|
17
18
|
selectorMatched: boolean;
|
|
@@ -40,7 +41,7 @@ interface GraphRow extends Record<string, unknown> {
|
|
|
40
41
|
status?: string;
|
|
41
42
|
}
|
|
42
43
|
interface ContextBinding {
|
|
43
|
-
bindingId
|
|
44
|
+
bindingId?: number;
|
|
44
45
|
alias?: string;
|
|
45
46
|
aliasExpr?: string;
|
|
46
47
|
destinationExpr?: string;
|
|
@@ -62,6 +63,8 @@ interface ContextBinding {
|
|
|
62
63
|
calleeReceiver: string;
|
|
63
64
|
callerSite?: { sourceFile?: string; sourceLine?: number };
|
|
64
65
|
calleeSite?: { sourceFile?: string; sourceLine?: number };
|
|
66
|
+
resolutionStatus?: 'selected' | 'ambiguous';
|
|
67
|
+
bindingCandidates?: Array<Record<string, unknown>>;
|
|
65
68
|
}
|
|
66
69
|
interface ImplementationHintOptions {
|
|
67
70
|
implementationRepo?: string;
|
|
@@ -74,42 +77,55 @@ function normalizeOperation(value: string | undefined): string | undefined {
|
|
|
74
77
|
function positiveDepth(value: number): number {
|
|
75
78
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 25;
|
|
76
79
|
}
|
|
77
|
-
|
|
78
|
-
function operationStartScope(db: Db, repoId: number | undefined, start: TraceStart, hintOptions: ImplementationHintOptions): { files?: Set<string>; symbols?: Set<number>; operationId?: string; diagnostics?: Array<Record<string, unknown>> } | undefined {
|
|
80
|
+
function operationStartScope(db: Db, repoId: number | undefined, start: TraceStart, hintOptions: ImplementationHintOptions): { files?: Set<string>; symbols?: Set<number>; repoId?: number; operationId?: string; diagnostics?: Array<Record<string, unknown>> } | undefined {
|
|
79
81
|
const requested = normalizeOperation(start.operationPath ?? start.operation);
|
|
80
82
|
if (!requested) return undefined;
|
|
81
|
-
const rows = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,s.service_path servicePath,r.id repoId,r.name repoName
|
|
83
|
+
const rows = db.prepare(`SELECT o.id operationId,o.operation_name operationName,o.operation_path operationPath,o.source_file sourceFile,o.source_line sourceLine,s.service_path servicePath,r.id repoId,r.name repoName
|
|
82
84
|
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id
|
|
83
85
|
WHERE (? IS NULL OR r.id=?) AND (? IS NULL OR s.service_path=?) AND (o.operation_name=? OR o.operation_path=? OR o.operation_path=?)
|
|
84
86
|
ORDER BY r.name,s.service_path,o.operation_name,o.id`).all(repoId, repoId, start.servicePath, start.servicePath, requested, requested, requested.startsWith('/') ? requested : `/${requested}`) as Array<Record<string, unknown>>;
|
|
85
87
|
if (rows.length === 0) return undefined;
|
|
86
88
|
const repoCount = new Set(rows.map((row) => String(row.repoName))).size;
|
|
87
89
|
const serviceCount = new Set(rows.map((row) => `${String(row.repoName)}:${String(row.servicePath)}`)).size;
|
|
88
|
-
if (!repoId && repoCount > 1)
|
|
89
|
-
|
|
90
|
-
if (
|
|
90
|
+
if (!repoId && repoCount > 1)
|
|
91
|
+
return { diagnostics: [ambiguousStartDiagnostic(requested, rows, 'Operation trace start matched multiple repositories; add --repo to disambiguate')] };
|
|
92
|
+
if (!start.servicePath && serviceCount > 1)
|
|
93
|
+
return { diagnostics: [ambiguousStartDiagnostic(requested, rows, 'Operation trace start matched multiple services; add --service to disambiguate')] };
|
|
94
|
+
if (rows.length !== 1)
|
|
95
|
+
return { diagnostics: [ambiguousStartDiagnostic(requested, rows, 'Operation trace start matched multiple indexed operations')] };
|
|
91
96
|
const operationId = String(rows[0]?.operationId);
|
|
92
97
|
const impl = implementationScope(db, operationId);
|
|
93
|
-
if (impl.edge?.status === 'resolved' && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? new Set([impl.symbolId]) : undefined, operationId, diagnostics: [] };
|
|
98
|
+
if (impl.edge?.status === 'resolved' && impl.files.size > 0) return { files: impl.files, symbols: impl.symbolId ? new Set([impl.symbolId]) : undefined, repoId: impl.repoId, operationId, diagnostics: [] };
|
|
94
99
|
const hinted = implementationMethodIdFromHint(impl.edge, hintOptions);
|
|
95
100
|
if (hinted.methodId) {
|
|
96
101
|
const hintedScope = handlerScope(db, hinted.methodId);
|
|
97
|
-
if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? new Set([hintedScope.symbolId]) : undefined, operationId, diagnostics: [] };
|
|
102
|
+
if (hintedScope?.files.size) return { files: hintedScope.files, symbols: hintedScope.symbolId ? new Set([hintedScope.symbolId]) : undefined, repoId: hintedScope.repoId, operationId, diagnostics: [] };
|
|
98
103
|
}
|
|
99
104
|
if (impl.edge) {
|
|
100
105
|
const evidence = parseEvidence(impl.edge.evidence_json);
|
|
101
106
|
const hintDiagnostic = implementationHintDiagnostic(hinted, evidence.implementationHintSuggestions);
|
|
102
|
-
const diagnostics = [{ severity: 'warning', code: impl.edge.status === 'ambiguous' ? 'trace_start_ambiguous' : 'trace_start_implementation_unresolved', message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? 'unresolved')}`, resolutionStage: 'implementation', resolutionStatus: impl.edge.status === 'ambiguous' ? 'ambiguous_implementation' : 'rejected_implementation', implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, implementationAmbiguityReasons: evidence.ambiguityReasons, implementationHintSuggestions: evidence.implementationHintSuggestions, candidates: evidence.candidates }];
|
|
107
|
+
const diagnostics = [{ severity: 'warning', code: impl.edge.status === 'ambiguous' ? 'trace_start_ambiguous' : 'trace_start_implementation_unresolved', message: `Indexed operation matched but implementation edge is ${String(impl.edge.status ?? 'unresolved')}`, resolutionStage: 'implementation', resolutionStatus: impl.edge.status === 'ambiguous' ? 'ambiguous_implementation' : 'rejected_implementation', implementationEdgeId: impl.edge.id, implementationStatus: impl.edge.status, implementationAmbiguityReasons: evidence.ambiguityReasons, implementationRejectionReasons: implementationRejectionReasons(evidence), implementationHintSuggestions: evidence.implementationHintSuggestions, candidates: evidence.candidates }];
|
|
103
108
|
return { operationId, diagnostics: hintDiagnostic ? [hintDiagnostic, ...diagnostics] : diagnostics };
|
|
104
109
|
}
|
|
105
110
|
return { operationId, diagnostics: [{ severity: 'warning', code: 'trace_start_implementation_unresolved', message: 'Indexed operation matched but no implementation candidate exists', resolutionStage: 'implementation', resolutionStatus: 'operation_without_implementation' }] };
|
|
106
111
|
}
|
|
107
|
-
|
|
112
|
+
function implementationRejectionReasons(evidence: Record<string, unknown>): string[] {
|
|
113
|
+
const candidates = Array.isArray(evidence.candidates)
|
|
114
|
+
? evidence.candidates.filter((item): item is Record<string, unknown> =>
|
|
115
|
+
Boolean(item && typeof item === 'object' && !Array.isArray(item)))
|
|
116
|
+
: [];
|
|
117
|
+
const reasons = candidates.flatMap((candidate) =>
|
|
118
|
+
Array.isArray(candidate.rejectedReasons)
|
|
119
|
+
? candidate.rejectedReasons.filter((reason): reason is string =>
|
|
120
|
+
typeof reason === 'string')
|
|
121
|
+
: []);
|
|
122
|
+
return [...new Set(reasons)].sort();
|
|
123
|
+
}
|
|
108
124
|
function sourceFilesForStart(
|
|
109
125
|
db: Db,
|
|
110
126
|
repoId: number | undefined,
|
|
111
127
|
start: TraceStart,
|
|
112
|
-
): { files?: Set<string>; symbols?: Set<number
|
|
128
|
+
): { files?: Set<string>; symbols?: Set<number>; repoId?: number } | undefined {
|
|
113
129
|
const handler = start.handler;
|
|
114
130
|
const operation = normalizeOperation(start.operation ?? start.operationPath);
|
|
115
131
|
if (!handler && !operation) return undefined;
|
|
@@ -138,7 +154,7 @@ function sourceFilesForStart(
|
|
|
138
154
|
operation,
|
|
139
155
|
operation,
|
|
140
156
|
) as Array<{ sourceFile?: string; symbolId?: number }>;
|
|
141
|
-
if (rows.length > 0) return { files: new Set(rows.map((row) => row.sourceFile).filter(Boolean) as string[]), symbols: new Set(rows.map((row) => Number(row.symbolId)).filter(Boolean)) };
|
|
157
|
+
if (rows.length > 0) return { files: new Set(rows.map((row) => row.sourceFile).filter(Boolean) as string[]), symbols: new Set(rows.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
|
|
142
158
|
if (start.servicePath && operation) {
|
|
143
159
|
const implRows = db.prepare(`SELECT DISTINCT hc.source_file sourceFile,sym.id symbolId
|
|
144
160
|
FROM cds_services s JOIN cds_operations o ON o.service_id=s.id
|
|
@@ -147,7 +163,7 @@ function sourceFilesForStart(
|
|
|
147
163
|
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
148
164
|
LEFT JOIN symbols sym ON sym.repo_id=hc.repo_id AND sym.source_file=hc.source_file AND sym.name=hm.method_name
|
|
149
165
|
WHERE (? IS NULL OR s.repo_id=?) AND s.service_path=? AND (o.operation_path=? OR o.operation_name=?)`).all(repoId, repoId, start.servicePath, operation, operation) as Array<{ sourceFile?: string; symbolId?: number }>;
|
|
150
|
-
if (implRows.length > 0) return { files: new Set(implRows.map((row) => row.sourceFile).filter(Boolean) as string[]), symbols: new Set(implRows.map((row) => Number(row.symbolId)).filter(Boolean)) };
|
|
166
|
+
if (implRows.length > 0) return { files: new Set(implRows.map((row) => row.sourceFile).filter(Boolean) as string[]), symbols: new Set(implRows.map((row) => Number(row.symbolId)).filter(Boolean)), repoId };
|
|
151
167
|
}
|
|
152
168
|
return undefined;
|
|
153
169
|
}
|
|
@@ -171,6 +187,7 @@ function startScope(db: Db, start: TraceStart, hintOptions: ImplementationHintOp
|
|
|
171
187
|
return { repo, selectorMatched: false };
|
|
172
188
|
return {
|
|
173
189
|
repo,
|
|
190
|
+
executionRepoId: sourceScope?.repoId ?? repo?.id,
|
|
174
191
|
sourceFiles,
|
|
175
192
|
symbolIds: sourceScope?.symbols,
|
|
176
193
|
selectorMatched: !terminalOperationScope && (!hasSelector || sourceFiles !== undefined),
|
|
@@ -201,7 +218,6 @@ function handlerFilesForOperation(db: Db, operationId: string): Set<string> {
|
|
|
201
218
|
}>;
|
|
202
219
|
return new Set(rows.map((row) => row.sourceFile).filter(Boolean) as string[]);
|
|
203
220
|
}
|
|
204
|
-
|
|
205
221
|
function implementationEdge(db: Db, operationId: string): GraphRow | undefined {
|
|
206
222
|
return db.prepare("SELECT * FROM graph_edges WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND from_kind='operation' AND from_id=? ORDER BY CASE status WHEN 'resolved' THEN 0 WHEN 'ambiguous' THEN 1 ELSE 2 END,id LIMIT 1").get(operationId) as GraphRow | undefined;
|
|
207
223
|
}
|
|
@@ -220,7 +236,6 @@ function implementationMethodIdFromHint(edge: GraphRow | undefined, options: Imp
|
|
|
220
236
|
if (!edge || edge.status !== 'ambiguous') return { blocksAutomatic: false, evidence: { status: 'not_applicable' } };
|
|
221
237
|
return selectImplementation(parseEvidence(edge.evidence_json), options.implementationHints, options.implementationRepo);
|
|
222
238
|
}
|
|
223
|
-
|
|
224
239
|
function contextImplementationMethodId(edge: GraphRow | undefined, callerRepoId: number | undefined, remoteEvidence: Record<string, unknown>, hintOptions: ImplementationHintOptions): ImplementationSelection {
|
|
225
240
|
const hinted = implementationMethodIdFromHint(edge, hintOptions);
|
|
226
241
|
if (hinted.methodId) return hinted;
|
|
@@ -331,21 +346,65 @@ function knownBindingsForCalls(db: Db, calls: CallRow[]): Map<string, ContextBin
|
|
|
331
346
|
function knownBindingsForScope(db: Db, repoId: number | undefined, symbolIds: Set<number> | undefined, files: Set<string> | undefined): Map<string, ContextBinding> {
|
|
332
347
|
const map = new Map<string, ContextBinding>();
|
|
333
348
|
if (repoId === undefined) return map;
|
|
334
|
-
type BindingRow = Omit<ContextBinding, 'bindingId' | 'source' | 'calleeReceiver'> & { id?: number; variableName?: string };
|
|
335
|
-
const rows = db.prepare(`SELECT b.id,b.variable_name variableName,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.source_file sourceFile,b.source_line sourceLine,req.service_path requireServicePath,req.destination requireDestination
|
|
349
|
+
type BindingRow = Omit<ContextBinding, 'bindingId' | 'source' | 'calleeReceiver'> & { id?: number; symbolId?: number | null; variableName?: string };
|
|
350
|
+
const rows = db.prepare(`SELECT b.id,b.symbol_id symbolId,b.variable_name variableName,b.alias,b.alias_expr aliasExpr,b.destination_expr destinationExpr,b.service_path_expr servicePathExpr,b.source_file sourceFile,b.source_line sourceLine,req.service_path requireServicePath,req.destination requireDestination
|
|
336
351
|
FROM service_bindings b LEFT JOIN cds_requires req ON req.repo_id=b.repo_id AND req.alias=b.alias
|
|
337
352
|
WHERE b.repo_id=?`).all(repoId) as BindingRow[];
|
|
338
353
|
for (const row of rows) {
|
|
339
354
|
if (!row.variableName) continue;
|
|
340
355
|
if (files && !files.has(String(row.sourceFile))) continue;
|
|
341
|
-
if (symbolIds && symbolIds.size > 0
|
|
342
|
-
|
|
343
|
-
|
|
356
|
+
if (symbolIds && symbolIds.size > 0 && row.symbolId != null
|
|
357
|
+
&& !symbolIds.has(Number(row.symbolId))) continue;
|
|
358
|
+
const candidate = enrichBinding({
|
|
359
|
+
...row,
|
|
360
|
+
bindingId: Number(row.id),
|
|
361
|
+
source: 'local_service_binding',
|
|
362
|
+
calleeReceiver: row.variableName,
|
|
363
|
+
resolutionStatus: 'selected',
|
|
364
|
+
});
|
|
365
|
+
const existing = map.get(row.variableName);
|
|
366
|
+
if (!existing) {
|
|
367
|
+
map.set(row.variableName, candidate);
|
|
368
|
+
continue;
|
|
344
369
|
}
|
|
345
|
-
|
|
370
|
+
const candidates = uniqueBindingCandidates([
|
|
371
|
+
...(existing.bindingCandidates ?? [bindingCandidateEvidence(existing)]),
|
|
372
|
+
bindingCandidateEvidence(candidate),
|
|
373
|
+
]);
|
|
374
|
+
map.set(row.variableName, {
|
|
375
|
+
...candidate,
|
|
376
|
+
bindingId: undefined,
|
|
377
|
+
source: 'ambiguous_local_service_bindings',
|
|
378
|
+
resolutionStatus: 'ambiguous',
|
|
379
|
+
bindingCandidates: candidates,
|
|
380
|
+
});
|
|
346
381
|
}
|
|
347
382
|
return map;
|
|
348
383
|
}
|
|
384
|
+
|
|
385
|
+
function bindingCandidateEvidence(binding: ContextBinding): Record<string, unknown> {
|
|
386
|
+
return {
|
|
387
|
+
bindingId: binding.bindingId,
|
|
388
|
+
sourceFile: binding.sourceFile,
|
|
389
|
+
sourceLine: binding.sourceLine,
|
|
390
|
+
alias: binding.alias,
|
|
391
|
+
aliasExpr: binding.aliasExpr,
|
|
392
|
+
destinationExpr: binding.destinationExpr,
|
|
393
|
+
servicePathExpr: binding.servicePathExpr,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function uniqueBindingCandidates(
|
|
398
|
+
candidates: Array<Record<string, unknown>>,
|
|
399
|
+
): Array<Record<string, unknown>> {
|
|
400
|
+
const seen = new Set<string>();
|
|
401
|
+
return candidates.filter((candidate) => {
|
|
402
|
+
const key = JSON.stringify(candidate);
|
|
403
|
+
if (seen.has(key)) return false;
|
|
404
|
+
seen.add(key);
|
|
405
|
+
return true;
|
|
406
|
+
});
|
|
407
|
+
}
|
|
349
408
|
function contextForSymbolCall(db: Db, symbolCall: Record<string, unknown>, callerBindings: Map<string, ContextBinding>): Map<string, ContextBinding> {
|
|
350
409
|
const next = new Map<string, ContextBinding>();
|
|
351
410
|
if (callerBindings.size === 0) return next;
|
|
@@ -397,6 +456,21 @@ function contextForSymbolCall(db: Db, symbolCall: Record<string, unknown>, calle
|
|
|
397
456
|
}
|
|
398
457
|
function contextualRuntimeResolution(db: Db, call: CallRow, binding: ContextBinding | undefined, workspaceId: number | undefined, persistedRows: GraphRow[] = []): { row?: GraphRow; evidence?: Record<string, unknown>; unresolvedReason?: string } {
|
|
399
458
|
if (!binding || String(call.call_type) !== 'remote_action' || call.operation_path_expr === undefined || call.operation_path_expr === null) return {};
|
|
459
|
+
if (binding.resolutionStatus === 'ambiguous') {
|
|
460
|
+
return {
|
|
461
|
+
evidence: {
|
|
462
|
+
contextualServiceBindingAttempted: true,
|
|
463
|
+
contextualBinding: {
|
|
464
|
+
source: binding.source,
|
|
465
|
+
status: 'tied',
|
|
466
|
+
candidates: binding.bindingCandidates,
|
|
467
|
+
},
|
|
468
|
+
contextualResolutionStatus: 'ambiguous',
|
|
469
|
+
contextualCandidateCount: binding.bindingCandidates?.length ?? 0,
|
|
470
|
+
},
|
|
471
|
+
unresolvedReason: 'Ambiguous contextual service binding candidates',
|
|
472
|
+
};
|
|
473
|
+
}
|
|
400
474
|
const normalized = normalizeODataOperationInvocationPath(String(call.operation_path_expr));
|
|
401
475
|
const op = normalized?.normalizedOperationPath ?? (String(call.operation_path_expr).startsWith('/') ? String(call.operation_path_expr) : `/${String(call.operation_path_expr)}`);
|
|
402
476
|
const servicePath = binding.effectiveServicePath ?? binding.servicePathExpr ?? binding.requireServicePath;
|
|
@@ -445,7 +519,7 @@ export function trace(
|
|
|
445
519
|
const seenEdges = new Set<number>();
|
|
446
520
|
const queue: Array<{ repoId?: number; files?: Set<string>; symbolIds?: Set<number>; depth: number; context?: Map<string, ContextBinding> }> =
|
|
447
521
|
scope.selectorMatched
|
|
448
|
-
? [{ repoId: scope.
|
|
522
|
+
? [{ repoId: scope.executionRepoId, files: scope.sourceFiles, symbolIds: scope.symbolIds, depth: 1, context: new Map() }]
|
|
449
523
|
: [];
|
|
450
524
|
if (scope.startOperationId && scope.selectorMatched) {
|
|
451
525
|
const op = operationNode(db, scope.startOperationId);
|
|
@@ -521,7 +595,7 @@ export function trace(
|
|
|
521
595
|
String(row.evidence_json || '{}'),
|
|
522
596
|
) as Record<string, unknown>;
|
|
523
597
|
const rawEvidence = baseTraceEvidence(row as TraceGraphRow, call, persistedEvidence, contextual.evidence);
|
|
524
|
-
const effective = runtimeResolution(db, row as TraceGraphRow, rawEvidence, options.vars, workspaceIdForCall(db, String(call.id)));
|
|
598
|
+
const effective = runtimeResolution(db, row as TraceGraphRow, rawEvidence, options.vars, workspaceIdForCall(db, String(call.id)), contextual.unresolvedReason);
|
|
525
599
|
const evidence = effective.evidence;
|
|
526
600
|
const effectiveRow = effective.row;
|
|
527
601
|
const targetNode = `${effectiveRow.to_kind}:${effectiveRow.to_id}`;
|
package/src/types.ts
CHANGED
|
@@ -101,6 +101,18 @@ export interface HandlerMethodFact {
|
|
|
101
101
|
decoratorKind: string;
|
|
102
102
|
decoratorValue?: string;
|
|
103
103
|
decoratorRawExpression: string;
|
|
104
|
+
decoratorResolution: {
|
|
105
|
+
rawExpression: string;
|
|
106
|
+
resolvedValue?: string;
|
|
107
|
+
resolutionKind:
|
|
108
|
+
| 'literal'
|
|
109
|
+
| 'const_identifier'
|
|
110
|
+
| 'enum_member'
|
|
111
|
+
| 'const_object_property'
|
|
112
|
+
| 'generated_constant_name'
|
|
113
|
+
| 'unresolved';
|
|
114
|
+
unresolvedReason?: string;
|
|
115
|
+
};
|
|
104
116
|
sourceFile: string;
|
|
105
117
|
sourceLine: number;
|
|
106
118
|
}
|