@saptools/service-flow 0.1.57 → 0.1.59
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 +12 -0
- package/dist/{chunk-DCOFNDKS.js → chunk-GLZSDBRG.js} +580 -154
- package/dist/chunk-GLZSDBRG.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/linker/003-package-import-symbol-resolver.ts +195 -0
- package/src/linker/cross-repo-linker.ts +2 -0
- package/src/parsers/000-direct-query-execution.ts +2 -0
- package/src/parsers/001-query-entity-resolution.ts +622 -0
- package/src/parsers/outbound-call-parser.ts +7 -94
- package/dist/chunk-DCOFNDKS.js.map +0 -1
|
@@ -10,10 +10,16 @@ import { parseServiceBindings } from './service-binding-parser.js';
|
|
|
10
10
|
import { parseImportedWrapperCalls } from './imported-wrapper-parser.js';
|
|
11
11
|
import {
|
|
12
12
|
directQueryBuilderStatement,
|
|
13
|
-
isCapQueryBuilderRootName,
|
|
14
13
|
queryBuilderRoot,
|
|
15
14
|
type DirectQueryBuilderStatement,
|
|
16
15
|
} from './000-direct-query-execution.js';
|
|
16
|
+
import {
|
|
17
|
+
expressionName,
|
|
18
|
+
maxAliasDepth,
|
|
19
|
+
queryEntityFromAst,
|
|
20
|
+
resolveBinding,
|
|
21
|
+
variableInitializers,
|
|
22
|
+
} from './001-query-entity-resolution.js';
|
|
17
23
|
import type { RepositorySourceContext } from './ts-project.js';
|
|
18
24
|
import {
|
|
19
25
|
analyzeOperationPath,
|
|
@@ -24,47 +30,6 @@ import {
|
|
|
24
30
|
function lineOf(text: string, idx: number): number {
|
|
25
31
|
return text.slice(0, idx).split('\n').length;
|
|
26
32
|
}
|
|
27
|
-
function entityFromExpression(expr: ts.Expression | undefined): string | undefined {
|
|
28
|
-
if (!expr) return undefined;
|
|
29
|
-
if (ts.isIdentifier(expr) || ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr)) return expr.text;
|
|
30
|
-
if (ts.isPropertyAccessExpression(expr) && expr.expression.kind === ts.SyntaxKind.ThisKeyword) return expr.name.text;
|
|
31
|
-
if (ts.isElementAccessExpression(expr) && expr.argumentExpression && (ts.isStringLiteral(expr.argumentExpression) || ts.isNoSubstitutionTemplateLiteral(expr.argumentExpression))) return expr.argumentExpression.text;
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
function expressionName(expr: ts.Expression): string {
|
|
35
|
-
if (ts.isIdentifier(expr)) return expr.text;
|
|
36
|
-
if (ts.isPropertyAccessExpression(expr)) return `${expressionName(expr.expression)}.${expr.name.text}`;
|
|
37
|
-
return expr.getText();
|
|
38
|
-
}
|
|
39
|
-
function variableInitializers(source: ts.SourceFile): Map<string, ts.Expression> {
|
|
40
|
-
const initializers = new Map<string, ts.Expression>();
|
|
41
|
-
for (const statement of source.statements) {
|
|
42
|
-
if (!ts.isVariableStatement(statement) || (statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
|
|
43
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
44
|
-
if (ts.isIdentifier(declaration.name) && declaration.initializer) initializers.set(declaration.name.text, declaration.initializer);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return initializers;
|
|
48
|
-
}
|
|
49
|
-
function unwrapQueryExpression(expr: ts.Expression): ts.Expression {
|
|
50
|
-
if (ts.isParenthesizedExpression(expr) || ts.isAwaitExpression(expr)
|
|
51
|
-
|| ts.isAsExpression(expr) || ts.isTypeAssertionExpression(expr)
|
|
52
|
-
|| ts.isNonNullExpression(expr) || ts.isSatisfiesExpression(expr))
|
|
53
|
-
return unwrapQueryExpression(expr.expression);
|
|
54
|
-
return expr;
|
|
55
|
-
}
|
|
56
|
-
function queryEntityFromAst(expr: ts.Expression, initializers = new Map<string, ts.Expression>()): string | undefined {
|
|
57
|
-
const unwrapped = unwrapQueryExpression(expr);
|
|
58
|
-
if (ts.isIdentifier(unwrapped) && initializers.has(unwrapped.text)) return queryEntityFromAst(initializers.get(unwrapped.text) as ts.Expression, initializers);
|
|
59
|
-
if (ts.isCallExpression(unwrapped)) {
|
|
60
|
-
const name = expressionName(unwrapped.expression);
|
|
61
|
-
if (name === 'cds.run') return queryEntityFromAst(unwrapped.arguments[0], initializers);
|
|
62
|
-
if (isCapQueryBuilderRootName(name)) return entityFromExpression(unwrapped.arguments[0]);
|
|
63
|
-
const receiver = ts.isPropertyAccessExpression(unwrapped.expression) ? unwrapped.expression.expression : undefined;
|
|
64
|
-
if (receiver) return queryEntityFromAst(receiver, initializers);
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
33
|
function queryBuilderEvidence(source: ts.SourceFile, statement: DirectQueryBuilderStatement): Record<string, unknown> {
|
|
69
34
|
return {
|
|
70
35
|
classifier: 'cap_query_builder_direct',
|
|
@@ -140,8 +105,6 @@ function nameOfProperty(name: ts.PropertyName): string | undefined {
|
|
|
140
105
|
type ExpressionStatus = 'static' | 'dynamic' | 'ambiguous' | 'unknown';
|
|
141
106
|
type ExpressionSourceKind = 'string_literal' | 'no_substitution_template' | 'template_with_substitutions' | 'const_alias' | 'conditional_candidates' | 'dynamic_expression';
|
|
142
107
|
interface ExpressionResolution { status: ExpressionStatus; sourceKind: ExpressionSourceKind; value?: string; rawExpression?: string; placeholderKeys: string[]; evidence: string[]; constName?: string }
|
|
143
|
-
interface BindingResolution { declaration?: ts.VariableDeclaration | ts.ParameterDeclaration; initializer?: ts.Expression; immutable: boolean; evidence: string[] }
|
|
144
|
-
const maxAliasDepth = 5;
|
|
145
108
|
function safeRaw(expr: ts.Expression): string | undefined {
|
|
146
109
|
if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr) || ts.isIdentifier(expr) || ts.isTemplateExpression(expr)) return expr.getText(expr.getSourceFile());
|
|
147
110
|
return undefined;
|
|
@@ -149,56 +112,6 @@ function safeRaw(expr: ts.Expression): string | undefined {
|
|
|
149
112
|
function placeholders(expr: ts.TemplateExpression): string[] {
|
|
150
113
|
return expr.templateSpans.map((span) => span.expression.getText(expr.getSourceFile()));
|
|
151
114
|
}
|
|
152
|
-
function isFunctionLikeScope(node: ts.Node): boolean {
|
|
153
|
-
return ts.isFunctionLike(node) || ts.isSourceFile(node);
|
|
154
|
-
}
|
|
155
|
-
function nodeContains(parent: ts.Node, child: ts.Node): boolean {
|
|
156
|
-
const source = child.getSourceFile();
|
|
157
|
-
return child.getStart(source) >= parent.getStart(source) && child.getEnd() <= parent.getEnd();
|
|
158
|
-
}
|
|
159
|
-
function declarationScope(node: ts.VariableDeclaration | ts.ParameterDeclaration): ts.Node {
|
|
160
|
-
if (ts.isParameter(node)) return node.parent;
|
|
161
|
-
if (ts.isCatchClause(node.parent) && node.parent.variableDeclaration === node) return node.parent;
|
|
162
|
-
const list = node.parent;
|
|
163
|
-
const blockScoped = (list.flags & (ts.NodeFlags.Const | ts.NodeFlags.Let)) !== 0;
|
|
164
|
-
let current: ts.Node = list.parent;
|
|
165
|
-
if (!blockScoped) {
|
|
166
|
-
while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
|
|
167
|
-
return current;
|
|
168
|
-
}
|
|
169
|
-
while (current.parent && !ts.isBlock(current) && !ts.isSourceFile(current) && !ts.isModuleBlock(current) && !ts.isCaseBlock(current) && !isLoopInitializerScope(node, current) && !isFunctionLikeScope(current)) current = current.parent;
|
|
170
|
-
return current;
|
|
171
|
-
}
|
|
172
|
-
function isLoopInitializerScope(declaration: ts.VariableDeclaration, scope: ts.Node): boolean {
|
|
173
|
-
const list = declaration.parent;
|
|
174
|
-
return (ts.isForStatement(scope) && scope.initializer === list) || ((ts.isForInStatement(scope) || ts.isForOfStatement(scope)) && scope.initializer === list);
|
|
175
|
-
}
|
|
176
|
-
function catchBindingScope(declaration: ts.VariableDeclaration | ts.ParameterDeclaration): ts.CatchClause | undefined {
|
|
177
|
-
if (ts.isParameter(declaration)) return undefined;
|
|
178
|
-
return ts.isCatchClause(declaration.parent) && declaration.parent.variableDeclaration === declaration ? declaration.parent : undefined;
|
|
179
|
-
}
|
|
180
|
-
function isAccessibleDeclaration(declaration: ts.VariableDeclaration | ts.ParameterDeclaration, use: ts.Node): boolean {
|
|
181
|
-
const source = use.getSourceFile();
|
|
182
|
-
if (declaration.name.getStart(source) >= use.getStart(source)) return false;
|
|
183
|
-
const catchScope = catchBindingScope(declaration);
|
|
184
|
-
if (catchScope) return nodeContains(catchScope.block, use);
|
|
185
|
-
const scope = declarationScope(declaration);
|
|
186
|
-
if (ts.isForStatement(scope) || ts.isForInStatement(scope) || ts.isForOfStatement(scope)) return nodeContains(scope.statement, use);
|
|
187
|
-
return ts.isSourceFile(scope) || nodeContains(scope, use);
|
|
188
|
-
}
|
|
189
|
-
function resolveBinding(identifier: ts.Identifier, use: ts.Node): BindingResolution {
|
|
190
|
-
const source = use.getSourceFile();
|
|
191
|
-
let best: ts.VariableDeclaration | ts.ParameterDeclaration | undefined;
|
|
192
|
-
const visit = (node: ts.Node): void => {
|
|
193
|
-
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
|
|
194
|
-
if (ts.isParameter(node) && ts.isIdentifier(node.name) && node.name.text === identifier.text && isAccessibleDeclaration(node, use)) best = node;
|
|
195
|
-
ts.forEachChild(node, visit);
|
|
196
|
-
};
|
|
197
|
-
visit(source);
|
|
198
|
-
if (!best) return { immutable: false, evidence: ['binding_not_found'] };
|
|
199
|
-
const immutable = ts.isVariableDeclaration(best) && (best.parent.flags & ts.NodeFlags.Const) !== 0;
|
|
200
|
-
return { declaration: best, initializer: ts.isVariableDeclaration(best) ? best.initializer : undefined, immutable, evidence: [immutable ? 'lexical_const_binding_before_use' : 'lexical_mutable_or_parameter_binding'] };
|
|
201
|
-
}
|
|
202
115
|
function resolveExpression(expr: ts.Expression | undefined, use: ts.Node, policy: 'operation_path' | 'external' | 'literal', depth = 0, seen = new Set<ts.Node>()): ExpressionResolution {
|
|
203
116
|
if (!expr) return { status: 'unknown', sourceKind: 'dynamic_expression', placeholderKeys: [], evidence: ['expression_missing'] };
|
|
204
117
|
if (ts.isStringLiteral(expr)) return { status: 'static', sourceKind: 'string_literal', value: expr.text, rawExpression: safeRaw(expr), placeholderKeys: [], evidence: ['string_literal'] };
|