@saptools/service-flow 0.1.38 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-WE3A6TOJ.js → chunk-SAZ5OK7R.js} +50 -9
- package/dist/chunk-SAZ5OK7R.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/parsers/outbound-call-parser.ts +51 -8
- package/dist/chunk-WE3A6TOJ.js.map +0 -1
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -92,9 +92,48 @@ function nameOfProperty(name: ts.PropertyName): string | undefined {
|
|
|
92
92
|
function staticExpressionText(expr: ts.Expression | undefined, initializers: Map<string, ts.Expression>): string | undefined {
|
|
93
93
|
if (!expr) return undefined;
|
|
94
94
|
if (isStringLike(expr)) return expr.text;
|
|
95
|
+
if (ts.isTemplateExpression(expr)) return stripQuotes(expr.getText(expr.getSourceFile()));
|
|
95
96
|
if (ts.isIdentifier(expr) && initializers.has(expr.text)) return staticExpressionText(initializers.get(expr.text), initializers);
|
|
96
97
|
return undefined;
|
|
97
98
|
}
|
|
99
|
+
interface StaticPathResolution { text?: string; sourceKind: 'literal' | 'template' | 'const' | 'dynamic'; rawExpression?: string; constName?: string }
|
|
100
|
+
function staticPathExpression(expr: ts.Expression | undefined, initializers: Map<string, ts.Expression>): StaticPathResolution {
|
|
101
|
+
if (!expr) return { sourceKind: 'dynamic' };
|
|
102
|
+
const rawExpression = expr.getText(expr.getSourceFile());
|
|
103
|
+
if (isStringLike(expr)) return { text: expr.text, sourceKind: 'literal', rawExpression };
|
|
104
|
+
if (ts.isTemplateExpression(expr)) return { text: stripQuotes(rawExpression), sourceKind: 'template', rawExpression };
|
|
105
|
+
if (ts.isIdentifier(expr) && initializers.has(expr.text)) {
|
|
106
|
+
const resolved = staticPathExpression(initializers.get(expr.text), initializers);
|
|
107
|
+
return resolved.text ? { ...resolved, sourceKind: 'const', rawExpression, constName: expr.text } : { sourceKind: 'dynamic', rawExpression };
|
|
108
|
+
}
|
|
109
|
+
return { sourceKind: 'dynamic', rawExpression };
|
|
110
|
+
}
|
|
111
|
+
function operationPathFromStatic(text: string | undefined): string | undefined {
|
|
112
|
+
return text ? `/${stripQuotes(text).replace(/^\//, '')}` : undefined;
|
|
113
|
+
}
|
|
114
|
+
interface PathCandidateEvidence { candidatePaths: string[]; normalizedCandidateOperations: string[]; candidateSourceKind: string; candidateIdentifier: string; hasDynamicAssignments: boolean }
|
|
115
|
+
function staticPathCandidates(source: ts.SourceFile, identifier: string, initializers: Map<string, ts.Expression>): PathCandidateEvidence | undefined {
|
|
116
|
+
const paths: string[] = [];
|
|
117
|
+
let hasDynamicAssignments = false;
|
|
118
|
+
const visit = (node: ts.Node): void => {
|
|
119
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === identifier && node.initializer) {
|
|
120
|
+
const resolved = staticPathExpression(node.initializer, initializers);
|
|
121
|
+
if (resolved.text) paths.push(operationPathFromStatic(resolved.text) ?? resolved.text);
|
|
122
|
+
else hasDynamicAssignments = true;
|
|
123
|
+
}
|
|
124
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isIdentifier(node.left) && node.left.text === identifier) {
|
|
125
|
+
const resolved = staticPathExpression(node.right, initializers);
|
|
126
|
+
if (resolved.text) paths.push(operationPathFromStatic(resolved.text) ?? resolved.text);
|
|
127
|
+
else hasDynamicAssignments = true;
|
|
128
|
+
}
|
|
129
|
+
ts.forEachChild(node, visit);
|
|
130
|
+
};
|
|
131
|
+
visit(source);
|
|
132
|
+
const candidatePaths = [...new Set(paths)];
|
|
133
|
+
if (candidatePaths.length === 0) return undefined;
|
|
134
|
+
const normalizedCandidateOperations = [...new Set(candidatePaths.map((candidate) => classifyODataPathIntent(candidate, 'POST').topLevelOperationName).filter((value): value is string => Boolean(value)))];
|
|
135
|
+
return { candidatePaths, normalizedCandidateOperations, candidateSourceKind: 'static candidates observed', candidateIdentifier: identifier, hasDynamicAssignments };
|
|
136
|
+
}
|
|
98
137
|
function destinationExpressionShape(expr: ts.Expression | undefined): string | undefined {
|
|
99
138
|
if (!expr) return undefined;
|
|
100
139
|
if (ts.isIdentifier(expr)) return 'identifier';
|
|
@@ -287,16 +326,17 @@ export function classifyOutboundCallsInSource(source: ts.SourceFile, filePath: s
|
|
|
287
326
|
const query = objectPropertyText(objectArg, 'query');
|
|
288
327
|
const method = stripQuotes(objectPropertyText(objectArg, 'method') ?? 'POST');
|
|
289
328
|
const pathExpr = propertyInitializer(objectArg, 'path') ?? propertyInitializer(objectArg, 'event');
|
|
290
|
-
const
|
|
329
|
+
const resolvedPath = staticPathExpression(pathExpr, initializers);
|
|
330
|
+
const op = pathExpr ? resolvedPath.text ?? pathExpr.getText(source) : undefined;
|
|
291
331
|
const shorthandPath = objectPropertyIsShorthand(objectArg, 'path');
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
const operationPathExpr =
|
|
332
|
+
const candidateEvidence = pathExpr && ts.isIdentifier(pathExpr) && !resolvedPath.text ? staticPathCandidates(source, pathExpr.text, initializers) : undefined;
|
|
333
|
+
const candidateOperationPath = candidateEvidence && !candidateEvidence.hasDynamicAssignments && candidateEvidence.normalizedCandidateOperations.length === 1 && candidateEvidence.candidatePaths.length > 0 ? candidateEvidence.candidatePaths[0] : undefined;
|
|
334
|
+
const operationPathExpr = operationPathFromStatic(resolvedPath.text) ?? candidateOperationPath;
|
|
295
335
|
const intent = classifyODataPathIntent(operationPathExpr, method);
|
|
296
336
|
const entityCallTypes: Record<string, OutboundCallFact['callType']> = { entity_mutation: 'remote_entity_mutation', entity_delete: 'remote_entity_delete', entity_media: 'remote_entity_media', entity_candidate: 'remote_entity_candidate' };
|
|
297
337
|
const entityCallType = entityCallTypes[intent.kind];
|
|
298
338
|
const isODataQueryRead = method.toUpperCase() === 'GET' && ['entity_query', 'entity_key_read', 'entity_navigation_query'].includes(intent.kind);
|
|
299
|
-
add(node, { callType: query ? 'remote_query' : entityCallType ?? (isODataQueryRead ? 'remote_query' : 'remote_action'), serviceVariableName: receiver, method, operationPathExpr, queryEntity: query ? extractQueryEntity(query) : isODataQueryRead ? intent.entitySegment : undefined, payloadSummary: summarizeExpression(objectArg.getText(source)), confidence: op || query ? 0.8 : 0.4, unresolvedReason: !query && pathExpr && !operationPathExpr ? 'dynamic_operation_path_identifier' : undefined }, { receiver, classifier: 'service_client_send_object', operationPathExpression: shorthandPath ? op : undefined, literalPathSource:
|
|
339
|
+
add(node, { callType: query ? 'remote_query' : entityCallType ?? (isODataQueryRead ? 'remote_query' : 'remote_action'), serviceVariableName: receiver, method, operationPathExpr, queryEntity: query ? extractQueryEntity(query) : isODataQueryRead ? intent.entitySegment : undefined, payloadSummary: summarizeExpression(objectArg.getText(source)), confidence: op || query ? 0.8 : 0.4, unresolvedReason: !query && pathExpr && !operationPathExpr ? 'dynamic_operation_path_identifier' : undefined }, { receiver, classifier: 'service_client_send_object', operationPathExpression: shorthandPath ? op : undefined, rawPathExpression: resolvedPath.rawExpression, literalPathSource: resolvedPath.text ? (resolvedPath.sourceKind === 'const' ? 'same_scope_const_initializer' : resolvedPath.sourceKind) : undefined, odataPathIntent: operationPathExpr ? intent : undefined, staticPathCandidates: candidateEvidence, parserWarning: !query && pathExpr && !operationPathExpr ? 'dynamic_operation_path_identifier' : undefined });
|
|
300
340
|
}
|
|
301
341
|
} else if (((ts.isCallExpression(expr) && ts.isIdentifier(expr.expression) && wrapperSpecs.has(expr.expression.text)) || (ts.isIdentifier(expr) && wrapperSpecs.has(expr.text)))) {
|
|
302
342
|
const wrapperName = ts.isIdentifier(expr) ? expr.text : ts.isCallExpression(expr) && ts.isIdentifier(expr.expression) ? expr.expression.text : '';
|
|
@@ -307,10 +347,13 @@ export function classifyOutboundCallsInSource(source: ts.SourceFile, filePath: s
|
|
|
307
347
|
const methodArg = spec?.methodIndex === undefined ? undefined : wrapperArgs[spec.methodIndex];
|
|
308
348
|
const receiver = clientArg && ts.isIdentifier(clientArg) ? clientArg.text : spec?.clientName;
|
|
309
349
|
const method = stripQuotes(staticExpressionText(methodArg, initializers) ?? spec?.methodLiteral ?? 'POST');
|
|
310
|
-
|
|
311
|
-
|
|
350
|
+
const resolvedPath = staticPathExpression(pathArg, initializers);
|
|
351
|
+
const operationPathExpr = operationPathFromStatic(resolvedPath.text);
|
|
352
|
+
const normalizedOperationPath = operationPathExpr ? classifyODataPathIntent(operationPathExpr, method).topLevelOperationName : undefined;
|
|
353
|
+
if (spec && receiver && operationPathExpr) {
|
|
354
|
+
add(node, { callType: 'remote_action', serviceVariableName: receiver, method, operationPathExpr, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.75 }, { receiver, classifier: resolvedPath.sourceKind === 'literal' ? 'higher_order_wrapper_literal_path' : 'higher_order_wrapper_static_path', wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, normalizedOperationPath, literalPathSource: resolvedPath.sourceKind === 'const' ? 'same_scope_const_initializer' : `wrapper_call_${resolvedPath.sourceKind}`, literalCallerArgumentDetected: true });
|
|
312
355
|
} else if (spec && receiver) {
|
|
313
|
-
add(node, { callType: 'remote_action', serviceVariableName: receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.45, unresolvedReason: 'dynamic_operation_path_identifier' }, { receiver, classifier: 'higher_order_wrapper_dynamic_path', wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf(source.text, node.getStart(source)), parserWarning: 'dynamic_operation_path_identifier' });
|
|
356
|
+
add(node, { callType: 'remote_action', serviceVariableName: receiver, method, payloadSummary: summarizeExpression(node.getText(source)), confidence: 0.45, unresolvedReason: 'dynamic_operation_path_identifier' }, { receiver, classifier: 'higher_order_wrapper_dynamic_path', wrapperFunction: wrapperName, wrapperDefinitionLine: spec.definitionLine, callerLine: lineOf(source.text, node.getStart(source)), wrapperPathSourceKind: resolvedPath.sourceKind, rawPathExpression: resolvedPath.rawExpression, parserWarning: 'dynamic_operation_path_identifier' });
|
|
314
357
|
}
|
|
315
358
|
} else if (ts.isPropertyAccessExpression(expr) && ['emit', 'publish', 'on'].includes(expr.name.text)) {
|
|
316
359
|
const receiver = receiverName(expr.expression);
|