@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
@@ -0,0 +1,261 @@
1
+ import ts from 'typescript';
2
+ import type { ExecutableSymbolFact } from '../types.js';
3
+ import {
4
+ lexicalIdentifierDeclaration,
5
+ lexicalIdentifierDeclarations,
6
+ } from './002-symbol-import-bindings.js';
7
+ import { stableLocalValueReference } from './020-stable-local-value.js';
8
+
9
+ export interface LocalSymbolTargetIdentity {
10
+ sourceFile: string;
11
+ qualifiedName: string;
12
+ startOffset: number;
13
+ endOffset: number;
14
+ }
15
+
16
+ function functionTargetNode(
17
+ declaration: ts.Identifier,
18
+ ): ts.Node | undefined {
19
+ const parent = declaration.parent;
20
+ if (ts.isFunctionDeclaration(parent)) return parent;
21
+ if (!ts.isVariableDeclaration(parent)) return undefined;
22
+ const initializer = parent.initializer;
23
+ return initializer
24
+ && (ts.isArrowFunction(initializer)
25
+ || ts.isFunctionExpression(initializer))
26
+ ? initializer
27
+ : undefined;
28
+ }
29
+
30
+ function declarationKey(identifier: ts.Identifier): string {
31
+ return `${identifier.getStart(identifier.getSourceFile())}:${identifier.getEnd()}`;
32
+ }
33
+
34
+ function unwrapAssignmentTarget(expression: ts.Expression): ts.Expression {
35
+ if (ts.isParenthesizedExpression(expression)
36
+ || ts.isAsExpression(expression)
37
+ || ts.isSatisfiesExpression(expression)
38
+ || ts.isTypeAssertionExpression(expression)
39
+ || ts.isNonNullExpression(expression))
40
+ return unwrapAssignmentTarget(expression.expression);
41
+ return expression;
42
+ }
43
+
44
+ function objectAssignmentTargets(
45
+ expression: ts.ObjectLiteralExpression,
46
+ ): ts.Identifier[] {
47
+ return expression.properties.flatMap((property) => {
48
+ if (ts.isShorthandPropertyAssignment(property)) return [property.name];
49
+ if (ts.isPropertyAssignment(property))
50
+ return assignmentTargets(property.initializer);
51
+ return ts.isSpreadAssignment(property)
52
+ ? assignmentTargets(property.expression) : [];
53
+ });
54
+ }
55
+
56
+ function assignmentTargets(expression: ts.Expression): ts.Identifier[] {
57
+ const target = unwrapAssignmentTarget(expression);
58
+ if (ts.isIdentifier(target)) return [target];
59
+ if (ts.isSpreadElement(target))
60
+ return assignmentTargets(target.expression);
61
+ if (ts.isBinaryExpression(target)
62
+ && target.operatorToken.kind === ts.SyntaxKind.EqualsToken)
63
+ return assignmentTargets(target.left);
64
+ if (ts.isArrayLiteralExpression(target))
65
+ return target.elements.flatMap((item) =>
66
+ ts.isExpression(item) ? assignmentTargets(item) : []);
67
+ return ts.isObjectLiteralExpression(target)
68
+ ? objectAssignmentTargets(target) : [];
69
+ }
70
+
71
+ function assignmentOperator(kind: ts.SyntaxKind): boolean {
72
+ return kind >= ts.SyntaxKind.FirstAssignment
73
+ && kind <= ts.SyntaxKind.LastAssignment;
74
+ }
75
+
76
+ function mutationUnary(
77
+ node: ts.PrefixUnaryExpression | ts.PostfixUnaryExpression,
78
+ ): boolean {
79
+ return node.operator === ts.SyntaxKind.PlusPlusToken
80
+ || node.operator === ts.SyntaxKind.MinusMinusToken;
81
+ }
82
+
83
+ type DeclarationMatcher = (identifier: ts.Identifier) => boolean;
84
+
85
+ function binaryWritesDeclaration(
86
+ node: ts.Node,
87
+ matches: DeclarationMatcher,
88
+ ): boolean {
89
+ if (!ts.isBinaryExpression(node)
90
+ || !assignmentOperator(node.operatorToken.kind)) return false;
91
+ return assignmentTargets(node.left).some(matches);
92
+ }
93
+
94
+ function unaryWritesDeclaration(
95
+ node: ts.Node,
96
+ matches: DeclarationMatcher,
97
+ ): boolean {
98
+ if (!ts.isPrefixUnaryExpression(node)
99
+ && !ts.isPostfixUnaryExpression(node)) return false;
100
+ return mutationUnary(node)
101
+ && ts.isIdentifier(node.operand)
102
+ && matches(node.operand);
103
+ }
104
+
105
+ function loopWritesDeclaration(
106
+ node: ts.Node,
107
+ matches: DeclarationMatcher,
108
+ ): boolean {
109
+ if (!ts.isForInStatement(node) && !ts.isForOfStatement(node)) return false;
110
+ return !ts.isVariableDeclarationList(node.initializer)
111
+ && assignmentTargets(node.initializer).some(matches);
112
+ }
113
+
114
+ function declarationWritten(
115
+ source: ts.SourceFile,
116
+ declarations: readonly ts.Identifier[],
117
+ ): boolean {
118
+ const keys = new Set(declarations.map(declarationKey));
119
+ let written = false;
120
+ const matches = (identifier: ts.Identifier): boolean =>
121
+ lexicalIdentifierDeclarations(identifier)
122
+ .some((item) => keys.has(declarationKey(item)));
123
+ const visit = (node: ts.Node): void => {
124
+ if (written) return;
125
+ written = binaryWritesDeclaration(node, matches)
126
+ || unaryWritesDeclaration(node, matches)
127
+ || loopWritesDeclaration(node, matches);
128
+ if (!written) ts.forEachChild(node, visit);
129
+ };
130
+ visit(source);
131
+ return written;
132
+ }
133
+
134
+ function singleImmutableDeclaration(
135
+ declaration: ts.Identifier | undefined,
136
+ ): boolean {
137
+ const parent = declaration?.parent;
138
+ if (parent && ts.isClassDeclaration(parent)) return true;
139
+ return Boolean(parent && ts.isVariableDeclaration(parent)
140
+ && ts.isVariableDeclarationList(parent.parent)
141
+ && (parent.parent.flags & ts.NodeFlags.Const) !== 0);
142
+ }
143
+
144
+ function immutableDeclarationSet(
145
+ declarations: readonly ts.Identifier[],
146
+ source: ts.SourceFile,
147
+ ): boolean {
148
+ if (declarations.length === 0
149
+ || declarationWritten(source, declarations)) return false;
150
+ if (declarations.every((item) => ts.isFunctionDeclaration(item.parent)))
151
+ return true;
152
+ if (declarations.length !== 1) return false;
153
+ return singleImmutableDeclaration(declarations[0]);
154
+ }
155
+
156
+ function propertyContainer(
157
+ declaration: ts.Identifier,
158
+ ): ts.Node | undefined {
159
+ const parent = declaration.parent;
160
+ if (ts.isClassDeclaration(parent)) return parent;
161
+ if (!ts.isVariableDeclaration(parent)) return undefined;
162
+ const initializer = parent.initializer;
163
+ return initializer
164
+ && (ts.isObjectLiteralExpression(initializer)
165
+ || ts.isClassExpression(initializer))
166
+ ? initializer
167
+ : undefined;
168
+ }
169
+
170
+ function identity(
171
+ symbol: ExecutableSymbolFact,
172
+ ): LocalSymbolTargetIdentity {
173
+ return {
174
+ sourceFile: symbol.sourceFile,
175
+ qualifiedName: symbol.qualifiedName,
176
+ startOffset: symbol.startOffset,
177
+ endOffset: symbol.endOffset,
178
+ };
179
+ }
180
+
181
+ function executableBody(symbol: ExecutableSymbolFact): boolean {
182
+ const value = symbol.importExportEvidence?.executableBodyEligibility;
183
+ return Boolean(value && typeof value === 'object'
184
+ && !Array.isArray(value)
185
+ && 'eligible' in value
186
+ && value.eligible === true);
187
+ }
188
+
189
+ function exactFunctionTarget(
190
+ declarations: readonly ts.Identifier[],
191
+ symbols: readonly ExecutableSymbolFact[],
192
+ source: ts.SourceFile,
193
+ ): LocalSymbolTargetIdentity | undefined {
194
+ if (!immutableDeclarationSet(declarations, source)) return undefined;
195
+ const spans = declarations.flatMap((declaration) => {
196
+ const target = functionTargetNode(declaration);
197
+ return target
198
+ ? [{ start: target.getStart(source), end: target.getEnd() }]
199
+ : [];
200
+ });
201
+ const matches = symbols.filter((symbol) =>
202
+ localFunctionMatches(symbol, spans, source.fileName));
203
+ return matches.length === 1 && matches[0]
204
+ ? identity(matches[0])
205
+ : undefined;
206
+ }
207
+
208
+ function localFunctionMatches(
209
+ symbol: ExecutableSymbolFact,
210
+ spans: readonly { start: number; end: number }[],
211
+ sourceFile: string,
212
+ ): boolean {
213
+ if (symbol.sourceFile !== sourceFile || !executableBody(symbol))
214
+ return false;
215
+ return spans.some((span) =>
216
+ symbol.startOffset === span.start && symbol.endOffset === span.end);
217
+ }
218
+
219
+ function exactPropertyTarget(
220
+ expression: ts.PropertyAccessExpression,
221
+ declaration: ts.Identifier,
222
+ symbols: readonly ExecutableSymbolFact[],
223
+ source: ts.SourceFile,
224
+ ): LocalSymbolTargetIdentity | undefined {
225
+ if (!immutableDeclarationSet([declaration], source)
226
+ || !stableLocalValueReference(source, declaration))
227
+ return undefined;
228
+ const container = propertyContainer(declaration);
229
+ if (!container) return undefined;
230
+ const qualifiedName = `${declaration.text}.${expression.name.text}`;
231
+ const start = container.getStart(source);
232
+ const end = container.getEnd();
233
+ const matches = symbols.filter((symbol) =>
234
+ symbol.sourceFile === source.fileName
235
+ && symbol.qualifiedName === qualifiedName
236
+ && symbol.startOffset >= start
237
+ && symbol.endOffset <= end
238
+ && executableBody(symbol));
239
+ return matches.length === 1 && matches[0]
240
+ ? identity(matches[0])
241
+ : undefined;
242
+ }
243
+
244
+ export function localSymbolTarget(
245
+ expression: ts.Expression,
246
+ source: ts.SourceFile,
247
+ symbols: readonly ExecutableSymbolFact[],
248
+ ): LocalSymbolTargetIdentity | undefined {
249
+ if (ts.isIdentifier(expression)) {
250
+ return exactFunctionTarget(
251
+ lexicalIdentifierDeclarations(expression), symbols, source,
252
+ );
253
+ }
254
+ if (!ts.isPropertyAccessExpression(expression)
255
+ || expression.questionDotToken
256
+ || !ts.isIdentifier(expression.expression)) return undefined;
257
+ const declaration = lexicalIdentifierDeclaration(expression.expression);
258
+ return declaration
259
+ ? exactPropertyTarget(expression, declaration, symbols, source)
260
+ : undefined;
261
+ }
@@ -0,0 +1,268 @@
1
+ import ts from 'typescript';
2
+ import {
3
+ symbolCallName,
4
+ type SymbolCallProxy,
5
+ type SymbolClassInstance,
6
+ } from './009-symbol-call-facts.js';
7
+ import {
8
+ identifierMatchesDeclaration,
9
+ symbolImportReference,
10
+ type SymbolImportBinding,
11
+ } from './002-symbol-import-bindings.js';
12
+
13
+ interface DerivedContextCollection {
14
+ source: ts.SourceFile;
15
+ importBindings: readonly SymbolImportBinding[];
16
+ declaredClasses: ReadonlySet<string>;
17
+ proxies: Map<string, SymbolCallProxy[]>;
18
+ instances: Map<string, SymbolClassInstance[]>;
19
+ }
20
+
21
+ const builtInConstructors = new Set([
22
+ 'Set', 'Map', 'WeakSet', 'WeakMap',
23
+ 'Date', 'RegExp', 'URL', 'URLSearchParams',
24
+ 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError',
25
+ 'TypeError', 'URIError', 'AggregateError',
26
+ 'ArrayBuffer', 'SharedArrayBuffer', 'DataView',
27
+ 'Int8Array', 'Uint8Array', 'Uint8ClampedArray', 'Int16Array',
28
+ 'Uint16Array', 'Int32Array', 'Uint32Array', 'Float32Array',
29
+ 'Float64Array', 'BigInt64Array', 'BigUint64Array',
30
+ 'Promise', 'AbortController',
31
+ ]);
32
+
33
+ function appendNamed<T>(
34
+ values: Map<string, T[]>,
35
+ name: string,
36
+ value: T,
37
+ ): void {
38
+ const matches = values.get(name) ?? [];
39
+ matches.push(value);
40
+ values.set(name, matches);
41
+ }
42
+
43
+ function assignmentOperator(kind: ts.SyntaxKind): boolean {
44
+ return kind >= ts.SyntaxKind.FirstAssignment
45
+ && kind <= ts.SyntaxKind.LastAssignment;
46
+ }
47
+
48
+ function mutationUnary(
49
+ node: ts.PrefixUnaryExpression | ts.PostfixUnaryExpression,
50
+ ): boolean {
51
+ return node.operator === ts.SyntaxKind.PlusPlusToken
52
+ || node.operator === ts.SyntaxKind.MinusMinusToken;
53
+ }
54
+
55
+ function receiverIdentifier(
56
+ expression: ts.Expression,
57
+ ): ts.Identifier | undefined {
58
+ let current = expression;
59
+ while (ts.isPropertyAccessExpression(current)
60
+ || ts.isElementAccessExpression(current))
61
+ current = current.expression;
62
+ return ts.isIdentifier(current) ? current : undefined;
63
+ }
64
+
65
+ function nodeWritesMember(
66
+ node: ts.Node,
67
+ matches: (expression: ts.Expression) => boolean,
68
+ ): boolean {
69
+ if (ts.isBinaryExpression(node))
70
+ return assignmentOperator(node.operatorToken.kind) && matches(node.left);
71
+ if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node))
72
+ return mutationUnary(node) && matches(node.operand);
73
+ return ts.isDeleteExpression(node) && matches(node.expression);
74
+ }
75
+
76
+ function memberWrite(
77
+ source: ts.SourceFile,
78
+ declaration: ts.Identifier,
79
+ ): boolean {
80
+ const start = declaration.getStart(source);
81
+ const end = declaration.getEnd();
82
+ const matches = (expression: ts.Expression): boolean => {
83
+ const receiver = receiverIdentifier(expression);
84
+ return Boolean(receiver && receiver !== expression
85
+ && identifierMatchesDeclaration(receiver, start, end));
86
+ };
87
+ let written = false;
88
+ const visit = (node: ts.Node): void => {
89
+ if (written) return;
90
+ written = nodeWritesMember(node, matches);
91
+ if (!written) ts.forEachChild(node, visit);
92
+ };
93
+ visit(source);
94
+ return written;
95
+ }
96
+
97
+ function stableVariable(
98
+ collection: DerivedContextCollection,
99
+ node: ts.VariableDeclaration,
100
+ ): node is ts.VariableDeclaration & { name: ts.Identifier } {
101
+ return ts.isIdentifier(node.name)
102
+ && ts.isVariableDeclarationList(node.parent)
103
+ && (node.parent.flags & ts.NodeFlags.Const) !== 0
104
+ && !memberWrite(collection.source, node.name);
105
+ }
106
+
107
+ function collectProxy(
108
+ collection: DerivedContextCollection,
109
+ node: ts.Node,
110
+ ): void {
111
+ if (!ts.isVariableDeclaration(node) || !stableVariable(collection, node)
112
+ || !node.initializer || !ts.isCallExpression(node.initializer)
113
+ || !ts.isPropertyAccessExpression(node.initializer.expression)) return;
114
+ const callee = symbolCallName(node.initializer.expression);
115
+ const binding = symbolImportReference(
116
+ node.initializer.expression, collection.importBindings,
117
+ );
118
+ if (!callee.member || !binding) return;
119
+ appendNamed(collection.proxies, node.name.text, {
120
+ importSource: binding.rawModuleSpecifier,
121
+ importBinding: binding,
122
+ factory: callee.expression,
123
+ variableName: node.name.text,
124
+ declarationStartOffset: node.name.getStart(collection.source),
125
+ declarationEndOffset: node.name.getEnd(),
126
+ });
127
+ }
128
+
129
+ function propertyContainer(
130
+ declaration: ts.Identifier | ts.PropertyName,
131
+ propertyName: string | undefined,
132
+ ): ts.ClassLikeDeclaration | undefined {
133
+ const property = propertyName && ts.isPropertyDeclaration(declaration.parent)
134
+ ? declaration.parent : undefined;
135
+ return property && ts.isClassLike(property.parent)
136
+ ? property.parent : undefined;
137
+ }
138
+
139
+ function eligibleClassName(
140
+ collection: DerivedContextCollection,
141
+ className: string,
142
+ imported: ReturnType<typeof symbolImportReference>,
143
+ ): boolean {
144
+ if (builtInConstructors.has(className)) return false;
145
+ return Boolean(imported || collection.declaredClasses.has(className));
146
+ }
147
+
148
+ function resolvedClassName(
149
+ className: string,
150
+ imported: ReturnType<typeof symbolImportReference>,
151
+ ): string {
152
+ const importedName = imported?.importedName;
153
+ return importedName && importedName !== 'default'
154
+ ? importedName : className;
155
+ }
156
+
157
+ function rememberInstance(
158
+ collection: DerivedContextCollection,
159
+ declaration: ts.Identifier | ts.PropertyName,
160
+ classExpression: ts.Identifier,
161
+ propertyName?: string,
162
+ ): void {
163
+ const className = classExpression.text;
164
+ const imported = symbolImportReference(
165
+ classExpression, collection.importBindings,
166
+ );
167
+ if (!eligibleClassName(collection, className, imported)) return;
168
+ const container = propertyContainer(declaration, propertyName);
169
+ const variableName = propertyName
170
+ ? `this.${propertyName}` : declaration.getText();
171
+ appendNamed(collection.instances, variableName, {
172
+ className: resolvedClassName(className, imported),
173
+ importSource: imported?.rawModuleSpecifier,
174
+ importBinding: imported,
175
+ propertyName,
176
+ declarationStartOffset: declaration.getStart(collection.source),
177
+ declarationEndOffset: declaration.getEnd(),
178
+ containerStartOffset: container?.getStart(collection.source),
179
+ containerEndOffset: container?.getEnd(),
180
+ });
181
+ }
182
+
183
+ function collectVariableInstance(
184
+ collection: DerivedContextCollection,
185
+ node: ts.Node,
186
+ ): void {
187
+ if (!ts.isVariableDeclaration(node) || !stableVariable(collection, node))
188
+ return;
189
+ const initializer = node.initializer;
190
+ if (initializer && ts.isNewExpression(initializer)
191
+ && ts.isIdentifier(initializer.expression))
192
+ rememberInstance(collection, node.name, initializer.expression);
193
+ }
194
+
195
+ function propertyName(name: ts.PropertyName): string | undefined {
196
+ return ts.isIdentifier(name) || ts.isStringLiteralLike(name)
197
+ || ts.isNumericLiteral(name) ? name.text : undefined;
198
+ }
199
+
200
+ function thisPropertyWrite(
201
+ expression: ts.Expression,
202
+ name: string,
203
+ ): boolean {
204
+ if (ts.isPropertyAccessExpression(expression))
205
+ return expression.expression.kind === ts.SyntaxKind.ThisKeyword
206
+ && expression.name.text === name;
207
+ return ts.isElementAccessExpression(expression)
208
+ && expression.expression.kind === ts.SyntaxKind.ThisKeyword
209
+ && Boolean(expression.argumentExpression
210
+ && ts.isStringLiteralLike(expression.argumentExpression)
211
+ && expression.argumentExpression.text === name);
212
+ }
213
+
214
+ function nodeWritesThisProperty(
215
+ node: ts.Node,
216
+ name: string,
217
+ ): boolean {
218
+ if (ts.isBinaryExpression(node))
219
+ return assignmentOperator(node.operatorToken.kind)
220
+ && thisPropertyWrite(node.left, name);
221
+ if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node))
222
+ return mutationUnary(node) && thisPropertyWrite(node.operand, name);
223
+ return ts.isDeleteExpression(node)
224
+ && thisPropertyWrite(node.expression, name);
225
+ }
226
+
227
+ function stableProperty(
228
+ node: ts.PropertyDeclaration,
229
+ name: string,
230
+ ): boolean {
231
+ const flags = ts.getCombinedModifierFlags(node);
232
+ if ((flags & (ts.ModifierFlags.Private | ts.ModifierFlags.Readonly)) === 0
233
+ || !ts.isClassLike(node.parent)) return false;
234
+ let written = false;
235
+ const visit = (child: ts.Node): void => {
236
+ if (written || child === node) return;
237
+ written = nodeWritesThisProperty(child, name);
238
+ if (!written) ts.forEachChild(child, visit);
239
+ };
240
+ visit(node.parent);
241
+ return !written;
242
+ }
243
+
244
+ function collectPropertyInstance(
245
+ collection: DerivedContextCollection,
246
+ node: ts.Node,
247
+ ): void {
248
+ if (!ts.isPropertyDeclaration(node)) return;
249
+ const initializer = node.initializer;
250
+ if (!initializer || !ts.isNewExpression(initializer)
251
+ || !ts.isIdentifier(initializer.expression)) return;
252
+ const name = propertyName(node.name);
253
+ if (name && stableProperty(node, name)) rememberInstance(
254
+ collection, node.name, initializer.expression, name,
255
+ );
256
+ }
257
+
258
+ export function collectDerivedSymbolContexts(
259
+ collection: DerivedContextCollection,
260
+ ): void {
261
+ const visit = (node: ts.Node): void => {
262
+ collectProxy(collection, node);
263
+ collectVariableInstance(collection, node);
264
+ collectPropertyInstance(collection, node);
265
+ ts.forEachChild(node, visit);
266
+ };
267
+ visit(collection.source);
268
+ }
@@ -0,0 +1,142 @@
1
+ import ts from 'typescript';
2
+ import {
3
+ lexicalIdentifierDeclarations,
4
+ } from './002-symbol-import-bindings.js';
5
+
6
+ interface AccessPath {
7
+ root: ts.Identifier;
8
+ members: Array<string | null>;
9
+ }
10
+
11
+ function unwrap(expression: ts.Expression): ts.Expression {
12
+ if (ts.isParenthesizedExpression(expression)
13
+ || ts.isAsExpression(expression)
14
+ || ts.isSatisfiesExpression(expression)
15
+ || ts.isTypeAssertionExpression(expression)
16
+ || ts.isNonNullExpression(expression))
17
+ return unwrap(expression.expression);
18
+ return expression;
19
+ }
20
+
21
+ function accessPath(expression: ts.Expression): AccessPath | undefined {
22
+ const value = unwrap(expression);
23
+ if (ts.isIdentifier(value)) return { root: value, members: [] };
24
+ if (ts.isPropertyAccessExpression(value)) {
25
+ const parent = accessPath(value.expression);
26
+ return parent
27
+ ? { root: parent.root, members: [...parent.members, value.name.text] }
28
+ : undefined;
29
+ }
30
+ if (!ts.isElementAccessExpression(value)) return undefined;
31
+ const parent = accessPath(value.expression);
32
+ if (!parent) return undefined;
33
+ const argument = value.argumentExpression;
34
+ const member = argument && (ts.isStringLiteralLike(argument)
35
+ || ts.isNumericLiteral(argument)) ? argument.text : null;
36
+ return { root: parent.root, members: [...parent.members, member] };
37
+ }
38
+
39
+ export function isUnshadowedCommonJsExportExpression(
40
+ expression: ts.Expression,
41
+ ): boolean {
42
+ const path = accessPath(expression);
43
+ if (!path || lexicalIdentifierDeclarations(path.root).length > 0)
44
+ return false;
45
+ if (path.root.text === 'exports') return true;
46
+ return path.root.text === 'module'
47
+ && path.members.length > 0
48
+ && (path.members[0] === 'exports' || path.members[0] === null);
49
+ }
50
+
51
+ function writeTargetContainsExport(expression: ts.Expression): boolean {
52
+ const target = unwrap(expression);
53
+ if (isUnshadowedCommonJsExportExpression(target)) return true;
54
+ if (ts.isSpreadElement(target))
55
+ return writeTargetContainsExport(target.expression);
56
+ if (ts.isBinaryExpression(target)
57
+ && target.operatorToken.kind === ts.SyntaxKind.EqualsToken)
58
+ return writeTargetContainsExport(target.left);
59
+ if (ts.isArrayLiteralExpression(target))
60
+ return target.elements.some((item) =>
61
+ !ts.isOmittedExpression(item) && writeTargetContainsExport(item));
62
+ if (!ts.isObjectLiteralExpression(target)) return false;
63
+ return target.properties.some((property) => {
64
+ if (ts.isShorthandPropertyAssignment(property))
65
+ return isUnshadowedCommonJsExportExpression(property.name);
66
+ if (ts.isSpreadAssignment(property))
67
+ return writeTargetContainsExport(property.expression);
68
+ return ts.isPropertyAssignment(property)
69
+ && writeTargetContainsExport(property.initializer);
70
+ });
71
+ }
72
+
73
+ function isAssignmentOperator(kind: ts.SyntaxKind): boolean {
74
+ return kind >= ts.SyntaxKind.FirstAssignment
75
+ && kind <= ts.SyntaxKind.LastAssignment;
76
+ }
77
+
78
+ export function unsupportedCommonJsMutation(
79
+ statement: ts.ExpressionStatement,
80
+ ): boolean {
81
+ const expression = statement.expression;
82
+ if (ts.isBinaryExpression(expression)
83
+ && isAssignmentOperator(expression.operatorToken.kind))
84
+ return expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken
85
+ ? writeTargetContainsExport(expression.left)
86
+ : !isUnshadowedCommonJsExportExpression(expression.left)
87
+ && writeTargetContainsExport(expression.left);
88
+ if (ts.isDeleteExpression(expression))
89
+ return writeTargetContainsExport(expression.expression);
90
+ if (ts.isPrefixUnaryExpression(expression)
91
+ || ts.isPostfixUnaryExpression(expression))
92
+ return writeTargetContainsExport(expression.operand);
93
+ return ts.isCallExpression(expression)
94
+ && expression.arguments.some(writeTargetContainsExport);
95
+ }
96
+
97
+ function topLevelDirectAssignment(node: ts.BinaryExpression): boolean {
98
+ return node.operatorToken.kind === ts.SyntaxKind.EqualsToken
99
+ && isUnshadowedCommonJsExportExpression(node.left)
100
+ && ts.isExpressionStatement(node.parent)
101
+ && ts.isSourceFile(node.parent.parent);
102
+ }
103
+
104
+ function unsupportedBinaryNode(node: ts.Node): boolean {
105
+ return ts.isBinaryExpression(node)
106
+ && isAssignmentOperator(node.operatorToken.kind)
107
+ && writeTargetContainsExport(node.left)
108
+ && !topLevelDirectAssignment(node);
109
+ }
110
+
111
+ function unsupportedMutationNode(node: ts.Node): boolean {
112
+ if (ts.isDeleteExpression(node))
113
+ return writeTargetContainsExport(node.expression);
114
+ if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node))
115
+ return writeTargetContainsExport(node.operand);
116
+ return ts.isCallExpression(node)
117
+ && node.arguments.some(writeTargetContainsExport);
118
+ }
119
+
120
+ function unsupportedLoopNode(node: ts.Node): boolean {
121
+ if (!ts.isForInStatement(node) && !ts.isForOfStatement(node))
122
+ return false;
123
+ return !ts.isVariableDeclarationList(node.initializer)
124
+ && writeTargetContainsExport(node.initializer);
125
+ }
126
+
127
+ function unsupportedCommonJsNode(node: ts.Node): boolean {
128
+ return unsupportedBinaryNode(node)
129
+ || unsupportedMutationNode(node)
130
+ || unsupportedLoopNode(node);
131
+ }
132
+
133
+ export function hasNestedCommonJsMutation(source: ts.SourceFile): boolean {
134
+ let unsupported = false;
135
+ const visit = (node: ts.Node): void => {
136
+ if (unsupported) return;
137
+ unsupported = unsupportedCommonJsNode(node);
138
+ if (!unsupported) ts.forEachChild(node, visit);
139
+ };
140
+ visit(source);
141
+ return unsupported;
142
+ }