@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.
- package/CHANGELOG.md +14 -0
- package/README.md +20 -8
- package/TECHNICAL-NOTE.md +27 -0
- package/dist/{chunk-TOVX4WYH.js → chunk-AEM4JY22.js} +12676 -6714
- package/dist/chunk-AEM4JY22.js.map +1 -0
- package/dist/cli.js +2325 -413
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +61 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/db/000-call-fact-repository.ts +475 -342
- package/src/db/001-fact-lifecycle.ts +60 -30
- package/src/db/002-fact-json-inventory.ts +169 -0
- package/src/db/003-current-fact-semantics.ts +698 -0
- package/src/db/004-package-target-invalidation.ts +173 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +443 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +207 -0
- package/src/db/009-binding-fact-semantics.ts +347 -0
- package/src/db/010-package-symbol-surface-semantics.ts +320 -0
- package/src/db/011-symbol-call-semantics.ts +144 -0
- package/src/db/012-binding-reference-proof.ts +264 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +113 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/repository-indexer.ts +72 -11
- package/src/indexer/workspace-indexer.ts +123 -32
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +2 -0
- package/src/linker/005-odata-path-structure.ts +371 -0
- package/src/linker/cross-repo-linker.ts +2 -1
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- package/src/parsers/002-symbol-import-bindings.ts +516 -0
- package/src/parsers/003-package-public-surface.ts +661 -0
- package/src/parsers/004-fact-identity.ts +108 -0
- package/src/parsers/005-event-subscription-facts.ts +281 -0
- package/src/parsers/006-binding-identity.ts +343 -0
- package/src/parsers/007-source-fact-reconciliation.ts +105 -0
- package/src/parsers/008-package-surface-publication.ts +82 -0
- package/src/parsers/009-symbol-call-facts.ts +528 -0
- package/src/parsers/010-package-public-surface-analysis.ts +352 -0
- package/src/parsers/011-binding-lexical-scope.ts +583 -0
- package/src/parsers/012-package-fact-contract.ts +306 -0
- package/src/parsers/013-executable-body-eligibility.ts +35 -0
- package/src/parsers/014-service-binding-helper-flow.ts +240 -0
- package/src/parsers/015-service-binding-collector.ts +693 -0
- package/src/parsers/016-local-symbol-reference.ts +261 -0
- package/src/parsers/017-symbol-derived-contexts.ts +268 -0
- package/src/parsers/018-package-commonjs-syntax.ts +142 -0
- package/src/parsers/019-binding-assignment-targets.ts +76 -0
- package/src/parsers/020-stable-local-value.ts +217 -0
- package/src/parsers/021-binding-visibility.ts +152 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +19 -6
- package/src/parsers/package-json-parser.ts +45 -3
- package/src/parsers/service-binding-parser-helpers.ts +86 -15
- package/src/parsers/service-binding-parser.ts +147 -597
- package/src/parsers/symbol-parser.ts +482 -353
- package/src/trace/002-trace-diagnostics.ts +36 -1
- package/src/trace/016-compact-projector.ts +16 -17
- package/src/trace/020-compact-field-projection.ts +48 -37
- package/src/trace/021-compact-decision-normalization.ts +105 -0
- package/src/trace/022-trace-fact-preflight.ts +21 -0
- package/src/trace/023-nested-event-scopes.ts +23 -0
- package/src/trace/024-compact-observation-decision.ts +76 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +335 -0
- package/src/trace/027-trace-scope-execution.ts +566 -0
- package/src/trace/028-trace-operation-execution.ts +336 -0
- package/src/trace/029-trace-start-implementation.ts +172 -0
- package/src/trace/trace-engine.ts +122 -624
- package/src/types.ts +56 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- 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
|
+
}
|