@saptools/service-flow 0.1.67 → 0.1.69

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +27 -9
  3. package/TECHNICAL-NOTE.md +36 -0
  4. package/dist/chunk-3N3B5KHV.js +19596 -0
  5. package/dist/chunk-3N3B5KHV.js.map +1 -0
  6. package/dist/cli.js +2645 -521
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +67 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/001-index-summary.ts +22 -0
  12. package/src/cli/003-doctor-package-resolution.ts +68 -0
  13. package/src/cli/doctor.ts +25 -14
  14. package/src/cli.ts +151 -87
  15. package/src/db/000-call-fact-repository.ts +499 -340
  16. package/src/db/001-fact-lifecycle.ts +60 -30
  17. package/src/db/002-fact-json-inventory.ts +169 -0
  18. package/src/db/003-current-fact-semantics.ts +699 -0
  19. package/src/db/004-package-target-invalidation.ts +183 -0
  20. package/src/db/005-schema-structure.ts +201 -0
  21. package/src/db/006-relative-symbol-resolution.ts +464 -0
  22. package/src/db/007-package-fact-semantics.ts +573 -0
  23. package/src/db/008-relative-fact-semantics.ts +210 -0
  24. package/src/db/009-binding-fact-semantics.ts +352 -0
  25. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  26. package/src/db/011-symbol-call-semantics.ts +144 -0
  27. package/src/db/012-binding-reference-proof.ts +268 -0
  28. package/src/db/013-index-publication-failure.ts +91 -0
  29. package/src/db/014-binding-helper-provenance.ts +17 -0
  30. package/src/db/migrations.ts +16 -3
  31. package/src/db/repositories.ts +130 -6
  32. package/src/db/schema.ts +4 -2
  33. package/src/index.ts +12 -0
  34. package/src/indexer/cds-extension-resolver.ts +27 -3
  35. package/src/indexer/repository-indexer.ts +135 -13
  36. package/src/indexer/workspace-indexer.ts +237 -34
  37. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  38. package/src/linker/004-event-subscription-handler-linker.ts +34 -11
  39. package/src/linker/005-odata-path-structure.ts +371 -0
  40. package/src/linker/006-event-template-link.ts +72 -0
  41. package/src/linker/007-call-edge-insertion.ts +568 -0
  42. package/src/linker/cross-repo-linker.ts +4 -166
  43. package/src/linker/odata-path-normalizer.ts +273 -180
  44. package/src/linker/service-resolver.ts +197 -77
  45. package/src/parsers/000-direct-query-execution.ts +11 -0
  46. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  47. package/src/parsers/003-package-public-surface.ts +661 -0
  48. package/src/parsers/004-fact-identity.ts +108 -0
  49. package/src/parsers/005-event-subscription-facts.ts +281 -0
  50. package/src/parsers/006-binding-identity.ts +348 -0
  51. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  52. package/src/parsers/008-package-surface-publication.ts +82 -0
  53. package/src/parsers/009-symbol-call-facts.ts +528 -0
  54. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  55. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  56. package/src/parsers/012-package-fact-contract.ts +306 -0
  57. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  58. package/src/parsers/014-service-binding-helper-flow.ts +306 -0
  59. package/src/parsers/015-service-binding-collector.ts +693 -0
  60. package/src/parsers/016-local-symbol-reference.ts +261 -0
  61. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  62. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  63. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  64. package/src/parsers/020-stable-local-value.ts +217 -0
  65. package/src/parsers/021-binding-visibility.ts +168 -0
  66. package/src/parsers/022-outbound-expression-analysis.ts +700 -0
  67. package/src/parsers/023-outbound-call-classifier.ts +692 -0
  68. package/src/parsers/operation-path-analysis.ts +6 -1
  69. package/src/parsers/outbound-call-parser.ts +162 -512
  70. package/src/parsers/package-json-parser.ts +45 -3
  71. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  72. package/src/parsers/service-binding-parser.ts +147 -597
  73. package/src/parsers/symbol-parser.ts +513 -352
  74. package/src/trace/002-trace-diagnostics.ts +36 -1
  75. package/src/trace/007-implementation-start-diagnostic.ts +1 -0
  76. package/src/trace/011-event-subscriber-traversal.ts +100 -8
  77. package/src/trace/013-trace-root-scopes.ts +2 -3
  78. package/src/trace/014-compact-contract.ts +6 -0
  79. package/src/trace/015-trace-edge-recorder.ts +61 -4
  80. package/src/trace/016-compact-projector.ts +15 -17
  81. package/src/trace/019-trace-edge-semantics.ts +6 -10
  82. package/src/trace/020-compact-field-projection.ts +122 -38
  83. package/src/trace/021-compact-decision-normalization.ts +171 -0
  84. package/src/trace/022-trace-fact-preflight.ts +21 -0
  85. package/src/trace/023-nested-event-scopes.ts +23 -0
  86. package/src/trace/024-compact-observation-decision.ts +81 -0
  87. package/src/trace/025-trace-implementation-scope.ts +123 -0
  88. package/src/trace/026-trace-start-scope.ts +336 -0
  89. package/src/trace/027-trace-scope-execution.ts +566 -0
  90. package/src/trace/028-trace-operation-execution.ts +336 -0
  91. package/src/trace/029-trace-start-implementation.ts +172 -0
  92. package/src/trace/030-event-runtime-resolution.ts +151 -0
  93. package/src/trace/031-local-call-expansion.ts +37 -0
  94. package/src/trace/implementation-hints.ts +9 -6
  95. package/src/trace/selectors.ts +1 -0
  96. package/src/trace/trace-engine.ts +122 -624
  97. package/src/types.ts +57 -0
  98. package/src/utils/001-placeholders.ts +188 -10
  99. package/src/version.ts +1 -1
  100. package/dist/chunk-ZQABU7MR.js +0 -12151
  101. package/dist/chunk-ZQABU7MR.js.map +0 -1
