@saptools/service-flow 0.1.58 → 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 +6 -0
- package/dist/{chunk-CRSSXWQ2.js → chunk-GLZSDBRG.js} +432 -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/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-CRSSXWQ2.js.map +0 -1
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { isCapQueryBuilderRootName } from './000-direct-query-execution.js';
|
|
3
|
+
|
|
4
|
+
export interface BindingResolution {
|
|
5
|
+
declaration?: ts.VariableDeclaration | ts.ParameterDeclaration;
|
|
6
|
+
initializer?: ts.Expression;
|
|
7
|
+
immutable: boolean;
|
|
8
|
+
evidence: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface DestructuredBinding {
|
|
12
|
+
declaration: ts.VariableDeclaration | ts.ParameterDeclaration;
|
|
13
|
+
initializer?: ts.Expression;
|
|
14
|
+
entityName: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const maxAliasDepth = 5;
|
|
18
|
+
const cdsModelSpecifier = /^#cds-models(?:\/|$)/;
|
|
19
|
+
const modelNamespaceCache = new WeakMap<ts.SourceFile, Set<string>>();
|
|
20
|
+
|
|
21
|
+
export function expressionName(expr: ts.Expression): string {
|
|
22
|
+
if (ts.isIdentifier(expr)) return expr.text;
|
|
23
|
+
if (ts.isPropertyAccessExpression(expr))
|
|
24
|
+
return `${expressionName(expr.expression)}.${expr.name.text}`;
|
|
25
|
+
return expr.getText();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function variableInitializers(
|
|
29
|
+
source: ts.SourceFile,
|
|
30
|
+
): Map<string, ts.Expression> {
|
|
31
|
+
const initializers = new Map<string, ts.Expression>();
|
|
32
|
+
for (const statement of source.statements) {
|
|
33
|
+
if (!ts.isVariableStatement(statement)
|
|
34
|
+
|| (statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue;
|
|
35
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
36
|
+
if (ts.isIdentifier(declaration.name) && declaration.initializer)
|
|
37
|
+
initializers.set(declaration.name.text, declaration.initializer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return initializers;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function unwrapQueryExpression(expr: ts.Expression): ts.Expression {
|
|
44
|
+
if (ts.isParenthesizedExpression(expr) || ts.isAwaitExpression(expr)
|
|
45
|
+
|| ts.isAsExpression(expr) || ts.isTypeAssertionExpression(expr)
|
|
46
|
+
|| ts.isNonNullExpression(expr) || ts.isSatisfiesExpression(expr))
|
|
47
|
+
return unwrapQueryExpression(expr.expression);
|
|
48
|
+
return expr;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isFunctionLikeScope(node: ts.Node): boolean {
|
|
52
|
+
return ts.isFunctionLike(node) || ts.isSourceFile(node);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function nodeContains(parent: ts.Node, child: ts.Node): boolean {
|
|
56
|
+
const source = child.getSourceFile();
|
|
57
|
+
return child.getStart(source) >= parent.getStart(source)
|
|
58
|
+
&& child.getEnd() <= parent.getEnd();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isLoopInitializerScope(
|
|
62
|
+
declaration: ts.VariableDeclaration,
|
|
63
|
+
scope: ts.Node,
|
|
64
|
+
): boolean {
|
|
65
|
+
const list = declaration.parent;
|
|
66
|
+
return (ts.isForStatement(scope) && scope.initializer === list)
|
|
67
|
+
|| ((ts.isForInStatement(scope) || ts.isForOfStatement(scope))
|
|
68
|
+
&& scope.initializer === list);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isLexicalScopeBoundary(
|
|
72
|
+
node: ts.Node,
|
|
73
|
+
declaration: ts.VariableDeclaration,
|
|
74
|
+
): boolean {
|
|
75
|
+
return ts.isBlock(node) || ts.isSourceFile(node) || ts.isModuleBlock(node)
|
|
76
|
+
|| ts.isCaseBlock(node) || isLoopInitializerScope(declaration, node)
|
|
77
|
+
|| isFunctionLikeScope(node);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function declarationScope(
|
|
81
|
+
node: ts.VariableDeclaration | ts.ParameterDeclaration,
|
|
82
|
+
): ts.Node {
|
|
83
|
+
if (ts.isParameter(node)) return node.parent;
|
|
84
|
+
if (ts.isCatchClause(node.parent) && node.parent.variableDeclaration === node)
|
|
85
|
+
return node.parent;
|
|
86
|
+
const list = node.parent;
|
|
87
|
+
const blockScoped = (list.flags & (ts.NodeFlags.Const | ts.NodeFlags.Let)) !== 0;
|
|
88
|
+
let current: ts.Node = list.parent;
|
|
89
|
+
if (!blockScoped) {
|
|
90
|
+
while (current.parent && !isFunctionLikeScope(current)) current = current.parent;
|
|
91
|
+
return current;
|
|
92
|
+
}
|
|
93
|
+
while (current.parent && !isLexicalScopeBoundary(current, node))
|
|
94
|
+
current = current.parent;
|
|
95
|
+
return current;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function catchBindingScope(
|
|
99
|
+
declaration: ts.VariableDeclaration | ts.ParameterDeclaration,
|
|
100
|
+
): ts.CatchClause | undefined {
|
|
101
|
+
if (ts.isParameter(declaration)) return undefined;
|
|
102
|
+
return ts.isCatchClause(declaration.parent)
|
|
103
|
+
&& declaration.parent.variableDeclaration === declaration
|
|
104
|
+
? declaration.parent
|
|
105
|
+
: undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function declarationIsInScope(
|
|
109
|
+
declaration: ts.VariableDeclaration | ts.ParameterDeclaration,
|
|
110
|
+
use: ts.Node,
|
|
111
|
+
): boolean {
|
|
112
|
+
const catchScope = catchBindingScope(declaration);
|
|
113
|
+
if (catchScope) return nodeContains(catchScope.block, use);
|
|
114
|
+
const scope = declarationScope(declaration);
|
|
115
|
+
if (ts.isForStatement(scope) || ts.isForInStatement(scope)
|
|
116
|
+
|| ts.isForOfStatement(scope)) return nodeContains(scope.statement, use);
|
|
117
|
+
return ts.isSourceFile(scope) || nodeContains(scope, use);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isAccessibleDeclaration(
|
|
121
|
+
declaration: ts.VariableDeclaration | ts.ParameterDeclaration,
|
|
122
|
+
use: ts.Node,
|
|
123
|
+
): boolean {
|
|
124
|
+
return declaration.name.getStart(use.getSourceFile()) < use.getStart()
|
|
125
|
+
&& declarationIsInScope(declaration, use);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function resolveBinding(
|
|
129
|
+
identifier: ts.Identifier,
|
|
130
|
+
use: ts.Node,
|
|
131
|
+
): BindingResolution {
|
|
132
|
+
const source = use.getSourceFile();
|
|
133
|
+
let best: ts.VariableDeclaration | ts.ParameterDeclaration | undefined;
|
|
134
|
+
const visit = (node: ts.Node): void => {
|
|
135
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)
|
|
136
|
+
&& node.name.text === identifier.text && isAccessibleDeclaration(node, use))
|
|
137
|
+
best = node;
|
|
138
|
+
if (ts.isParameter(node) && ts.isIdentifier(node.name)
|
|
139
|
+
&& node.name.text === identifier.text && isAccessibleDeclaration(node, use))
|
|
140
|
+
best = node;
|
|
141
|
+
ts.forEachChild(node, visit);
|
|
142
|
+
};
|
|
143
|
+
visit(source);
|
|
144
|
+
if (!best) return { immutable: false, evidence: ['binding_not_found'] };
|
|
145
|
+
const immutable = ts.isVariableDeclaration(best)
|
|
146
|
+
&& (best.parent.flags & ts.NodeFlags.Const) !== 0;
|
|
147
|
+
return {
|
|
148
|
+
declaration: best,
|
|
149
|
+
initializer: ts.isVariableDeclaration(best) ? best.initializer : undefined,
|
|
150
|
+
immutable,
|
|
151
|
+
evidence: [immutable
|
|
152
|
+
? 'lexical_const_binding_before_use'
|
|
153
|
+
: 'lexical_mutable_or_parameter_binding'],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function directBindingElement(
|
|
158
|
+
name: ts.BindingName,
|
|
159
|
+
identifier: ts.Identifier,
|
|
160
|
+
): ts.BindingElement | undefined {
|
|
161
|
+
if (ts.isIdentifier(name)) return undefined;
|
|
162
|
+
for (const element of name.elements) {
|
|
163
|
+
if (ts.isOmittedExpression(element) || element.dotDotDotToken
|
|
164
|
+
|| element.initializer
|
|
165
|
+
|| !ts.isIdentifier(element.name)) continue;
|
|
166
|
+
if (element.name.text === identifier.text) return element;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function bindingNameContains(name: ts.BindingName, target: string): boolean {
|
|
172
|
+
if (ts.isIdentifier(name)) return name.text === target;
|
|
173
|
+
return name.elements.some((element) => ts.isBindingElement(element)
|
|
174
|
+
&& bindingNameContains(element.name, target));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function bindingScope(node: ts.Node): ts.Node {
|
|
178
|
+
if (ts.isVariableDeclaration(node) || ts.isParameter(node))
|
|
179
|
+
return declarationScope(node);
|
|
180
|
+
if (ts.isFunctionExpression(node) || ts.isClassExpression(node)) return node;
|
|
181
|
+
return node.parent;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function bindingIsCloser(candidate: ts.Node, current: ts.Node, use: ts.Node): boolean {
|
|
185
|
+
const source = use.getSourceFile();
|
|
186
|
+
const candidateScope = bindingScope(candidate);
|
|
187
|
+
const currentScope = bindingScope(current);
|
|
188
|
+
const candidateSpan = candidateScope.getEnd() - candidateScope.getStart(source);
|
|
189
|
+
const currentSpan = currentScope.getEnd() - currentScope.getStart(source);
|
|
190
|
+
return candidateSpan < currentSpan
|
|
191
|
+
|| (candidateSpan === currentSpan
|
|
192
|
+
&& candidate.getStart(source) > current.getStart(source));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function nearestScopedVariableDeclaration(
|
|
196
|
+
identifier: ts.Identifier,
|
|
197
|
+
): ts.VariableDeclaration | ts.ParameterDeclaration | undefined {
|
|
198
|
+
let best: ts.VariableDeclaration | ts.ParameterDeclaration | undefined;
|
|
199
|
+
const visit = (node: ts.Node): void => {
|
|
200
|
+
if ((ts.isVariableDeclaration(node) || ts.isParameter(node))
|
|
201
|
+
&& bindingNameContains(node.name, identifier.text)
|
|
202
|
+
&& declarationIsInScope(node, identifier)
|
|
203
|
+
&& (!best || bindingIsCloser(node, best, identifier))) best = node;
|
|
204
|
+
ts.forEachChild(node, visit);
|
|
205
|
+
};
|
|
206
|
+
visit(identifier.getSourceFile());
|
|
207
|
+
return best;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function hasScopedVariableDeclaration(identifier: ts.Identifier): boolean {
|
|
211
|
+
return Boolean(nearestScopedVariableDeclaration(identifier));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function namedValueDeclarationMatches(
|
|
215
|
+
node: ts.Node,
|
|
216
|
+
name: string,
|
|
217
|
+
): boolean {
|
|
218
|
+
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)
|
|
219
|
+
|| ts.isEnumDeclaration(node)) return node.name?.text === name;
|
|
220
|
+
return ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)
|
|
221
|
+
&& node.name.text === name;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function declarationContainerContains(node: ts.Node, use: ts.Node): boolean {
|
|
225
|
+
const container = node.parent;
|
|
226
|
+
return ts.isSourceFile(container)
|
|
227
|
+
|| ((ts.isBlock(container) || ts.isModuleBlock(container))
|
|
228
|
+
&& nodeContains(container, use));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function selfNamedValueDeclarationMatches(
|
|
232
|
+
node: ts.Node,
|
|
233
|
+
identifier: ts.Identifier,
|
|
234
|
+
): boolean {
|
|
235
|
+
return (ts.isFunctionExpression(node) || ts.isClassExpression(node))
|
|
236
|
+
&& node.name?.text === identifier.text
|
|
237
|
+
&& nodeContains(node, identifier);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function scopedNonVariableDeclarationMatches(
|
|
241
|
+
node: ts.Node,
|
|
242
|
+
identifier: ts.Identifier,
|
|
243
|
+
): boolean {
|
|
244
|
+
return (namedValueDeclarationMatches(node, identifier.text)
|
|
245
|
+
&& declarationContainerContains(node, identifier))
|
|
246
|
+
|| selfNamedValueDeclarationMatches(node, identifier);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function nearestScopedNonVariableDeclaration(
|
|
250
|
+
identifier: ts.Identifier,
|
|
251
|
+
): ts.Node | undefined {
|
|
252
|
+
let best: ts.Node | undefined;
|
|
253
|
+
const visit = (node: ts.Node): void => {
|
|
254
|
+
if (scopedNonVariableDeclarationMatches(node, identifier)
|
|
255
|
+
&& (!best || bindingIsCloser(node, best, identifier))) best = node;
|
|
256
|
+
ts.forEachChild(node, visit);
|
|
257
|
+
};
|
|
258
|
+
visit(identifier.getSourceFile());
|
|
259
|
+
return best;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function hasScopedNonVariableDeclaration(identifier: ts.Identifier): boolean {
|
|
263
|
+
return Boolean(nearestScopedNonVariableDeclaration(identifier));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function nonVariableBindingWins(
|
|
267
|
+
identifier: ts.Identifier,
|
|
268
|
+
selected: ts.Node | undefined,
|
|
269
|
+
): boolean {
|
|
270
|
+
const declaration = nearestScopedNonVariableDeclaration(identifier);
|
|
271
|
+
return Boolean(declaration
|
|
272
|
+
&& (!selected || bindingIsCloser(declaration, selected, identifier)));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function importClauseBinds(
|
|
276
|
+
clause: ts.ImportClause | undefined,
|
|
277
|
+
name: string,
|
|
278
|
+
): boolean {
|
|
279
|
+
if (!clause || clause.isTypeOnly) return false;
|
|
280
|
+
if (clause.name?.text === name) return true;
|
|
281
|
+
const bindings = clause.namedBindings;
|
|
282
|
+
if (!bindings) return false;
|
|
283
|
+
if (ts.isNamespaceImport(bindings)) return bindings.name.text === name;
|
|
284
|
+
return bindings.elements.some((element) => !element.isTypeOnly
|
|
285
|
+
&& element.name.text === name);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function hasValueImportBinding(identifier: ts.Identifier): boolean {
|
|
289
|
+
return identifier.getSourceFile().statements.some((statement) => {
|
|
290
|
+
if (ts.isImportDeclaration(statement))
|
|
291
|
+
return importClauseBinds(statement.importClause, identifier.text);
|
|
292
|
+
return ts.isImportEqualsDeclaration(statement) && !statement.isTypeOnly
|
|
293
|
+
&& statement.name.text === identifier.text;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function sourcePropertyName(
|
|
298
|
+
element: ts.BindingElement,
|
|
299
|
+
localName: string,
|
|
300
|
+
): string | undefined {
|
|
301
|
+
const property = element.propertyName;
|
|
302
|
+
if (!property) return localName;
|
|
303
|
+
if (ts.isIdentifier(property) || ts.isStringLiteral(property)
|
|
304
|
+
|| ts.isNumericLiteral(property)) return property.text;
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function resolveDestructuredBinding(
|
|
309
|
+
identifier: ts.Identifier,
|
|
310
|
+
use: ts.Node,
|
|
311
|
+
): DestructuredBinding | undefined {
|
|
312
|
+
const source = use.getSourceFile();
|
|
313
|
+
let best: DestructuredBinding | undefined;
|
|
314
|
+
const visit = (node: ts.Node): void => {
|
|
315
|
+
if ((ts.isVariableDeclaration(node) || ts.isParameter(node))
|
|
316
|
+
&& isAccessibleDeclaration(node, use)) {
|
|
317
|
+
const element = directBindingElement(node.name, identifier);
|
|
318
|
+
const entityName = element
|
|
319
|
+
? sourcePropertyName(element, identifier.text)
|
|
320
|
+
: undefined;
|
|
321
|
+
if (entityName && (!best || node.name.getStart(source)
|
|
322
|
+
> best.declaration.name.getStart(source))) {
|
|
323
|
+
best = {
|
|
324
|
+
declaration: node,
|
|
325
|
+
initializer: node.initializer,
|
|
326
|
+
entityName,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
ts.forEachChild(node, visit);
|
|
331
|
+
};
|
|
332
|
+
visit(source);
|
|
333
|
+
return best;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function isEntitiesBase(expr: ts.Expression): boolean {
|
|
337
|
+
const value = unwrapQueryExpression(expr);
|
|
338
|
+
if (ts.isPropertyAccessExpression(value)) return value.name.text === 'entities';
|
|
339
|
+
return ts.isCallExpression(value)
|
|
340
|
+
&& ts.isPropertyAccessExpression(value.expression)
|
|
341
|
+
&& value.expression.name.text === 'entities';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function hasLexicalValueShadow(
|
|
345
|
+
identifier: ts.Identifier,
|
|
346
|
+
includeImports: boolean,
|
|
347
|
+
): boolean {
|
|
348
|
+
return hasScopedVariableDeclaration(identifier)
|
|
349
|
+
|| hasScopedNonVariableDeclaration(identifier)
|
|
350
|
+
|| (includeImports && hasValueImportBinding(identifier));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function modelRequire(expr: ts.Expression): boolean {
|
|
354
|
+
const value = unwrapQueryExpression(expr);
|
|
355
|
+
if (!ts.isCallExpression(value) || !ts.isIdentifier(value.expression)
|
|
356
|
+
|| value.expression.text !== 'require' || value.arguments.length !== 1)
|
|
357
|
+
return false;
|
|
358
|
+
if (hasLexicalValueShadow(value.expression, true)) return false;
|
|
359
|
+
const specifier = value.arguments[0];
|
|
360
|
+
return Boolean(specifier && ts.isStringLiteralLike(specifier)
|
|
361
|
+
&& cdsModelSpecifier.test(specifier.text));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function modelNamespaceImportName(
|
|
365
|
+
statement: ts.Statement,
|
|
366
|
+
): string | undefined {
|
|
367
|
+
if (!ts.isImportDeclaration(statement)
|
|
368
|
+
|| !ts.isStringLiteralLike(statement.moduleSpecifier)
|
|
369
|
+
|| !cdsModelSpecifier.test(statement.moduleSpecifier.text)
|
|
370
|
+
|| statement.importClause?.isTypeOnly) return undefined;
|
|
371
|
+
const bindings = statement.importClause?.namedBindings;
|
|
372
|
+
return bindings && ts.isNamespaceImport(bindings)
|
|
373
|
+
? bindings.name.text
|
|
374
|
+
: undefined;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function modelImportEqualsName(
|
|
378
|
+
statement: ts.Statement,
|
|
379
|
+
): string | undefined {
|
|
380
|
+
if (!ts.isImportEqualsDeclaration(statement) || statement.isTypeOnly
|
|
381
|
+
|| !ts.isExternalModuleReference(statement.moduleReference)) return undefined;
|
|
382
|
+
const specifier = statement.moduleReference.expression;
|
|
383
|
+
return specifier && ts.isStringLiteralLike(specifier)
|
|
384
|
+
&& cdsModelSpecifier.test(specifier.text)
|
|
385
|
+
? statement.name.text
|
|
386
|
+
: undefined;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function modelNamespaceNames(source: ts.SourceFile): Set<string> {
|
|
390
|
+
const cached = modelNamespaceCache.get(source);
|
|
391
|
+
if (cached) return cached;
|
|
392
|
+
const names = new Set<string>();
|
|
393
|
+
for (const statement of source.statements) {
|
|
394
|
+
const name = modelNamespaceImportName(statement)
|
|
395
|
+
?? modelImportEqualsName(statement);
|
|
396
|
+
if (name) names.add(name);
|
|
397
|
+
}
|
|
398
|
+
modelNamespaceCache.set(source, names);
|
|
399
|
+
return names;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function destructuredEntitySource(
|
|
403
|
+
initializer: ts.Expression | undefined,
|
|
404
|
+
): boolean {
|
|
405
|
+
if (!initializer) return false;
|
|
406
|
+
if (isEntitiesBase(initializer) || modelRequire(initializer)) return true;
|
|
407
|
+
const value = unwrapQueryExpression(initializer);
|
|
408
|
+
return ts.isIdentifier(value)
|
|
409
|
+
&& !hasLexicalValueShadow(value, false)
|
|
410
|
+
&& modelNamespaceNames(value.getSourceFile()).has(value.text);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function isAssignmentOperator(kind: ts.SyntaxKind): boolean {
|
|
414
|
+
return kind >= ts.SyntaxKind.FirstAssignment
|
|
415
|
+
&& kind <= ts.SyntaxKind.LastAssignment;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function identifierIsReadWithinTarget(
|
|
419
|
+
identifier: ts.Identifier,
|
|
420
|
+
parent: ts.Node,
|
|
421
|
+
): boolean {
|
|
422
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === identifier) return true;
|
|
423
|
+
if (ts.isElementAccessExpression(parent) && parent.argumentExpression
|
|
424
|
+
&& nodeContains(parent.argumentExpression, identifier)) return true;
|
|
425
|
+
if (ts.isPropertyAssignment(parent) && nodeContains(parent.name, identifier)) return true;
|
|
426
|
+
return ts.isComputedPropertyName(parent) && nodeContains(parent.expression, identifier);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function identifierIsUnaryWrite(identifier: ts.Identifier): boolean {
|
|
430
|
+
const direct = identifier.parent;
|
|
431
|
+
if (!((ts.isPrefixUnaryExpression(direct) || ts.isPostfixUnaryExpression(direct))
|
|
432
|
+
&& direct.operand === identifier)) return false;
|
|
433
|
+
return direct.operator === ts.SyntaxKind.PlusPlusToken
|
|
434
|
+
|| direct.operator === ts.SyntaxKind.MinusMinusToken;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function identifierIsNestedWrite(identifier: ts.Identifier): boolean {
|
|
438
|
+
let current: ts.Node = identifier;
|
|
439
|
+
while (current.parent) {
|
|
440
|
+
const parent = current.parent;
|
|
441
|
+
if (identifierIsReadWithinTarget(identifier, parent)) return false;
|
|
442
|
+
if (ts.isBinaryExpression(parent))
|
|
443
|
+
return isAssignmentOperator(parent.operatorToken.kind)
|
|
444
|
+
&& nodeContains(parent.left, identifier);
|
|
445
|
+
if (ts.isForInStatement(parent) || ts.isForOfStatement(parent))
|
|
446
|
+
return nodeContains(parent.initializer, identifier);
|
|
447
|
+
current = parent;
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function identifierIsWrite(identifier: ts.Identifier): boolean {
|
|
453
|
+
return identifierIsUnaryWrite(identifier) || identifierIsNestedWrite(identifier);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function destructuredBindingWasWritten(
|
|
457
|
+
binding: DestructuredBinding,
|
|
458
|
+
localName: string,
|
|
459
|
+
use: ts.Node,
|
|
460
|
+
): boolean {
|
|
461
|
+
if (ts.isParameter(binding.declaration)) return true;
|
|
462
|
+
if ((binding.declaration.parent.flags & ts.NodeFlags.Const) !== 0) return false;
|
|
463
|
+
let written = false;
|
|
464
|
+
const declarationEnd = binding.declaration.name.getEnd();
|
|
465
|
+
const useStart = use.getStart();
|
|
466
|
+
const visit = (node: ts.Node): void => {
|
|
467
|
+
if (written) return;
|
|
468
|
+
if (ts.isIdentifier(node) && node.text === localName
|
|
469
|
+
&& node.getStart() > declarationEnd && node.getStart() < useStart
|
|
470
|
+
&& identifierIsWrite(node)
|
|
471
|
+
&& resolveDestructuredBinding(node, node)?.declaration === binding.declaration)
|
|
472
|
+
written = true;
|
|
473
|
+
ts.forEachChild(node, visit);
|
|
474
|
+
};
|
|
475
|
+
visit(use.getSourceFile());
|
|
476
|
+
return written;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function destructuredBindingWins(
|
|
480
|
+
destructured: DestructuredBinding,
|
|
481
|
+
simple: BindingResolution,
|
|
482
|
+
use: ts.Node,
|
|
483
|
+
): boolean {
|
|
484
|
+
return !simple.declaration
|
|
485
|
+
|| bindingIsCloser(destructured.declaration, simple.declaration, use);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function entityFromDestructuredBinding(
|
|
489
|
+
binding: DestructuredBinding,
|
|
490
|
+
identifier: ts.Identifier,
|
|
491
|
+
): string | undefined {
|
|
492
|
+
if (destructuredBindingWasWritten(binding, identifier.text, identifier))
|
|
493
|
+
return undefined;
|
|
494
|
+
return destructuredEntitySource(binding.initializer)
|
|
495
|
+
? binding.entityName
|
|
496
|
+
: undefined;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function entityFromSimpleBinding(
|
|
500
|
+
binding: BindingResolution,
|
|
501
|
+
depth: number,
|
|
502
|
+
seen: Set<ts.Node>,
|
|
503
|
+
): string | undefined {
|
|
504
|
+
if (!binding.declaration || !binding.immutable || !binding.initializer
|
|
505
|
+
|| seen.has(binding.declaration)) return undefined;
|
|
506
|
+
seen.add(binding.declaration);
|
|
507
|
+
return entityFromExpression(binding.initializer, depth + 1, seen);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function scopedBindingIsCurrent(
|
|
511
|
+
identifier: ts.Identifier,
|
|
512
|
+
declaration: ts.VariableDeclaration | ts.ParameterDeclaration,
|
|
513
|
+
): boolean {
|
|
514
|
+
return nearestScopedVariableDeclaration(identifier) === declaration
|
|
515
|
+
&& !nonVariableBindingWins(identifier, declaration);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function unboundEntityName(identifier: ts.Identifier): string | undefined {
|
|
519
|
+
return hasScopedVariableDeclaration(identifier)
|
|
520
|
+
|| hasScopedNonVariableDeclaration(identifier)
|
|
521
|
+
? undefined
|
|
522
|
+
: identifier.text;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function entityFromIdentifier(
|
|
526
|
+
expr: ts.Identifier,
|
|
527
|
+
depth: number,
|
|
528
|
+
seen: Set<ts.Node>,
|
|
529
|
+
): string | undefined {
|
|
530
|
+
const binding = resolveBinding(expr, expr);
|
|
531
|
+
const destructured = resolveDestructuredBinding(expr, expr);
|
|
532
|
+
if (destructured && destructuredBindingWins(destructured, binding, expr)) {
|
|
533
|
+
return scopedBindingIsCurrent(expr, destructured.declaration)
|
|
534
|
+
? entityFromDestructuredBinding(destructured, expr)
|
|
535
|
+
: undefined;
|
|
536
|
+
}
|
|
537
|
+
if (!binding.declaration) return unboundEntityName(expr);
|
|
538
|
+
if (!scopedBindingIsCurrent(expr, binding.declaration)) return undefined;
|
|
539
|
+
return entityFromSimpleBinding(binding, depth, seen);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function literalEntity(expr: ts.Expression): string | undefined {
|
|
543
|
+
if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr))
|
|
544
|
+
return expr.text;
|
|
545
|
+
if (!ts.isElementAccessExpression(expr) || !expr.argumentExpression)
|
|
546
|
+
return undefined;
|
|
547
|
+
return ts.isStringLiteral(expr.argumentExpression)
|
|
548
|
+
|| ts.isNoSubstitutionTemplateLiteral(expr.argumentExpression)
|
|
549
|
+
? expr.argumentExpression.text
|
|
550
|
+
: undefined;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function propertyEntity(expr: ts.Expression): string | undefined {
|
|
554
|
+
if (!ts.isPropertyAccessExpression(expr)) return undefined;
|
|
555
|
+
if (expr.expression.kind === ts.SyntaxKind.ThisKeyword) return undefined;
|
|
556
|
+
return isEntitiesBase(expr.expression) ? expr.name.text : undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function entityFromExpression(
|
|
560
|
+
expr: ts.Expression | undefined,
|
|
561
|
+
depth = 0,
|
|
562
|
+
seen = new Set<ts.Node>(),
|
|
563
|
+
): string | undefined {
|
|
564
|
+
if (!expr || depth >= maxAliasDepth) return undefined;
|
|
565
|
+
const value = unwrapQueryExpression(expr);
|
|
566
|
+
const literal = literalEntity(value);
|
|
567
|
+
if (literal !== undefined) return literal;
|
|
568
|
+
if (ts.isIdentifier(value)) return entityFromIdentifier(value, depth, seen);
|
|
569
|
+
return propertyEntity(value);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function queryAliasInitializer(
|
|
573
|
+
identifier: ts.Identifier,
|
|
574
|
+
initializers: Map<string, ts.Expression>,
|
|
575
|
+
seen: Set<string>,
|
|
576
|
+
): ts.Expression | undefined {
|
|
577
|
+
const initializer = initializers.get(identifier.text);
|
|
578
|
+
if (!initializer || seen.has(identifier.text)) return undefined;
|
|
579
|
+
const binding = resolveBinding(identifier, identifier);
|
|
580
|
+
if (!binding.declaration || !binding.immutable || binding.initializer !== initializer
|
|
581
|
+
|| nearestScopedVariableDeclaration(identifier) !== binding.declaration
|
|
582
|
+
|| nonVariableBindingWins(identifier, binding.declaration)) return undefined;
|
|
583
|
+
seen.add(identifier.text);
|
|
584
|
+
return initializer;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function queryEntityFromCall(
|
|
588
|
+
call: ts.CallExpression,
|
|
589
|
+
initializers: Map<string, ts.Expression>,
|
|
590
|
+
seenInitializers: Set<string>,
|
|
591
|
+
): string | undefined {
|
|
592
|
+
const name = expressionName(call.expression);
|
|
593
|
+
if (name === 'cds.run')
|
|
594
|
+
return queryEntityFromAst(call.arguments[0], initializers, seenInitializers);
|
|
595
|
+
if (isCapQueryBuilderRootName(name))
|
|
596
|
+
return entityFromExpression(call.arguments[0]);
|
|
597
|
+
const receiver = ts.isPropertyAccessExpression(call.expression)
|
|
598
|
+
? call.expression.expression
|
|
599
|
+
: undefined;
|
|
600
|
+
return receiver
|
|
601
|
+
? queryEntityFromAst(receiver, initializers, seenInitializers)
|
|
602
|
+
: undefined;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export function queryEntityFromAst(
|
|
606
|
+
expr: ts.Expression,
|
|
607
|
+
initializers = new Map<string, ts.Expression>(),
|
|
608
|
+
seenInitializers = new Set<string>(),
|
|
609
|
+
): string | undefined {
|
|
610
|
+
const unwrapped = unwrapQueryExpression(expr);
|
|
611
|
+
if (ts.isIdentifier(unwrapped)) {
|
|
612
|
+
const initializer = queryAliasInitializer(
|
|
613
|
+
unwrapped, initializers, seenInitializers,
|
|
614
|
+
);
|
|
615
|
+
return initializer
|
|
616
|
+
? queryEntityFromAst(initializer, initializers, seenInitializers)
|
|
617
|
+
: undefined;
|
|
618
|
+
}
|
|
619
|
+
return ts.isCallExpression(unwrapped)
|
|
620
|
+
? queryEntityFromCall(unwrapped, initializers, seenInitializers)
|
|
621
|
+
: undefined;
|
|
622
|
+
}
|