@saptools/service-flow 0.1.66 → 0.1.68

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 (80) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +20 -8
  3. package/TECHNICAL-NOTE.md +27 -0
  4. package/dist/{chunk-TOVX4WYH.js → chunk-AEM4JY22.js} +12676 -6714
  5. package/dist/chunk-AEM4JY22.js.map +1 -0
  6. package/dist/cli.js +2325 -413
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +61 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/003-doctor-package-resolution.ts +68 -0
  12. package/src/cli/doctor.ts +25 -14
  13. package/src/db/000-call-fact-repository.ts +475 -342
  14. package/src/db/001-fact-lifecycle.ts +60 -30
  15. package/src/db/002-fact-json-inventory.ts +169 -0
  16. package/src/db/003-current-fact-semantics.ts +698 -0
  17. package/src/db/004-package-target-invalidation.ts +173 -0
  18. package/src/db/005-schema-structure.ts +201 -0
  19. package/src/db/006-relative-symbol-resolution.ts +443 -0
  20. package/src/db/007-package-fact-semantics.ts +573 -0
  21. package/src/db/008-relative-fact-semantics.ts +207 -0
  22. package/src/db/009-binding-fact-semantics.ts +347 -0
  23. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  24. package/src/db/011-symbol-call-semantics.ts +144 -0
  25. package/src/db/012-binding-reference-proof.ts +264 -0
  26. package/src/db/migrations.ts +16 -3
  27. package/src/db/repositories.ts +113 -6
  28. package/src/db/schema.ts +4 -2
  29. package/src/index.ts +12 -0
  30. package/src/indexer/repository-indexer.ts +72 -11
  31. package/src/indexer/workspace-indexer.ts +123 -32
  32. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  33. package/src/linker/004-event-subscription-handler-linker.ts +2 -0
  34. package/src/linker/005-odata-path-structure.ts +371 -0
  35. package/src/linker/cross-repo-linker.ts +2 -1
  36. package/src/linker/odata-path-normalizer.ts +273 -180
  37. package/src/linker/service-resolver.ts +197 -77
  38. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  39. package/src/parsers/003-package-public-surface.ts +661 -0
  40. package/src/parsers/004-fact-identity.ts +108 -0
  41. package/src/parsers/005-event-subscription-facts.ts +281 -0
  42. package/src/parsers/006-binding-identity.ts +343 -0
  43. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  44. package/src/parsers/008-package-surface-publication.ts +82 -0
  45. package/src/parsers/009-symbol-call-facts.ts +528 -0
  46. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  47. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  48. package/src/parsers/012-package-fact-contract.ts +306 -0
  49. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  50. package/src/parsers/014-service-binding-helper-flow.ts +240 -0
  51. package/src/parsers/015-service-binding-collector.ts +693 -0
  52. package/src/parsers/016-local-symbol-reference.ts +261 -0
  53. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  54. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  55. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  56. package/src/parsers/020-stable-local-value.ts +217 -0
  57. package/src/parsers/021-binding-visibility.ts +152 -0
  58. package/src/parsers/operation-path-analysis.ts +6 -1
  59. package/src/parsers/outbound-call-parser.ts +19 -6
  60. package/src/parsers/package-json-parser.ts +45 -3
  61. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  62. package/src/parsers/service-binding-parser.ts +147 -597
  63. package/src/parsers/symbol-parser.ts +482 -353
  64. package/src/trace/002-trace-diagnostics.ts +36 -1
  65. package/src/trace/016-compact-projector.ts +16 -17
  66. package/src/trace/020-compact-field-projection.ts +48 -37
  67. package/src/trace/021-compact-decision-normalization.ts +105 -0
  68. package/src/trace/022-trace-fact-preflight.ts +21 -0
  69. package/src/trace/023-nested-event-scopes.ts +23 -0
  70. package/src/trace/024-compact-observation-decision.ts +76 -0
  71. package/src/trace/025-trace-implementation-scope.ts +123 -0
  72. package/src/trace/026-trace-start-scope.ts +335 -0
  73. package/src/trace/027-trace-scope-execution.ts +566 -0
  74. package/src/trace/028-trace-operation-execution.ts +336 -0
  75. package/src/trace/029-trace-start-implementation.ts +172 -0
  76. package/src/trace/trace-engine.ts +122 -624
  77. package/src/types.ts +56 -0
  78. package/src/utils/001-placeholders.ts +188 -10
  79. package/src/version.ts +1 -1
  80. package/dist/chunk-TOVX4WYH.js.map +0 -1
package/src/types.ts CHANGED
@@ -160,8 +160,63 @@ 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
+ lexicalScopeChain?: LexicalScopeFact[];
208
+ bindingScopeIndex?: number;
209
+ scopeChainTotal: number;
210
+ scopeChainShown: number;
211
+ scopeChainOmitted: number;
212
+ reason?: ServiceBindingReferenceReason;
213
+ }
214
+ export type HandlerReferenceStatus =
215
+ | 'role_required'
216
+ | 'unsupported_inline'
217
+ | 'unsupported_wrapper'
218
+ | 'unsupported_reference_shape'
219
+ | 'missing_argument';
165
220
  export interface OutboundCallFact {
166
221
  callType: CallType;
167
222
  sourceSymbolQualifiedName?: string;
@@ -178,6 +233,7 @@ export interface OutboundCallFact {
178
233
  sourceLine: number;
179
234
  callSiteStartOffset?: number;
180
235
  callSiteEndOffset?: number;
236
+ serviceBindingReference?: ServiceBindingReference;
181
237
  confidence: number;
182
238
  unresolvedReason?: string;
183
239
  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.68-facts.1';