package/src/types.ts CHANGED
@@ -160,8 +160,64 @@ export interface ServiceBindingFact {
160
160
  placeholders: string[];
161
161
  sourceFile: string;
162
162
  sourceLine: number;
163
+ bindingSiteStartOffset?: number;
164
+ bindingSiteEndOffset?: number;
165
+ sourceSymbolQualifiedName?: string;
166
+ ownerResolution?: ServiceBindingOwnerResolution;
163
167
  helperChain?: Array<Record<string, unknown>>;
164
168
  }
169
+ export type ServiceBindingOwnerResolution =
170
+ | 'owned_exact'
171
+ | 'ownerless_file_scope'
172
+ | 'legacy_unknown';
173
+ export type ServiceBindingReferenceStatus =
174
+ | 'resolved_exact'
175
+ | 'ambiguous'
176
+ | 'unresolved'
177
+ | 'not_applicable';
178
+ export type ServiceBindingReferenceReason =
179
+ | 'binding_not_found'
180
+ | 'binding_declared_after_call'
181
+ | 'binding_scope_ambiguous'
182
+ | 'scope_chain_limit_exceeded'
183
+ | 'unsupported_reaching_assignment'
184
+ | 'unsupported_var_binding'
185
+ | 'binding_flow_unsupported';
186
+ export interface LexicalScopeFact {
187
+ kind: 'source_file'
188
+ | 'module_block'
189
+ | 'function'
190
+ | 'class'
191
+ | 'loop'
192
+ | 'case_block'
193
+ | 'block'
194
+ | 'catch';
195
+ startOffset: number;
196
+ endOffset: number;
197
+ }
198
+ export interface ServiceBindingReference {
199
+ status: ServiceBindingReferenceStatus;
200
+ variableName?: string;
201
+ bindingSourceFile?: string;
202
+ bindingSiteStartOffset?: number;
203
+ bindingSiteEndOffset?: number;
204
+ resolutionStrategy?: 'lexical_declaration'
205
+ | 'lexical_alias_declaration'
206
+ | 'deterministic_reaching_assignment'
207
+ | 'single_hop_helper_return';
208
+ lexicalScopeChain?: LexicalScopeFact[];
209
+ bindingScopeIndex?: number;
210
+ scopeChainTotal: number;
211
+ scopeChainShown: number;
212
+ scopeChainOmitted: number;
213
+ reason?: ServiceBindingReferenceReason;
214
+ }
215
+ export type HandlerReferenceStatus =
216
+ | 'role_required'
217
+ | 'unsupported_inline'
218
+ | 'unsupported_wrapper'
219
+ | 'unsupported_reference_shape'
220
+ | 'missing_argument';
165
221
  export interface OutboundCallFact {
166
222
  callType: CallType;
167
223
  sourceSymbolQualifiedName?: string;
@@ -178,6 +234,7 @@ export interface OutboundCallFact {
178
234
  sourceLine: number;
179
235
  callSiteStartOffset?: number;
180
236
  callSiteEndOffset?: number;
237
+ serviceBindingReference?: ServiceBindingReference;
181
238
  confidence: number;
182
239
  unresolvedReason?: string;
183
240
  evidence?: Record<string, unknown>;
@@ -1,26 +1,80 @@
1
+ import ts from 'typescript';
2
+
1
3
  export interface PlaceholderSpan {
2
4
  readonly start: number;
3
5
  readonly end: number;
4
6
  readonly key: string;
5
7
  }
6
8
 
7
- export function scanPlaceholders(value: string | undefined): readonly PlaceholderSpan[] {
9
+ export type PlaceholderScanReason =
10
+ | 'placeholder_expression_empty'
11
+ | 'placeholder_delimiter_mismatch'
12
+ | 'placeholder_token_unterminated'
13
+ | 'placeholder_unterminated';
14
+
15
+ export interface PlaceholderScanResult {
16
+ readonly status: 'balanced' | 'malformed';
17
+ readonly spans: readonly PlaceholderSpan[];
18
+ readonly reason?: PlaceholderScanReason;
19
+ readonly failureOffset?: number;
20
+ }
21
+
22
+ type Delimiter = 'brace' | 'bracket' | 'paren' | 'template';
23
+
24
+ interface PlaceholderMatch {
25
+ readonly span?: PlaceholderSpan;
26
+ readonly reason?: PlaceholderScanReason;
27
+ readonly failureOffset?: number;
28
+ }
29
+
30
+ const expressionEndTokens = new Set<ts.SyntaxKind>([
31
+ ts.SyntaxKind.Identifier,
32
+ ts.SyntaxKind.PrivateIdentifier,
33
+ ts.SyntaxKind.NumericLiteral,
34
+ ts.SyntaxKind.BigIntLiteral,
35
+ ts.SyntaxKind.StringLiteral,
36
+ ts.SyntaxKind.RegularExpressionLiteral,
37
+ ts.SyntaxKind.NoSubstitutionTemplateLiteral,
38
+ ts.SyntaxKind.TemplateTail,
39
+ ts.SyntaxKind.TrueKeyword,
40
+ ts.SyntaxKind.FalseKeyword,
41
+ ts.SyntaxKind.NullKeyword,
42
+ ts.SyntaxKind.ThisKeyword,
43
+ ts.SyntaxKind.SuperKeyword,
44
+ ts.SyntaxKind.CloseBraceToken,
45
+ ts.SyntaxKind.CloseBracketToken,
46
+ ts.SyntaxKind.CloseParenToken,
47
+ ts.SyntaxKind.PlusPlusToken,
48
+ ts.SyntaxKind.MinusMinusToken,
49
+ ]);
50
+
51
+ export function scanPlaceholderStructure(
52
+ value: string | undefined,
53
+ ): PlaceholderScanResult {
8
54
  const input = value ?? '';
9
55
  const spans: PlaceholderSpan[] = [];
10
56
  let cursor = 0;
11
57
  while (cursor < input.length) {
12
58
  const start = input.indexOf('${', cursor);
13
59
  if (start < 0) break;
14
- const closingBrace = input.indexOf('}', start + 2);
15
- if (closingBrace < 0) break;
16
- spans.push({
17
- start,
18
- end: closingBrace + 1,
19
- key: input.slice(start + 2, closingBrace),
20
- });
21
- cursor = closingBrace + 1;
60
+ const match = scanPlaceholderAt(input, start);
61
+ if (!match.span) {
62
+ return {
63
+ status: 'malformed', spans: [], reason: match.reason,
64
+ failureOffset: match.failureOffset,
65
+ };
66
+ }
67
+ spans.push(match.span);
68
+ cursor = match.span.end;
22
69
  }
23
- return spans;
70
+ return { status: 'balanced', spans };
71
+ }
72
+
73
+ export function scanPlaceholders(
74
+ value: string | undefined,
75
+ ): readonly PlaceholderSpan[] {
76
+ const result = scanPlaceholderStructure(value);
77
+ return result.status === 'balanced' ? result.spans : [];
24
78
  }
25
79
 
26
80
  export function extractPlaceholderKeys(value: string | undefined): string[] {
@@ -28,3 +82,127 @@ export function extractPlaceholderKeys(value: string | undefined): string[] {
28
82
  .map((span) => span.key.trim())
29
83
  .filter(Boolean);
30
84
  }
85
+
86
+ function scanPlaceholderAt(input: string, start: number): PlaceholderMatch {
87
+ const bodyOffset = start + 2;
88
+ if (input.indexOf('}', bodyOffset) < 0)
89
+ return { reason: 'placeholder_unterminated', failureOffset: start };
90
+ const scanner = ts.createScanner(
91
+ ts.ScriptTarget.Latest,
92
+ false,
93
+ ts.LanguageVariant.Standard,
94
+ input.slice(bodyOffset),
95
+ );
96
+ const delimiters: Delimiter[] = [];
97
+ let previousEndsExpression = false;
98
+ while (true) {
99
+ const scanned = scanToken(scanner, previousEndsExpression);
100
+ if (scanner.isUnterminated())
101
+ return failedMatch('placeholder_token_unterminated', bodyOffset, scanner);
102
+ if (scanned === ts.SyntaxKind.EndOfFileToken)
103
+ return failedMatch('placeholder_unterminated', bodyOffset, scanner);
104
+ if (isTrivia(scanned)) continue;
105
+ const result = consumeDelimiter(scanner, scanned, delimiters);
106
+ if (result.reason)
107
+ return failedMatch(result.reason, bodyOffset, scanner);
108
+ if (result.closed) {
109
+ const end = bodyOffset + scanner.getTextPos();
110
+ const key = input.slice(bodyOffset, end - 1);
111
+ return key.trim()
112
+ ? { span: { start, end, key } }
113
+ : { reason: 'placeholder_expression_empty', failureOffset: start };
114
+ }
115
+ previousEndsExpression = tokenEndsExpression(
116
+ result.token, previousEndsExpression,
117
+ );
118
+ }
119
+ }
120
+
121
+ function scanToken(
122
+ scanner: ts.Scanner,
123
+ previousEndsExpression: boolean,
124
+ ): ts.SyntaxKind {
125
+ const token = scanner.scan();
126
+ if ((token === ts.SyntaxKind.SlashToken
127
+ || token === ts.SyntaxKind.SlashEqualsToken)
128
+ && !previousEndsExpression)
129
+ return scanner.reScanSlashToken();
130
+ return token;
131
+ }
132
+
133
+ function consumeDelimiter(
134
+ scanner: ts.Scanner,
135
+ token: ts.SyntaxKind,
136
+ stack: Delimiter[],
137
+ ): { token: ts.SyntaxKind; closed?: boolean; reason?: PlaceholderScanReason } {
138
+ if (token === ts.SyntaxKind.TemplateHead) stack.push('template');
139
+ else if (token === ts.SyntaxKind.OpenBraceToken) stack.push('brace');
140
+ else if (token === ts.SyntaxKind.OpenBracketToken) stack.push('bracket');
141
+ else if (token === ts.SyntaxKind.OpenParenToken) stack.push('paren');
142
+ else if (token === ts.SyntaxKind.CloseBracketToken)
143
+ return closeDelimiter(token, stack, 'bracket');
144
+ else if (token === ts.SyntaxKind.CloseParenToken)
145
+ return closeDelimiter(token, stack, 'paren');
146
+ else if (token === ts.SyntaxKind.CloseBraceToken)
147
+ return closeBrace(scanner, stack);
148
+ return { token };
149
+ }
150
+
151
+ function closeDelimiter(
152
+ token: ts.SyntaxKind,
153
+ stack: Delimiter[],
154
+ expected: Delimiter,
155
+ ): { token: ts.SyntaxKind; reason?: PlaceholderScanReason } {
156
+ if (stack.at(-1) !== expected)
157
+ return { token, reason: 'placeholder_delimiter_mismatch' };
158
+ stack.pop();
159
+ return { token };
160
+ }
161
+
162
+ function closeBrace(
163
+ scanner: ts.Scanner,
164
+ stack: Delimiter[],
165
+ ): { token: ts.SyntaxKind; closed?: boolean; reason?: PlaceholderScanReason } {
166
+ const current = stack.at(-1);
167
+ if (!current) return { token: ts.SyntaxKind.CloseBraceToken, closed: true };
168
+ if (current === 'brace') {
169
+ stack.pop();
170
+ return { token: ts.SyntaxKind.CloseBraceToken };
171
+ }
172
+ if (current !== 'template')
173
+ return {
174
+ token: ts.SyntaxKind.CloseBraceToken,
175
+ reason: 'placeholder_delimiter_mismatch',
176
+ };
177
+ const token = scanner.reScanTemplateToken(false);
178
+ if (token === ts.SyntaxKind.TemplateTail) stack.pop();
179
+ if (token !== ts.SyntaxKind.TemplateTail
180
+ && token !== ts.SyntaxKind.TemplateMiddle)
181
+ return { token, reason: 'placeholder_token_unterminated' };
182
+ return { token };
183
+ }
184
+
185
+ function failedMatch(
186
+ reason: PlaceholderScanReason,
187
+ bodyOffset: number,
188
+ scanner: ts.Scanner,
189
+ ): PlaceholderMatch {
190
+ return {
191
+ reason,
192
+ failureOffset: bodyOffset + scanner.getTokenPos(),
193
+ };
194
+ }
195
+
196
+ function tokenEndsExpression(
197
+ token: ts.SyntaxKind,
198
+ previousEndsExpression: boolean,
199
+ ): boolean {
200
+ if (token === ts.SyntaxKind.ExclamationToken)
201
+ return previousEndsExpression;
202
+ return expressionEndTokens.has(token);
203
+ }
204
+
205
+ function isTrivia(token: ts.SyntaxKind): boolean {
206
+ return token >= ts.SyntaxKind.FirstTriviaToken
207
+ && token <= ts.SyntaxKind.LastTriviaToken;
208
+ }
package/src/version.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import packageJson from '../package.json' with { type: 'json' };
2
2
 
3
3
  export const VERSION = packageJson.version;
4
- export const ANALYZER_VERSION = '0.1.66-facts.1';
4
+ export const ANALYZER_VERSION = '0.1.69-facts.1';