@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.
@@ -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'] };