fcis 0.1.0
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/.plans/001-fcis-analyzer.md +832 -0
- package/.plans/002-fcis-analyzer-improvements.md +205 -0
- package/README.md +272 -0
- package/TECHNICAL.md +386 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1836 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +709 -0
- package/dist/index.js +1845 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
- package/pnpm-workspace.yaml +0 -0
- package/src/analyzer.ts +266 -0
- package/src/classification/classifier.ts +156 -0
- package/src/classification/derive-status.ts +171 -0
- package/src/classification/quality-scorer.ts +481 -0
- package/src/cli.ts +286 -0
- package/src/detection/detect-markers.ts +480 -0
- package/src/detection/markers.ts +332 -0
- package/src/extraction/extract-functions.ts +570 -0
- package/src/extraction/extractor.ts +188 -0
- package/src/index.ts +111 -0
- package/src/reporting/report-console.ts +416 -0
- package/src/reporting/report-json.ts +232 -0
- package/src/scoring/scorer.ts +504 -0
- package/src/types.ts +248 -0
- package/tests/classifier.test.ts +480 -0
- package/tests/derive-status.test.ts +464 -0
- package/tests/detect-markers.test.ts +639 -0
- package/tests/extractor.test.ts +155 -0
- package/tests/integration.test.ts +706 -0
- package/tests/quality-scorer.test.ts +650 -0
- package/tests/scorer.test.ts +768 -0
- package/tsconfig.json +34 -0
- package/tsup.config.ts +17 -0
- package/vendor/ts-morph/.editorconfig +10 -0
- package/vendor/ts-morph/.gitattributes +11 -0
- package/vendor/ts-morph/.github/CODE_OF_CONDUCT.md +77 -0
- package/vendor/ts-morph/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- package/vendor/ts-morph/.github/ISSUE_TEMPLATE/custom.md +4 -0
- package/vendor/ts-morph/.github/ISSUE_TEMPLATE/feature_request.md +18 -0
- package/vendor/ts-morph/.github/workflows/ci.yml +50 -0
- package/vendor/ts-morph/.github/workflows/publish.yml +53 -0
- package/vendor/ts-morph/.vscode/settings.json +10 -0
- package/vendor/ts-morph/CONTRIBUTING.md +23 -0
- package/vendor/ts-morph/DEVELOPMENT.md +32 -0
- package/vendor/ts-morph/LICENSE +21 -0
- package/vendor/ts-morph/deno.json +8 -0
- package/vendor/ts-morph/deno.lock +1233 -0
- package/vendor/ts-morph/docs/CNAME +1 -0
- package/vendor/ts-morph/docs/Gemfile +2 -0
- package/vendor/ts-morph/docs/_config.yml +5 -0
- package/vendor/ts-morph/docs/_layouts/default.html +159 -0
- package/vendor/ts-morph/docs/_script-templates/main.ts +116 -0
- package/vendor/ts-morph/docs/assets/css/style.scss +212 -0
- package/vendor/ts-morph/docs/details/ambient.md +38 -0
- package/vendor/ts-morph/docs/details/async.md +31 -0
- package/vendor/ts-morph/docs/details/classes.md +314 -0
- package/vendor/ts-morph/docs/details/comment-ranges.md +7 -0
- package/vendor/ts-morph/docs/details/comments.md +122 -0
- package/vendor/ts-morph/docs/details/decorators.md +119 -0
- package/vendor/ts-morph/docs/details/documentation.md +73 -0
- package/vendor/ts-morph/docs/details/enums.md +117 -0
- package/vendor/ts-morph/docs/details/exports.md +308 -0
- package/vendor/ts-morph/docs/details/expressions.md +46 -0
- package/vendor/ts-morph/docs/details/functions.md +150 -0
- package/vendor/ts-morph/docs/details/generators.md +27 -0
- package/vendor/ts-morph/docs/details/identifiers.md +79 -0
- package/vendor/ts-morph/docs/details/imports.md +191 -0
- package/vendor/ts-morph/docs/details/index.md +52 -0
- package/vendor/ts-morph/docs/details/initializers.md +40 -0
- package/vendor/ts-morph/docs/details/interfaces.md +218 -0
- package/vendor/ts-morph/docs/details/literals.md +20 -0
- package/vendor/ts-morph/docs/details/modifiers.md +38 -0
- package/vendor/ts-morph/docs/details/modules.md +113 -0
- package/vendor/ts-morph/docs/details/namespaces.md +7 -0
- package/vendor/ts-morph/docs/details/object-literal-expressions.md +106 -0
- package/vendor/ts-morph/docs/details/parameters.md +64 -0
- package/vendor/ts-morph/docs/details/signatures.md +41 -0
- package/vendor/ts-morph/docs/details/source-files.md +292 -0
- package/vendor/ts-morph/docs/details/type-aliases.md +34 -0
- package/vendor/ts-morph/docs/details/type-parameters.md +72 -0
- package/vendor/ts-morph/docs/details/types.md +254 -0
- package/vendor/ts-morph/docs/details/variables.md +110 -0
- package/vendor/ts-morph/docs/emitting.md +151 -0
- package/vendor/ts-morph/docs/index.md +25 -0
- package/vendor/ts-morph/docs/manipulation/code-writer.md +20 -0
- package/vendor/ts-morph/docs/manipulation/formatting.md +76 -0
- package/vendor/ts-morph/docs/manipulation/index.md +136 -0
- package/vendor/ts-morph/docs/manipulation/order.md +14 -0
- package/vendor/ts-morph/docs/manipulation/performance.md +222 -0
- package/vendor/ts-morph/docs/manipulation/removing.md +31 -0
- package/vendor/ts-morph/docs/manipulation/renaming.md +106 -0
- package/vendor/ts-morph/docs/manipulation/settings.md +76 -0
- package/vendor/ts-morph/docs/manipulation/structures.md +117 -0
- package/vendor/ts-morph/docs/manipulation/transforms.md +84 -0
- package/vendor/ts-morph/docs/metrics/performance.json +4 -0
- package/vendor/ts-morph/docs/navigation/ambient-modules.md +22 -0
- package/vendor/ts-morph/docs/navigation/compiler-nodes.md +82 -0
- package/vendor/ts-morph/docs/navigation/directories.md +287 -0
- package/vendor/ts-morph/docs/navigation/example.md +50 -0
- package/vendor/ts-morph/docs/navigation/finding-references.md +53 -0
- package/vendor/ts-morph/docs/navigation/getting-source-files.md +59 -0
- package/vendor/ts-morph/docs/navigation/images/getChildrenVsForEachChild.gif +0 -0
- package/vendor/ts-morph/docs/navigation/index.md +94 -0
- package/vendor/ts-morph/docs/navigation/language-service.md +23 -0
- package/vendor/ts-morph/docs/navigation/program.md +25 -0
- package/vendor/ts-morph/docs/navigation/type-checker.md +33 -0
- package/vendor/ts-morph/docs/setup/adding-source-files.md +145 -0
- package/vendor/ts-morph/docs/setup/ast-viewers.md +46 -0
- package/vendor/ts-morph/docs/setup/diagnostics.md +109 -0
- package/vendor/ts-morph/docs/setup/file-system.md +106 -0
- package/vendor/ts-morph/docs/setup/images/atom-ast.png +0 -0
- package/vendor/ts-morph/docs/setup/images/atom-ast_small.png +0 -0
- package/vendor/ts-morph/docs/setup/images/atom-command-palette.png +0 -0
- package/vendor/ts-morph/docs/setup/images/atom-file.png +0 -0
- package/vendor/ts-morph/docs/setup/images/ts-ast-viewer.png +0 -0
- package/vendor/ts-morph/docs/setup/index.md +94 -0
- package/vendor/ts-morph/docs/utilities.md +55 -0
- package/vendor/ts-morph/dprint.json +23 -0
- package/vendor/ts-morph/package.json +30 -0
- package/vendor/ts-morph/packages/bootstrap/LICENSE +21 -0
- package/vendor/ts-morph/packages/bootstrap/lib/ts-morph-bootstrap.d.ts +397 -0
- package/vendor/ts-morph/packages/bootstrap/package.json +46 -0
- package/vendor/ts-morph/packages/bootstrap/readme.md +200 -0
- package/vendor/ts-morph/packages/common/LICENSE +21 -0
- package/vendor/ts-morph/packages/common/lib/ts-morph-common.d.ts +1082 -0
- package/vendor/ts-morph/packages/common/lib/typescript.d.ts +11439 -0
- package/vendor/ts-morph/packages/common/package.json +65 -0
- package/vendor/ts-morph/packages/common/readme.md +5 -0
- package/vendor/ts-morph/packages/scripts/changeTypeScriptVersion.ts +28 -0
- package/vendor/ts-morph/packages/scripts/createDeclarationProject.ts +47 -0
- package/vendor/ts-morph/packages/scripts/deps.ts +2 -0
- package/vendor/ts-morph/packages/scripts/execScript.ts +31 -0
- package/vendor/ts-morph/packages/scripts/folders.ts +11 -0
- package/vendor/ts-morph/packages/scripts/getDevCompilerVersions.ts +19 -0
- package/vendor/ts-morph/packages/scripts/mod.ts +7 -0
- package/vendor/ts-morph/packages/scripts/utils/Memoize.ts +36 -0
- package/vendor/ts-morph/packages/scripts/utils/forEachTypeText.ts +23 -0
- package/vendor/ts-morph/packages/scripts/utils/makeConstructorsPrivate.ts +26 -0
- package/vendor/ts-morph/packages/scripts/utils/mod.ts +4 -0
- package/vendor/ts-morph/packages/scripts/utils/printDiagnostics.ts +10 -0
- package/vendor/ts-morph/packages/ts-morph/LICENSE +21 -0
- package/vendor/ts-morph/packages/ts-morph/lib/ts-morph.d.ts +11198 -0
- package/vendor/ts-morph/packages/ts-morph/package.json +78 -0
- package/vendor/ts-morph/packages/ts-morph/readme.md +111 -0
- package/vendor/ts-morph/readme.md +14 -0
- package/vendor/ts-morph/rfcs/README.md +13 -0
- package/vendor/ts-morph/rfcs/RFC-0001 - Inserting Into Statements Handling Comments.md +181 -0
- package/vendor/ts-morph/tsconfig.common.json +17 -0
- package/vitest.config.ts +16 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker Detection - Pure Core
|
|
3
|
+
*
|
|
4
|
+
* Detects impurity markers in extracted functions. This is a PURE module -
|
|
5
|
+
* all functions take data in and return data out with no I/O.
|
|
6
|
+
*
|
|
7
|
+
* Marker categories:
|
|
8
|
+
* - Async/Await: await expressions indicate I/O suspension
|
|
9
|
+
* - Database: Prisma/db calls
|
|
10
|
+
* - Network: fetch, axios, http calls
|
|
11
|
+
* - File System: fs operations
|
|
12
|
+
* - Environment: process.env access
|
|
13
|
+
* - Logging: console, logger calls
|
|
14
|
+
* - Queue/Events: enqueue, emit calls
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
DetectionContext,
|
|
19
|
+
ExtractedFunction,
|
|
20
|
+
ImpurityMarker,
|
|
21
|
+
} from '../types.js'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Database operations that indicate impurity
|
|
25
|
+
*/
|
|
26
|
+
const DATABASE_OPERATIONS = new Set([
|
|
27
|
+
'findFirst',
|
|
28
|
+
'findMany',
|
|
29
|
+
'findUnique',
|
|
30
|
+
'findUniqueOrThrow',
|
|
31
|
+
'findFirstOrThrow',
|
|
32
|
+
'create',
|
|
33
|
+
'createMany',
|
|
34
|
+
'update',
|
|
35
|
+
'updateMany',
|
|
36
|
+
'delete',
|
|
37
|
+
'deleteMany',
|
|
38
|
+
'upsert',
|
|
39
|
+
'aggregate',
|
|
40
|
+
'count',
|
|
41
|
+
'groupBy',
|
|
42
|
+
'$transaction',
|
|
43
|
+
'$queryRaw',
|
|
44
|
+
'$executeRaw',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Telemetry/analytics patterns
|
|
49
|
+
*/
|
|
50
|
+
const TELEMETRY_PATTERNS = [
|
|
51
|
+
/^track[A-Z]/,
|
|
52
|
+
/^analytics\./,
|
|
53
|
+
/^segment\./,
|
|
54
|
+
/^mixpanel\./,
|
|
55
|
+
/^amplitude\./,
|
|
56
|
+
/^posthog\./,
|
|
57
|
+
/^gtag\(/,
|
|
58
|
+
/^ga\(/,
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detect all impurity markers in a function
|
|
63
|
+
*
|
|
64
|
+
* @param fn - The extracted function to analyze
|
|
65
|
+
* @param context - Detection context including imports
|
|
66
|
+
* @returns Array of detected impurity markers
|
|
67
|
+
*/
|
|
68
|
+
export function detectMarkers(
|
|
69
|
+
fn: ExtractedFunction,
|
|
70
|
+
context: DetectionContext,
|
|
71
|
+
): ImpurityMarker[] {
|
|
72
|
+
const markers: ImpurityMarker[] = []
|
|
73
|
+
|
|
74
|
+
// Detect await expressions
|
|
75
|
+
detectAwaitMarkers(fn, markers)
|
|
76
|
+
|
|
77
|
+
// Detect database calls
|
|
78
|
+
detectDatabaseMarkers(fn, markers)
|
|
79
|
+
|
|
80
|
+
// Detect network calls
|
|
81
|
+
detectNetworkMarkers(fn, context, markers)
|
|
82
|
+
|
|
83
|
+
// Detect file system usage
|
|
84
|
+
detectFileSystemMarkers(fn, context, markers)
|
|
85
|
+
|
|
86
|
+
// Detect environment access
|
|
87
|
+
detectEnvMarkers(fn, markers)
|
|
88
|
+
|
|
89
|
+
// Detect logging
|
|
90
|
+
detectLoggingMarkers(fn, context, markers)
|
|
91
|
+
|
|
92
|
+
// Detect telemetry
|
|
93
|
+
detectTelemetryMarkers(fn, markers)
|
|
94
|
+
|
|
95
|
+
// Detect queue operations
|
|
96
|
+
detectQueueMarkers(fn, markers)
|
|
97
|
+
|
|
98
|
+
// Detect event emissions
|
|
99
|
+
detectEventMarkers(fn, markers)
|
|
100
|
+
|
|
101
|
+
return markers
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Detect await expressions
|
|
106
|
+
*/
|
|
107
|
+
function detectAwaitMarkers(
|
|
108
|
+
fn: ExtractedFunction,
|
|
109
|
+
markers: ImpurityMarker[],
|
|
110
|
+
): void {
|
|
111
|
+
if (fn.hasAwait) {
|
|
112
|
+
// Find awaited call sites for details
|
|
113
|
+
const awaitedCalls = fn.callSites.filter(cs => cs.isAwaited)
|
|
114
|
+
if (awaitedCalls.length > 0) {
|
|
115
|
+
for (const call of awaitedCalls) {
|
|
116
|
+
markers.push({
|
|
117
|
+
type: 'await-expression',
|
|
118
|
+
detail: `await ${call.expression}`,
|
|
119
|
+
line: call.line,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
// Await on something other than a call (e.g., await promise variable)
|
|
124
|
+
markers.push({
|
|
125
|
+
type: 'await-expression',
|
|
126
|
+
detail: 'await expression',
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Detect database calls (Prisma pattern: db.<entity>.<operation>)
|
|
134
|
+
*/
|
|
135
|
+
function detectDatabaseMarkers(
|
|
136
|
+
fn: ExtractedFunction,
|
|
137
|
+
markers: ImpurityMarker[],
|
|
138
|
+
): void {
|
|
139
|
+
for (const callSite of fn.callSites) {
|
|
140
|
+
if (isDatabaseCall(callSite.expression)) {
|
|
141
|
+
markers.push({
|
|
142
|
+
type: 'database-call',
|
|
143
|
+
detail: callSite.expression,
|
|
144
|
+
line: callSite.line,
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check if an expression is a database call
|
|
152
|
+
*/
|
|
153
|
+
function isDatabaseCall(expression: string): boolean {
|
|
154
|
+
// Match patterns like: db.user.findFirst, prisma.user.create, ctx.db.user.findMany
|
|
155
|
+
const parts = expression.split('.')
|
|
156
|
+
|
|
157
|
+
// Need at least 3 parts: db.entity.operation
|
|
158
|
+
if (parts.length < 3) {
|
|
159
|
+
// Check for direct prisma operations like prisma.$transaction
|
|
160
|
+
if (parts.length === 2) {
|
|
161
|
+
const [base, operation] = parts
|
|
162
|
+
if (
|
|
163
|
+
(base === 'db' || base === 'prisma') &&
|
|
164
|
+
operation &&
|
|
165
|
+
DATABASE_OPERATIONS.has(operation)
|
|
166
|
+
) {
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check if any part is 'db' or 'prisma' and the last part is a database operation
|
|
174
|
+
const lastPart = parts[parts.length - 1]
|
|
175
|
+
if (!lastPart || !DATABASE_OPERATIONS.has(lastPart)) {
|
|
176
|
+
return false
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check for db or prisma in the chain
|
|
180
|
+
return parts.some(
|
|
181
|
+
part => part === 'db' || part === 'prisma' || part === 'database',
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Detect network/HTTP calls
|
|
187
|
+
*
|
|
188
|
+
* Note: We only detect actual call sites, not file-level imports.
|
|
189
|
+
* A function is only marked with network markers if it actually calls
|
|
190
|
+
* fetch, axios, or other HTTP client functions.
|
|
191
|
+
*/
|
|
192
|
+
function detectNetworkMarkers(
|
|
193
|
+
fn: ExtractedFunction,
|
|
194
|
+
_context: DetectionContext,
|
|
195
|
+
markers: ImpurityMarker[],
|
|
196
|
+
): void {
|
|
197
|
+
// Check for fetch calls
|
|
198
|
+
for (const callSite of fn.callSites) {
|
|
199
|
+
if (
|
|
200
|
+
callSite.expression === 'fetch' ||
|
|
201
|
+
callSite.expression.endsWith('.fetch')
|
|
202
|
+
) {
|
|
203
|
+
markers.push({
|
|
204
|
+
type: 'network-fetch',
|
|
205
|
+
detail: callSite.expression,
|
|
206
|
+
line: callSite.line,
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check for axios calls
|
|
211
|
+
if (
|
|
212
|
+
callSite.expression.startsWith('axios') ||
|
|
213
|
+
callSite.expression === 'axios'
|
|
214
|
+
) {
|
|
215
|
+
markers.push({
|
|
216
|
+
type: 'network-http',
|
|
217
|
+
detail: callSite.expression,
|
|
218
|
+
line: callSite.line,
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Detect file system usage
|
|
226
|
+
*
|
|
227
|
+
* Note: We only detect actual call sites, not file-level imports.
|
|
228
|
+
* A function is only marked with fs markers if it actually calls
|
|
229
|
+
* fs functions like readFile, writeFile, etc.
|
|
230
|
+
*/
|
|
231
|
+
function detectFileSystemMarkers(
|
|
232
|
+
fn: ExtractedFunction,
|
|
233
|
+
_context: DetectionContext,
|
|
234
|
+
markers: ImpurityMarker[],
|
|
235
|
+
): void {
|
|
236
|
+
// Check for fs calls in the function
|
|
237
|
+
for (const callSite of fn.callSites) {
|
|
238
|
+
if (isFsCall(callSite.expression)) {
|
|
239
|
+
markers.push({
|
|
240
|
+
type: 'fs-call',
|
|
241
|
+
detail: callSite.expression,
|
|
242
|
+
line: callSite.line,
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if an expression is a file system call
|
|
250
|
+
*/
|
|
251
|
+
function isFsCall(expression: string): boolean {
|
|
252
|
+
const parts = expression.split('.')
|
|
253
|
+
return parts.some(
|
|
254
|
+
part => part === 'fs' || part === 'readFile' || part === 'writeFile',
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Detect environment variable access
|
|
260
|
+
*/
|
|
261
|
+
function detectEnvMarkers(
|
|
262
|
+
fn: ExtractedFunction,
|
|
263
|
+
markers: ImpurityMarker[],
|
|
264
|
+
): void {
|
|
265
|
+
for (const chain of fn.propertyAccessChains) {
|
|
266
|
+
if (chain.startsWith('process.env')) {
|
|
267
|
+
markers.push({
|
|
268
|
+
type: 'env-access',
|
|
269
|
+
detail: chain,
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Detect logging calls
|
|
277
|
+
*
|
|
278
|
+
* Note: We only detect actual call sites, not file-level imports.
|
|
279
|
+
* A function is only marked with logging markers if it actually calls
|
|
280
|
+
* console.log, logger.info, log(), etc.
|
|
281
|
+
*/
|
|
282
|
+
function detectLoggingMarkers(
|
|
283
|
+
fn: ExtractedFunction,
|
|
284
|
+
_context: DetectionContext,
|
|
285
|
+
markers: ImpurityMarker[],
|
|
286
|
+
): void {
|
|
287
|
+
for (const callSite of fn.callSites) {
|
|
288
|
+
// Console logging
|
|
289
|
+
if (isConsoleLog(callSite.expression)) {
|
|
290
|
+
markers.push({
|
|
291
|
+
type: 'console-log',
|
|
292
|
+
detail: callSite.expression,
|
|
293
|
+
line: callSite.line,
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Logger calls
|
|
298
|
+
if (isLoggerCall(callSite.expression)) {
|
|
299
|
+
markers.push({
|
|
300
|
+
type: 'logging',
|
|
301
|
+
detail: callSite.expression,
|
|
302
|
+
line: callSite.line,
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Check if an expression is a console.log call
|
|
310
|
+
*/
|
|
311
|
+
function isConsoleLog(expression: string): boolean {
|
|
312
|
+
return (
|
|
313
|
+
expression === 'console.log' ||
|
|
314
|
+
expression === 'console.error' ||
|
|
315
|
+
expression === 'console.warn' ||
|
|
316
|
+
expression === 'console.info' ||
|
|
317
|
+
expression === 'console.debug'
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Check if an expression is a logger call
|
|
323
|
+
*/
|
|
324
|
+
function isLoggerCall(expression: string): boolean {
|
|
325
|
+
const parts = expression.split('.')
|
|
326
|
+
if (parts.length < 2) {
|
|
327
|
+
return expression === 'log'
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const base = parts[0]
|
|
331
|
+
const method = parts[parts.length - 1]
|
|
332
|
+
|
|
333
|
+
if (base === 'logger' || base === 'log') {
|
|
334
|
+
return true
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Check for common logger methods
|
|
338
|
+
const logMethods = new Set([
|
|
339
|
+
'info',
|
|
340
|
+
'warn',
|
|
341
|
+
'error',
|
|
342
|
+
'debug',
|
|
343
|
+
'trace',
|
|
344
|
+
'fatal',
|
|
345
|
+
])
|
|
346
|
+
return (
|
|
347
|
+
(parts.includes('logger') || parts.includes('log')) &&
|
|
348
|
+
method !== undefined &&
|
|
349
|
+
logMethods.has(method)
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Detect telemetry/analytics calls
|
|
355
|
+
*/
|
|
356
|
+
function detectTelemetryMarkers(
|
|
357
|
+
fn: ExtractedFunction,
|
|
358
|
+
markers: ImpurityMarker[],
|
|
359
|
+
): void {
|
|
360
|
+
for (const callSite of fn.callSites) {
|
|
361
|
+
if (isTelemetryCall(callSite.expression)) {
|
|
362
|
+
markers.push({
|
|
363
|
+
type: 'telemetry',
|
|
364
|
+
detail: callSite.expression,
|
|
365
|
+
line: callSite.line,
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Check if an expression is a telemetry call
|
|
373
|
+
*/
|
|
374
|
+
function isTelemetryCall(expression: string): boolean {
|
|
375
|
+
return TELEMETRY_PATTERNS.some(pattern => pattern.test(expression))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Detect queue operations
|
|
380
|
+
*/
|
|
381
|
+
function detectQueueMarkers(
|
|
382
|
+
fn: ExtractedFunction,
|
|
383
|
+
markers: ImpurityMarker[],
|
|
384
|
+
): void {
|
|
385
|
+
for (const callSite of fn.callSites) {
|
|
386
|
+
if (isQueueCall(callSite.expression)) {
|
|
387
|
+
markers.push({
|
|
388
|
+
type: 'queue-enqueue',
|
|
389
|
+
detail: callSite.expression,
|
|
390
|
+
line: callSite.line,
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Check if an expression is a queue operation
|
|
398
|
+
*/
|
|
399
|
+
function isQueueCall(expression: string): boolean {
|
|
400
|
+
return (
|
|
401
|
+
expression.endsWith('.enqueue') ||
|
|
402
|
+
expression.endsWith('.add') ||
|
|
403
|
+
expression.endsWith('.publish') ||
|
|
404
|
+
expression.includes('queue.') ||
|
|
405
|
+
expression.includes('Queue.')
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Detect event emissions
|
|
411
|
+
*/
|
|
412
|
+
function detectEventMarkers(
|
|
413
|
+
fn: ExtractedFunction,
|
|
414
|
+
markers: ImpurityMarker[],
|
|
415
|
+
): void {
|
|
416
|
+
for (const callSite of fn.callSites) {
|
|
417
|
+
if (isEventEmit(callSite.expression)) {
|
|
418
|
+
markers.push({
|
|
419
|
+
type: 'event-emit',
|
|
420
|
+
detail: callSite.expression,
|
|
421
|
+
line: callSite.line,
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Check if an expression is an event emission
|
|
429
|
+
*/
|
|
430
|
+
function isEventEmit(expression: string): boolean {
|
|
431
|
+
return (
|
|
432
|
+
expression.endsWith('.emit') ||
|
|
433
|
+
expression.endsWith('.dispatch') ||
|
|
434
|
+
expression.endsWith('.trigger') ||
|
|
435
|
+
expression.endsWith('.fire')
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Create detection context from file imports
|
|
441
|
+
*/
|
|
442
|
+
export function createDetectionContext(
|
|
443
|
+
imports: import('../types.js').FileImports,
|
|
444
|
+
): DetectionContext {
|
|
445
|
+
// Identify imports from .pure files
|
|
446
|
+
const pureFileImports = new Set<string>()
|
|
447
|
+
|
|
448
|
+
for (const imp of imports.imports) {
|
|
449
|
+
if (
|
|
450
|
+
imp.moduleSpecifier.endsWith('.pure') ||
|
|
451
|
+
imp.moduleSpecifier.includes('.pure/')
|
|
452
|
+
) {
|
|
453
|
+
pureFileImports.add(imp.moduleSpecifier)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
imports,
|
|
459
|
+
pureFileImports,
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get the names of functions imported from .pure files
|
|
465
|
+
*/
|
|
466
|
+
export function getPureImportedFunctions(
|
|
467
|
+
context: DetectionContext,
|
|
468
|
+
): Set<string> {
|
|
469
|
+
const pureFunctions = new Set<string>()
|
|
470
|
+
|
|
471
|
+
for (const imp of context.imports.imports) {
|
|
472
|
+
if (context.pureFileImports.has(imp.moduleSpecifier)) {
|
|
473
|
+
for (const name of imp.namedImports) {
|
|
474
|
+
pureFunctions.add(name)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return pureFunctions
|
|
480
|
+
}
|