code-auditor-mcp 3.4.11 → 3.4.13
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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +119 -1
- package/dist/analyzers/__verify_self_probe.d.ts +2 -0
- package/dist/analyzers/__verify_self_probe.d.ts.map +1 -0
- package/dist/analyzers/__verify_self_probe.js +5 -0
- package/dist/analyzers/__verify_self_probe.js.map +1 -0
- package/dist/analyzers/analyzerUtils.d.ts +34 -1
- package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
- package/dist/analyzers/analyzerUtils.js +61 -22
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +18 -23
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +198 -186
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +78 -55
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +244 -243
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +27 -37
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +314 -275
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +17 -46
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +620 -523
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts +7 -2
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +259 -198
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.js +59 -46
- package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts +10 -0
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.js +10 -0
- package/dist/analyzers/orm/adapterRegistry.js.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.d.ts +22 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.js +42 -44
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.d.ts +30 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.js +30 -0
- package/dist/analyzers/orm/prismaAdapter.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +72 -2
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +462 -338
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts +11 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +188 -125
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +118 -1
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +1717 -104
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +54 -0
- package/dist/analyzers/ruleTiming.d.ts.map +1 -0
- package/dist/analyzers/ruleTiming.js +71 -0
- package/dist/analyzers/ruleTiming.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +13 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +362 -283
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +26 -68
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +430 -391
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +24 -186
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +1081 -1061
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -49
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +414 -340
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +48 -5
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +262 -134
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +23 -212
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +139 -1518
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +78 -79
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +613 -472
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +307 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js +965 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -0
- package/dist/analyzers/universal/schema/config.d.ts +31 -0
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/config.js +57 -0
- package/dist/analyzers/universal/schema/config.js.map +1 -0
- package/dist/analyzers/universal/schema/discovery.d.ts +167 -0
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/discovery.js +616 -0
- package/dist/analyzers/universal/schema/discovery.js.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts +27 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.js +368 -0
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -0
- package/dist/analyzers/universal/schema/migrations.d.ts +90 -0
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/migrations.js +238 -0
- package/dist/analyzers/universal/schema/migrations.js.map +1 -0
- package/dist/analyzers/universal/schema/types.d.ts +131 -0
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/types.js +10 -0
- package/dist/analyzers/universal/schema/types.js.map +1 -0
- package/dist/analyzers/universal/schema/violations.d.ts +44 -0
- package/dist/analyzers/universal/schema/violations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/violations.js +54 -0
- package/dist/analyzers/universal/schema/violations.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +101 -39
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +348 -40
- package/dist/cli.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +9 -7
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +12 -3
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +15 -5
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +75 -0
- package/dist/config/effectiveConfig.d.ts.map +1 -0
- package/dist/config/effectiveConfig.js +213 -0
- package/dist/config/effectiveConfig.js.map +1 -0
- package/dist/config/pathProfiles.d.ts +7 -4
- package/dist/config/pathProfiles.d.ts.map +1 -1
- package/dist/config/pathProfiles.js +8 -10
- package/dist/config/pathProfiles.js.map +1 -1
- package/dist/config/thresholdRationales.d.ts +45 -0
- package/dist/config/thresholdRationales.d.ts.map +1 -0
- package/dist/config/thresholdRationales.js +67 -0
- package/dist/config/thresholdRationales.js.map +1 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +6 -2
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/enforcement/diffGate.d.ts +55 -0
- package/dist/enforcement/diffGate.d.ts.map +1 -0
- package/dist/enforcement/diffGate.js +136 -0
- package/dist/enforcement/diffGate.js.map +1 -0
- package/dist/enforcement/gate.d.ts +36 -0
- package/dist/enforcement/gate.d.ts.map +1 -0
- package/dist/enforcement/gate.js +72 -0
- package/dist/enforcement/gate.js.map +1 -0
- package/dist/enforcement/suppressions.d.ts +41 -0
- package/dist/enforcement/suppressions.d.ts.map +1 -0
- package/dist/enforcement/suppressions.js +170 -0
- package/dist/enforcement/suppressions.js.map +1 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +4 -1
- package/dist/fingerprint.js.map +1 -1
- package/dist/grammars/tree-sitter-scss.wasm +0 -0
- package/dist/invariants/ruleEngine.d.ts +3 -4
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +2 -2
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +14 -0
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +93 -55
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/LanguageRegistry.d.ts +8 -0
- package/dist/languages/LanguageRegistry.d.ts.map +1 -1
- package/dist/languages/LanguageRegistry.js +8 -0
- package/dist/languages/LanguageRegistry.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +82 -60
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +404 -349
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts +51 -6
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +127 -88
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +36 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +89 -35
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts +66 -23
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +553 -474
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +38 -9
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +111 -74
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.d.ts +18 -0
- package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/converter.js +18 -0
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.d.ts +39 -2
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +95 -12
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/types.d.ts +67 -2
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +253 -62
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +1461 -791
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +1 -0
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +1 -0
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/pipeline.d.ts +30 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +527 -203
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +5 -12
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +175 -100
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/presets/presets.d.ts +54 -0
- package/dist/presets/presets.d.ts.map +1 -0
- package/dist/presets/presets.js +181 -0
- package/dist/presets/presets.js.map +1 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +0 -2
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/ruleAliases.d.ts +67 -0
- package/dist/ruleAliases.d.ts.map +1 -0
- package/dist/ruleAliases.js +79 -0
- package/dist/ruleAliases.js.map +1 -0
- package/dist/styles/cssAstExtractor.d.ts +20 -5
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +247 -9
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.js +13 -4
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +8 -6
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +61 -15
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +13 -1
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/types.d.ts +201 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +7 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.json +1 -1
- package/plugin/scripts/hook-audit.sh +25 -7
- package/plugin/skills/code-auditor/SKILL.md +5 -5
|
@@ -65,7 +65,26 @@ export const DB_TYPES = new Set([
|
|
|
65
65
|
'SqliteDatabase',
|
|
66
66
|
'BetterSQLite3Database',
|
|
67
67
|
]);
|
|
68
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* DB call methods — the fixed API surface (language-invariant).
|
|
70
|
+
*
|
|
71
|
+
* Spec 33 Item 11 FP category 5: the bare-identifier hybrid fallback in
|
|
72
|
+
* `isDBProvenanced` treated any `get(...)` / `each(...)` / `values(...)` call
|
|
73
|
+
* as a DB query, flagging lodash-style object accessors (e.g.
|
|
74
|
+
* `@directus/utils`'s `get(item, ...)`) as sql-injection. Those three names
|
|
75
|
+
* are also common non-DB methods (lodash `get`, jQuery/iterator
|
|
76
|
+
* `each`, Map/WebSocket `.values()`), so they are removed from the fallback —
|
|
77
|
+
* mirroring the `get`/`each` trim in CHANGELOG 3.4.9 (DB_CALL_METHOD_NAMES).
|
|
78
|
+
*
|
|
79
|
+
* `query` is deliberately RETAINED: it is a genuine query-execution method on
|
|
80
|
+
* mysql2, pg, node-postgres, D1 and Planetscale (`.query(...)`), and the
|
|
81
|
+
* spec-19 data-access fixtures exercise it as a canonical DB entry point.
|
|
82
|
+
* Removing it would turn real SQL-injection positives into false negatives.
|
|
83
|
+
*
|
|
84
|
+
* `raw` is deliberately retained: it is a genuine raw-execution method on
|
|
85
|
+
* D1 prepared statements, Knex, and Kysely, and the Item-6 taint-tracking
|
|
86
|
+
* fixtures exercise it as the canonical raw-SQL entry point.
|
|
87
|
+
*/
|
|
69
88
|
export const DB_CALL_METHODS = new Set([
|
|
70
89
|
'exec',
|
|
71
90
|
'prepare',
|
|
@@ -74,10 +93,7 @@ export const DB_CALL_METHODS = new Set([
|
|
|
74
93
|
'all',
|
|
75
94
|
'first',
|
|
76
95
|
'query',
|
|
77
|
-
'get',
|
|
78
|
-
'each',
|
|
79
96
|
'raw',
|
|
80
|
-
'values',
|
|
81
97
|
]);
|
|
82
98
|
/** ORM method patterns — fixed API surface for ORM recognition (Spec 21 R1) */
|
|
83
99
|
export const ORM_METHODS = new Set([
|
|
@@ -128,6 +144,9 @@ function matchesValidatorPackage(specifier) {
|
|
|
128
144
|
*
|
|
129
145
|
* An import like `import Database from 'better-sqlite3'` produces
|
|
130
146
|
* `Database` as DB-provenanced with reason "package".
|
|
147
|
+
* @param adapter
|
|
148
|
+
* @param ast
|
|
149
|
+
* @returns
|
|
131
150
|
*/
|
|
132
151
|
export function extractDBProvenancedImports(ast, adapter) {
|
|
133
152
|
const seedMap = new Map();
|
|
@@ -159,6 +178,9 @@ export function extractDBProvenancedImports(ast, adapter) {
|
|
|
159
178
|
/**
|
|
160
179
|
* Extract all validator-provenanced identifiers from a file's imports.
|
|
161
180
|
* Same pattern as extractDBProvenancedImports but for validator packages.
|
|
181
|
+
* @param adapter
|
|
182
|
+
* @param ast
|
|
183
|
+
* @returns
|
|
162
184
|
*/
|
|
163
185
|
export function extractValidatorProvenancedImports(ast, adapter) {
|
|
164
186
|
const seedMap = new Map();
|
|
@@ -189,6 +211,133 @@ export function extractValidatorProvenancedImports(ast, adapter) {
|
|
|
189
211
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
190
212
|
// Provenance propagation (R1 core)
|
|
191
213
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
214
|
+
/**
|
|
215
|
+
* Propagate provenance through a variable declaration (rules 1-3, 8).
|
|
216
|
+
* Returns true if any new identifier was added.
|
|
217
|
+
*/
|
|
218
|
+
function propagateVariableDeclaration(node, ctx) {
|
|
219
|
+
const { nameNode, valueNode, typeAnnotationNode } = splitVariableDeclarator(node, ctx.adapter);
|
|
220
|
+
if (!nameNode)
|
|
221
|
+
return false;
|
|
222
|
+
let mutated = false;
|
|
223
|
+
// Rule 8: type annotation — let x: D1Database
|
|
224
|
+
if (typeAnnotationNode) {
|
|
225
|
+
mutated = propagateFromTypeAnnotation(nameNode, typeAnnotationNode, ctx);
|
|
226
|
+
}
|
|
227
|
+
if (valueNode) {
|
|
228
|
+
mutated = propagateFromValue(nameNode, valueNode, ctx) || mutated;
|
|
229
|
+
}
|
|
230
|
+
return mutated;
|
|
231
|
+
}
|
|
232
|
+
/** Rule 8: type annotation — `let x: D1Database`. */
|
|
233
|
+
function propagateFromTypeAnnotation(nameNode, typeAnnotationNode, ctx) {
|
|
234
|
+
if (nameNode.type !== 'identifier')
|
|
235
|
+
return false;
|
|
236
|
+
const typeText = ctx.adapter.getNodeText(typeAnnotationNode, ctx.sourceCode).trim();
|
|
237
|
+
if (!DB_TYPES.has(typeText))
|
|
238
|
+
return false;
|
|
239
|
+
const name = ctx.adapter.getNodeText(nameNode, ctx.sourceCode);
|
|
240
|
+
if (ctx.provenanceMap.has(name))
|
|
241
|
+
return false;
|
|
242
|
+
ctx.provenanceMap.set(name, {
|
|
243
|
+
identifier: name,
|
|
244
|
+
reason: 'type',
|
|
245
|
+
source: `type annotation ${typeText}`,
|
|
246
|
+
chain: [],
|
|
247
|
+
});
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
/** Propagate provenance from a declarator's value expression into its names. */
|
|
251
|
+
function propagateFromValue(nameNode, valueNode, ctx) {
|
|
252
|
+
const propagated = tryPropagateFromExpression(valueNode, ctx.adapter, ctx.sourceCode, ctx.provenanceMap);
|
|
253
|
+
if (!propagated)
|
|
254
|
+
return false;
|
|
255
|
+
let mutated = false;
|
|
256
|
+
for (const varName of extractPatternNames(nameNode, ctx.adapter, ctx.sourceCode)) {
|
|
257
|
+
if (!ctx.provenanceMap.has(varName)) {
|
|
258
|
+
ctx.provenanceMap.set(varName, {
|
|
259
|
+
identifier: varName,
|
|
260
|
+
reason: 'propagation',
|
|
261
|
+
source: propagated.source,
|
|
262
|
+
chain: [...propagated.chain, propagated.identifier],
|
|
263
|
+
});
|
|
264
|
+
mutated = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return mutated;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Propagate provenance through a default parameter (rule 6).
|
|
271
|
+
* Returns true if a new identifier was added.
|
|
272
|
+
*/
|
|
273
|
+
function propagateDefaultParameter(node, adapter, sourceCode, provenanceMap) {
|
|
274
|
+
const children = adapter.getChildren(node);
|
|
275
|
+
// assignment_pattern has [left, right]
|
|
276
|
+
if (children.length < 2)
|
|
277
|
+
return false;
|
|
278
|
+
const leftNode = children[0];
|
|
279
|
+
const rightNode = children[1];
|
|
280
|
+
if (leftNode.type !== 'identifier')
|
|
281
|
+
return false;
|
|
282
|
+
const paramName = adapter.getNodeText(leftNode, sourceCode);
|
|
283
|
+
const propagated = tryPropagateFromExpression(rightNode, adapter, sourceCode, provenanceMap);
|
|
284
|
+
if (propagated && !provenanceMap.has(paramName)) {
|
|
285
|
+
provenanceMap.set(paramName, {
|
|
286
|
+
identifier: paramName,
|
|
287
|
+
reason: 'propagation',
|
|
288
|
+
source: `default parameter = ${propagated.source}`,
|
|
289
|
+
chain: [...propagated.chain, propagated.identifier],
|
|
290
|
+
});
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Propagate provenance through a class field initialization (rule 7).
|
|
297
|
+
* Returns true if a new identifier was added.
|
|
298
|
+
*/
|
|
299
|
+
function propagateClassField(node, adapter, sourceCode, provenanceMap) {
|
|
300
|
+
const children = adapter.getChildren(node);
|
|
301
|
+
// Typically [name, value] or [decorators..., name, value]
|
|
302
|
+
const nameChild = children.find((c) => c.type === 'property_identifier');
|
|
303
|
+
const valueChild = children.find((c) => c.type !== 'property_identifier' &&
|
|
304
|
+
c.type !== 'decorator' &&
|
|
305
|
+
c.type !== 'private' &&
|
|
306
|
+
c.type !== 'public' &&
|
|
307
|
+
c.type !== 'protected' &&
|
|
308
|
+
c.type !== 'static' &&
|
|
309
|
+
c.type !== 'readonly' &&
|
|
310
|
+
c.type !== 'abstract');
|
|
311
|
+
if (nameChild && valueChild) {
|
|
312
|
+
const fieldName = adapter.getNodeText(nameChild, sourceCode);
|
|
313
|
+
const propagated = tryPropagateFromExpression(valueChild, adapter, sourceCode, provenanceMap);
|
|
314
|
+
if (propagated && !provenanceMap.has(fieldName)) {
|
|
315
|
+
provenanceMap.set(fieldName, {
|
|
316
|
+
identifier: fieldName,
|
|
317
|
+
reason: 'propagation',
|
|
318
|
+
source: `class field initialized from ${propagated.source}`,
|
|
319
|
+
chain: [...propagated.chain, propagated.identifier],
|
|
320
|
+
});
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
function applyPropagationRule(node, parent, ctx) {
|
|
327
|
+
const { adapter, sourceCode, provenanceMap } = ctx;
|
|
328
|
+
if (node.type === 'variable_declarator') {
|
|
329
|
+
return propagateVariableDeclaration(node, ctx);
|
|
330
|
+
}
|
|
331
|
+
if (node.type === 'assignment_pattern' &&
|
|
332
|
+
parent?.type === 'formal_parameters') {
|
|
333
|
+
return propagateDefaultParameter(node, adapter, sourceCode, provenanceMap);
|
|
334
|
+
}
|
|
335
|
+
if (node.type === 'public_field_definition' ||
|
|
336
|
+
node.type === 'field_definition') {
|
|
337
|
+
return propagateClassField(node, adapter, sourceCode, provenanceMap);
|
|
338
|
+
}
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
192
341
|
/**
|
|
193
342
|
* Propagate provenance through assignments, destructuring, parameters,
|
|
194
343
|
* class fields, and type annotations within a single file.
|
|
@@ -202,10 +351,16 @@ export function extractValidatorProvenancedImports(ast, adapter) {
|
|
|
202
351
|
* 6. default parameter with DB value
|
|
203
352
|
* 7. class field initialized with DB value
|
|
204
353
|
* 8. type annotation with known DB type
|
|
354
|
+
* @param adapter
|
|
355
|
+
* @param ast
|
|
356
|
+
* @param seedMap
|
|
357
|
+
* @param sourceCode
|
|
358
|
+
* @returns
|
|
205
359
|
*/
|
|
206
360
|
export function propagateProvenance(ast, adapter, sourceCode, seedMap) {
|
|
207
361
|
// Work on a copy so we can add newly-provenanced identifiers during the walk
|
|
208
362
|
const provenanceMap = new Map(seedMap);
|
|
363
|
+
const ctx = { adapter, sourceCode, provenanceMap };
|
|
209
364
|
// Keep iterating until no new identifiers are discovered (handles chains)
|
|
210
365
|
let changed = true;
|
|
211
366
|
let iterations = 0;
|
|
@@ -214,100 +369,8 @@ export function propagateProvenance(ast, adapter, sourceCode, seedMap) {
|
|
|
214
369
|
changed = false;
|
|
215
370
|
iterations++;
|
|
216
371
|
walkAST(ast.root, (node, parent) => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const { nameNode, valueNode, typeAnnotationNode } = splitVariableDeclarator(node, adapter);
|
|
220
|
-
if (!nameNode)
|
|
221
|
-
return;
|
|
222
|
-
// Rule 8: type annotation — let x: D1Database
|
|
223
|
-
if (typeAnnotationNode && nameNode.type === 'identifier') {
|
|
224
|
-
const typeText = adapter.getNodeText(typeAnnotationNode, sourceCode).trim();
|
|
225
|
-
if (DB_TYPES.has(typeText)) {
|
|
226
|
-
const name = adapter.getNodeText(nameNode, sourceCode);
|
|
227
|
-
if (!provenanceMap.has(name)) {
|
|
228
|
-
provenanceMap.set(name, {
|
|
229
|
-
identifier: name,
|
|
230
|
-
reason: 'type',
|
|
231
|
-
source: `type annotation ${typeText}`,
|
|
232
|
-
chain: [],
|
|
233
|
-
});
|
|
234
|
-
changed = true;
|
|
235
|
-
}
|
|
236
|
-
// Type-provenanced names count as DB-provenanced for further propagation
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (valueNode) {
|
|
240
|
-
const propagated = tryPropagateFromExpression(valueNode, adapter, sourceCode, provenanceMap);
|
|
241
|
-
if (propagated) {
|
|
242
|
-
// Extract the variable name(s) from the name node
|
|
243
|
-
const varNames = extractPatternNames(nameNode, adapter, sourceCode);
|
|
244
|
-
for (const varName of varNames) {
|
|
245
|
-
if (!provenanceMap.has(varName)) {
|
|
246
|
-
provenanceMap.set(varName, {
|
|
247
|
-
identifier: varName,
|
|
248
|
-
reason: 'propagation',
|
|
249
|
-
source: propagated.source,
|
|
250
|
-
chain: [...propagated.chain, propagated.identifier],
|
|
251
|
-
});
|
|
252
|
-
changed = true;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
// ── Rule 6: default parameters ──
|
|
260
|
-
if (node.type === 'assignment_pattern' &&
|
|
261
|
-
parent?.type === 'formal_parameters') {
|
|
262
|
-
const children = adapter.getChildren(node);
|
|
263
|
-
// assignment_pattern has [left, right]
|
|
264
|
-
if (children.length >= 2) {
|
|
265
|
-
const leftNode = children[0];
|
|
266
|
-
const rightNode = children[1];
|
|
267
|
-
if (leftNode.type === 'identifier') {
|
|
268
|
-
const paramName = adapter.getNodeText(leftNode, sourceCode);
|
|
269
|
-
const propagated = tryPropagateFromExpression(rightNode, adapter, sourceCode, provenanceMap);
|
|
270
|
-
if (propagated && !provenanceMap.has(paramName)) {
|
|
271
|
-
provenanceMap.set(paramName, {
|
|
272
|
-
identifier: paramName,
|
|
273
|
-
reason: 'propagation',
|
|
274
|
-
source: `default parameter = ${propagated.source}`,
|
|
275
|
-
chain: [...propagated.chain, propagated.identifier],
|
|
276
|
-
});
|
|
277
|
-
changed = true;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
// ── Rule 7: class field initialization ──
|
|
284
|
-
if (node.type === 'public_field_definition' ||
|
|
285
|
-
node.type === 'field_definition') {
|
|
286
|
-
const children = adapter.getChildren(node);
|
|
287
|
-
// Typically [name, value] or [decorators..., name, value]
|
|
288
|
-
const nameChild = children.find((c) => c.type === 'property_identifier');
|
|
289
|
-
const valueChild = children.find((c) => c.type !== 'property_identifier' &&
|
|
290
|
-
c.type !== 'decorator' &&
|
|
291
|
-
c.type !== 'private' &&
|
|
292
|
-
c.type !== 'public' &&
|
|
293
|
-
c.type !== 'protected' &&
|
|
294
|
-
c.type !== 'static' &&
|
|
295
|
-
c.type !== 'readonly' &&
|
|
296
|
-
c.type !== 'abstract');
|
|
297
|
-
if (nameChild && valueChild) {
|
|
298
|
-
const fieldName = adapter.getNodeText(nameChild, sourceCode);
|
|
299
|
-
const propagated = tryPropagateFromExpression(valueChild, adapter, sourceCode, provenanceMap);
|
|
300
|
-
if (propagated && !provenanceMap.has(fieldName)) {
|
|
301
|
-
provenanceMap.set(fieldName, {
|
|
302
|
-
identifier: fieldName,
|
|
303
|
-
reason: 'propagation',
|
|
304
|
-
source: `class field initialized from ${propagated.source}`,
|
|
305
|
-
chain: [...propagated.chain, propagated.identifier],
|
|
306
|
-
});
|
|
307
|
-
changed = true;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
return;
|
|
372
|
+
if (applyPropagationRule(node, parent, ctx)) {
|
|
373
|
+
changed = true;
|
|
311
374
|
}
|
|
312
375
|
});
|
|
313
376
|
}
|
|
@@ -337,24 +400,9 @@ function tryPropagateFromExpression(node, adapter, sourceCode, provenanceMap) {
|
|
|
337
400
|
// ── Rule 2: drizzle(env.DB) — call where callee is DB-provenanced ──
|
|
338
401
|
// ── Rule 3: db.prepare(sql) — member expression call on DB receiver ──
|
|
339
402
|
if (node.type === 'call_expression') {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// Case: simple identifier call — drizzle(...)
|
|
344
|
-
if (calleeNode.type === 'identifier') {
|
|
345
|
-
const name = adapter.getNodeText(calleeNode, sourceCode);
|
|
346
|
-
if (name && provenanceMap.has(name)) {
|
|
347
|
-
return provenanceMap.get(name);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
// Case: member expression — db.prepare(...)
|
|
351
|
-
if (calleeNode.type === 'member_expression') {
|
|
352
|
-
const receiver = getMemberExpressionReceiver(calleeNode, adapter, sourceCode);
|
|
353
|
-
if (receiver && provenanceMap.has(receiver)) {
|
|
354
|
-
return provenanceMap.get(receiver);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
403
|
+
const viaCall = tryCallProvenance(node, adapter, sourceCode, provenanceMap);
|
|
404
|
+
if (viaCall)
|
|
405
|
+
return viaCall;
|
|
358
406
|
}
|
|
359
407
|
// ── Simple identifier reference (for destructuring sources) ──
|
|
360
408
|
if (node.type === 'identifier') {
|
|
@@ -372,6 +420,31 @@ function tryPropagateFromExpression(node, adapter, sourceCode, provenanceMap) {
|
|
|
372
420
|
}
|
|
373
421
|
return null;
|
|
374
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Rule 2/3: check a call_expression whose callee is either a DB-provenanced
|
|
425
|
+
* identifier (e.g. `drizzle(...)`) or a member expression on a DB receiver
|
|
426
|
+
* (e.g. `db.prepare(sql)`).
|
|
427
|
+
*/
|
|
428
|
+
function tryCallProvenance(node, adapter, sourceCode, provenanceMap) {
|
|
429
|
+
const calleeNode = getCallExpressionCallee(node, adapter);
|
|
430
|
+
if (!calleeNode)
|
|
431
|
+
return null;
|
|
432
|
+
// Case: simple identifier call — drizzle(...)
|
|
433
|
+
if (calleeNode.type === 'identifier') {
|
|
434
|
+
const name = adapter.getNodeText(calleeNode, sourceCode);
|
|
435
|
+
if (name && provenanceMap.has(name)) {
|
|
436
|
+
return provenanceMap.get(name);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// Case: member expression — db.prepare(...)
|
|
440
|
+
if (calleeNode.type === 'member_expression') {
|
|
441
|
+
const receiver = getMemberExpressionReceiver(calleeNode, adapter, sourceCode);
|
|
442
|
+
if (receiver && provenanceMap.has(receiver)) {
|
|
443
|
+
return provenanceMap.get(receiver);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
375
448
|
/**
|
|
376
449
|
* Split a variable_declarator into its name, value, and type annotation
|
|
377
450
|
* child nodes.
|
|
@@ -425,59 +498,57 @@ function splitVariableDeclarator(node, adapter) {
|
|
|
425
498
|
*/
|
|
426
499
|
function extractPatternNames(node, adapter, sourceCode) {
|
|
427
500
|
const names = [];
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
else if (child.type === 'pair_pattern') {
|
|
448
|
-
// pair_pattern children: [property, value]
|
|
449
|
-
// Extract from the value side (which might be an identifier or nested pattern)
|
|
450
|
-
const pairChildren = adapter.getChildren(child);
|
|
451
|
-
if (pairChildren.length >= 2) {
|
|
452
|
-
collect(pairChildren[1]);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
else if (child.type === 'rest_pattern') {
|
|
456
|
-
collectChildren(child, adapter, sourceCode, names);
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
collect(child);
|
|
460
|
-
}
|
|
501
|
+
collectPatternNames(node, adapter, sourceCode, names);
|
|
502
|
+
return names;
|
|
503
|
+
}
|
|
504
|
+
/** Recursively collect identifier names from a destructuring pattern node. */
|
|
505
|
+
function collectPatternNames(node, adapter, sourceCode, names) {
|
|
506
|
+
if (node.type === 'identifier') {
|
|
507
|
+
const name = adapter.getNodeText(node, sourceCode);
|
|
508
|
+
if (name)
|
|
509
|
+
names.push(name);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (node.type === 'object_pattern') {
|
|
513
|
+
for (const child of adapter.getChildren(node)) {
|
|
514
|
+
if (child.type === '{' || child.type === '}' || child.type === ',')
|
|
515
|
+
continue;
|
|
516
|
+
if (child.type === 'shorthand_property_identifier') {
|
|
517
|
+
const name = adapter.getNodeText(child, sourceCode);
|
|
518
|
+
if (name)
|
|
519
|
+
names.push(name);
|
|
461
520
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (
|
|
466
|
-
|
|
521
|
+
else if (child.type === 'pair_pattern') {
|
|
522
|
+
// pair_pattern children: [property, value] — collect from value side
|
|
523
|
+
const pairChildren = adapter.getChildren(child);
|
|
524
|
+
if (pairChildren.length >= 2) {
|
|
525
|
+
collectPatternNames(pairChildren[1], adapter, sourceCode, names);
|
|
467
526
|
}
|
|
468
|
-
collect(child);
|
|
469
527
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
collect(children[0]);
|
|
528
|
+
else if (child.type === 'rest_pattern') {
|
|
529
|
+
collectChildren(child, adapter, sourceCode, names);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
collectPatternNames(child, adapter, sourceCode, names);
|
|
476
533
|
}
|
|
477
534
|
}
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (node.type === 'array_pattern') {
|
|
538
|
+
for (const child of adapter.getChildren(node)) {
|
|
539
|
+
if (child.type === '[' || child.type === ']' || child.type === ',')
|
|
540
|
+
continue;
|
|
541
|
+
collectPatternNames(child, adapter, sourceCode, names);
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
// assignment_pattern — default value in destructuring, collect from left side
|
|
546
|
+
if (node.type === 'assignment_pattern') {
|
|
547
|
+
const children = adapter.getChildren(node);
|
|
548
|
+
if (children.length >= 1) {
|
|
549
|
+
collectPatternNames(children[0], adapter, sourceCode, names);
|
|
550
|
+
}
|
|
478
551
|
}
|
|
479
|
-
collect(node);
|
|
480
|
-
return names;
|
|
481
552
|
}
|
|
482
553
|
function collectChildren(node, adapter, sourceCode, names) {
|
|
483
554
|
for (const child of adapter.getChildren(node)) {
|
|
@@ -510,8 +581,10 @@ function getMemberExpressionReceiver(node, adapter, sourceCode) {
|
|
|
510
581
|
firstChild.type !== 'property_identifier' && firstChild.type !== 'field_identifier') {
|
|
511
582
|
if (firstChild.type === 'member_expression' ||
|
|
512
583
|
firstChild.type === 'selector_expression') {
|
|
513
|
-
|
|
514
|
-
|
|
584
|
+
// Compound receiver: env.DB.prepare → receiver is "env.DB", not "env".
|
|
585
|
+
// Return the full text of the inner member expression so fallback
|
|
586
|
+
// entries like dbBindingNames: ['env.DB'] match the provenance check.
|
|
587
|
+
return adapter.getNodeText(firstChild, sourceCode);
|
|
515
588
|
}
|
|
516
589
|
if (firstChild.type === 'identifier') {
|
|
517
590
|
return adapter.getNodeText(firstChild, sourceCode);
|
|
@@ -592,6 +665,11 @@ function walkAST(root, visitor) {
|
|
|
592
665
|
*
|
|
593
666
|
* This is the main entry point — call once per file before analysis.
|
|
594
667
|
* Combines import extraction, propagation, and mode-based fallback.
|
|
668
|
+
* @param adapter
|
|
669
|
+
* @param ast
|
|
670
|
+
* @param options
|
|
671
|
+
* @param sourceCode
|
|
672
|
+
* @returns
|
|
595
673
|
*/
|
|
596
674
|
export function buildProvenanceContext(ast, adapter, sourceCode, options) {
|
|
597
675
|
const mode = options.mode;
|
|
@@ -608,11 +686,15 @@ export function buildProvenanceContext(ast, adapter, sourceCode, options) {
|
|
|
608
686
|
// 3. Fallback: in hybrid mode, add identifiers that match name lists
|
|
609
687
|
// but weren't caught by provenance (R3)
|
|
610
688
|
if (mode === 'hybrid') {
|
|
611
|
-
dbProvenanced = addNameListFallbacks(dbProvenanced, sourceCode,
|
|
689
|
+
dbProvenanced = addNameListFallbacks(dbProvenanced, sourceCode, {
|
|
690
|
+
dbReceiverNames: options.dbReceiverNames ?? [],
|
|
691
|
+
dbBindingNames: options.dbBindingNames ?? [],
|
|
692
|
+
dbWrapperNames: options.dbWrapperNames ?? [],
|
|
693
|
+
});
|
|
612
694
|
}
|
|
613
695
|
// 4. In names mode, use ONLY name lists
|
|
614
696
|
if (mode === 'names') {
|
|
615
|
-
dbProvenanced = buildNamesOnlyProvenance(sourceCode, options.dbReceiverNames ?? [], options.dbBindingNames ?? []);
|
|
697
|
+
dbProvenanced = buildNamesOnlyProvenance(sourceCode, options.dbReceiverNames ?? [], options.dbBindingNames ?? [], options.dbWrapperNames ?? []);
|
|
616
698
|
}
|
|
617
699
|
return {
|
|
618
700
|
dbProvenanced,
|
|
@@ -620,61 +702,63 @@ export function buildProvenanceContext(ast, adapter, sourceCode, options) {
|
|
|
620
702
|
mode,
|
|
621
703
|
};
|
|
622
704
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
* but weren't caught by the provenance chain (R3 hybrid mode).
|
|
626
|
-
*
|
|
627
|
-
* These entries carry `reason: 'fallback'` — visible in config detection
|
|
628
|
-
* so users can audit and tighten their chains.
|
|
629
|
-
*/
|
|
630
|
-
function addNameListFallbacks(provenanceMap, sourceCode, dbReceiverNames, dbBindingNames) {
|
|
705
|
+
function addNameListFallbacks(provenanceMap, sourceCode, nameLists) {
|
|
706
|
+
const { dbReceiverNames, dbBindingNames, dbWrapperNames } = nameLists;
|
|
631
707
|
const result = new Map(provenanceMap);
|
|
632
|
-
|
|
633
|
-
|
|
708
|
+
addIdentifierFallbacks(result, sourceCode, dbReceiverNames, 'dbReceiverNames');
|
|
709
|
+
addBindingFallbacks(result, sourceCode, dbBindingNames);
|
|
710
|
+
addIdentifierFallbacks(result, sourceCode, dbWrapperNames, 'dbWrapperNames');
|
|
711
|
+
return result;
|
|
712
|
+
}
|
|
713
|
+
/** Add fallback entries for identifiers that appear in source but lack provenance. */
|
|
714
|
+
function addIdentifierFallbacks(result, sourceCode, names, label) {
|
|
715
|
+
for (const name of names) {
|
|
634
716
|
if (result.has(name))
|
|
635
717
|
continue; // already provenanced — provenance wins
|
|
636
|
-
// Check if this name appears as an identifier in the source
|
|
637
718
|
if (identifierAppearsInSource(sourceCode, name)) {
|
|
638
719
|
result.set(name, {
|
|
639
720
|
identifier: name,
|
|
640
721
|
reason: 'fallback',
|
|
641
|
-
source: `name list match:
|
|
722
|
+
source: `name list match: ${label} contains "${name}"`,
|
|
642
723
|
chain: [],
|
|
643
724
|
});
|
|
644
725
|
}
|
|
645
726
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
727
|
+
}
|
|
728
|
+
/** Add fallback entries for binding names like `env.DB` (plus their short forms). */
|
|
729
|
+
function addBindingFallbacks(result, sourceCode, bindings) {
|
|
730
|
+
for (const binding of bindings) {
|
|
731
|
+
if (!sourceCode.includes(binding))
|
|
732
|
+
continue;
|
|
733
|
+
const dotIdx = binding.lastIndexOf('.');
|
|
734
|
+
const shortName = dotIdx >= 0 ? binding.substring(dotIdx + 1) : binding;
|
|
735
|
+
if (!result.has(binding)) {
|
|
736
|
+
result.set(binding, {
|
|
737
|
+
identifier: binding,
|
|
738
|
+
reason: 'fallback',
|
|
739
|
+
source: `name list match: dbBindingNames contains "${binding}"`,
|
|
740
|
+
chain: [],
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
if (shortName !== binding && !result.has(shortName)) {
|
|
744
|
+
result.set(shortName, {
|
|
745
|
+
identifier: shortName,
|
|
746
|
+
reason: 'fallback',
|
|
747
|
+
source: `from binding ${binding}`,
|
|
748
|
+
chain: [],
|
|
749
|
+
});
|
|
669
750
|
}
|
|
670
751
|
}
|
|
671
|
-
return result;
|
|
672
752
|
}
|
|
673
753
|
/**
|
|
674
754
|
* Build a provenance map using ONLY name lists (for names mode).
|
|
675
755
|
*/
|
|
676
|
-
function buildNamesOnlyProvenance(sourceCode, dbReceiverNames, dbBindingNames) {
|
|
677
|
-
return addNameListFallbacks(new Map(), sourceCode,
|
|
756
|
+
function buildNamesOnlyProvenance(sourceCode, dbReceiverNames, dbBindingNames, dbWrapperNames) {
|
|
757
|
+
return addNameListFallbacks(new Map(), sourceCode, {
|
|
758
|
+
dbReceiverNames,
|
|
759
|
+
dbBindingNames,
|
|
760
|
+
dbWrapperNames,
|
|
761
|
+
});
|
|
678
762
|
}
|
|
679
763
|
/** Check if an identifier name appears as a standalone identifier in source. */
|
|
680
764
|
function identifierAppearsInSource(sourceCode, name) {
|
|
@@ -685,9 +769,6 @@ function identifierAppearsInSource(sourceCode, name) {
|
|
|
685
769
|
function escapeRegex(s) {
|
|
686
770
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
687
771
|
}
|
|
688
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
689
|
-
// Core detection — is this call DB-provenanced?
|
|
690
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
691
772
|
/**
|
|
692
773
|
* Determine if a call-expression node's callee is DB-provenanced.
|
|
693
774
|
*
|
|
@@ -699,11 +780,15 @@ function escapeRegex(s) {
|
|
|
699
780
|
* 2. Member expression call → is the receiver DB-provenanced AND is the
|
|
700
781
|
* method in the DB call method set?
|
|
701
782
|
* 3. ORM patterns → receiver is DB-provenanced and method matches ORM API
|
|
783
|
+
*
|
|
784
|
+
* @param node the call-expression node whose callee is under test
|
|
785
|
+
* @param query bundled adapter, source, provenance context, and DB methods
|
|
786
|
+
* @returns true if the call's callee resolves to a DB-provenanced target
|
|
702
787
|
*/
|
|
703
|
-
export function isDBProvenanced(node,
|
|
788
|
+
export function isDBProvenanced(node, query) {
|
|
704
789
|
if (node.type !== 'call_expression')
|
|
705
790
|
return false;
|
|
706
|
-
const methods =
|
|
791
|
+
const { adapter, sourceCode, context, methods } = query;
|
|
707
792
|
const calleeNode = getCallExpressionCallee(node, adapter);
|
|
708
793
|
if (!calleeNode)
|
|
709
794
|
return false;
|
|
@@ -725,7 +810,7 @@ export function isDBProvenanced(node, adapter, sourceCode, context, dbCallMethod
|
|
|
725
810
|
}
|
|
726
811
|
// Case 2: Member expression — e.g. db.prepare(...)
|
|
727
812
|
if (calleeNode.type === 'member_expression' || calleeNode.type === 'selector_expression') {
|
|
728
|
-
return isMemberExpressionDBProvenanced(calleeNode,
|
|
813
|
+
return isMemberExpressionDBProvenanced(calleeNode, query);
|
|
729
814
|
}
|
|
730
815
|
return false;
|
|
731
816
|
}
|
|
@@ -736,9 +821,42 @@ export function isDBProvenanced(node, adapter, sourceCode, context, dbCallMethod
|
|
|
736
821
|
* checks if it's in the provenance context, and verifies
|
|
737
822
|
* the method matches the DB call/ORM API.
|
|
738
823
|
*/
|
|
739
|
-
function isMemberExpressionDBProvenanced(node,
|
|
740
|
-
|
|
741
|
-
|
|
824
|
+
function isMemberExpressionDBProvenanced(node, query) {
|
|
825
|
+
const { adapter, sourceCode, context } = query;
|
|
826
|
+
const rootReceiver = findRootReceiver(node, adapter, sourceCode);
|
|
827
|
+
if (!rootReceiver)
|
|
828
|
+
return false;
|
|
829
|
+
// In names mode, check the method name directly
|
|
830
|
+
if (context.mode === 'names') {
|
|
831
|
+
return isDBMethodCall(node, query);
|
|
832
|
+
}
|
|
833
|
+
// Check if the root receiver is DB-provenanced.
|
|
834
|
+
// Compound receivers (e.g. "db.users" or "env.DB") need to match both
|
|
835
|
+
// the full text (for bindings like "env.DB") and each sub-identifier
|
|
836
|
+
// (for receivers like "db.users" where "db" is in dbReceiverNames).
|
|
837
|
+
const matchesProvenance = (r) => {
|
|
838
|
+
if (context.dbProvenanced.has(r))
|
|
839
|
+
return true;
|
|
840
|
+
for (const part of r.split('.')) {
|
|
841
|
+
if (context.dbProvenanced.has(part))
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
return false;
|
|
845
|
+
};
|
|
846
|
+
if (!matchesProvenance(rootReceiver)) {
|
|
847
|
+
if (rootReceiver !== 'this')
|
|
848
|
+
return false;
|
|
849
|
+
// For `this.xxx`, check if the method chain itself suggests DB usage
|
|
850
|
+
return isDBMethodOnThis(node, query);
|
|
851
|
+
}
|
|
852
|
+
// Check that the method is in the DB call/ORM API
|
|
853
|
+
return isDBMethodCall(node, query);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Walk a member/selector expression chain to its root receiver text.
|
|
857
|
+
* Returns `'this'` for `this.xxx` chains and `null` when no root is found.
|
|
858
|
+
*/
|
|
859
|
+
function findRootReceiver(node, adapter, sourceCode) {
|
|
742
860
|
let current = node;
|
|
743
861
|
while (current.type === 'member_expression' || current.type === 'selector_expression') {
|
|
744
862
|
const children = adapter.getChildren(current);
|
|
@@ -746,41 +864,28 @@ function isMemberExpressionDBProvenanced(node, adapter, sourceCode, context, met
|
|
|
746
864
|
if (!firstChild)
|
|
747
865
|
break;
|
|
748
866
|
if (firstChild.type === 'identifier') {
|
|
749
|
-
|
|
750
|
-
break;
|
|
867
|
+
return adapter.getNodeText(firstChild, sourceCode);
|
|
751
868
|
}
|
|
752
869
|
if (firstChild.type === 'member_expression' || firstChild.type === 'selector_expression') {
|
|
753
|
-
|
|
754
|
-
|
|
870
|
+
// Compound receiver: env.DB.prepare → root is "env.DB", not "env".
|
|
871
|
+
// Returning the full text of the inner member expression ensures
|
|
872
|
+
// fallback entries match (e.g. dbBindingNames: ['env.DB']).
|
|
873
|
+
return adapter.getNodeText(firstChild, sourceCode);
|
|
755
874
|
}
|
|
756
875
|
// this.db.prepare → root is a chain on `this`, check `this.xxx`
|
|
757
876
|
if (firstChild.type === 'this' || firstChild.type === 'super') {
|
|
758
|
-
|
|
759
|
-
break;
|
|
877
|
+
return 'this';
|
|
760
878
|
}
|
|
761
879
|
break;
|
|
762
880
|
}
|
|
763
|
-
|
|
764
|
-
return false;
|
|
765
|
-
// In names mode, check the method name directly
|
|
766
|
-
if (context.mode === 'names') {
|
|
767
|
-
return isDBMethodCall(node, adapter, sourceCode, context, methods);
|
|
768
|
-
}
|
|
769
|
-
// Check if the root receiver is DB-provenanced
|
|
770
|
-
if (!context.dbProvenanced.has(rootReceiver)) {
|
|
771
|
-
if (rootReceiver !== 'this')
|
|
772
|
-
return false;
|
|
773
|
-
// For `this.xxx`, check if the method chain itself suggests DB usage
|
|
774
|
-
return isDBMethodOnThis(node, adapter, sourceCode, context, methods);
|
|
775
|
-
}
|
|
776
|
-
// Check that the method is in the DB call/ORM API
|
|
777
|
-
return isDBMethodCall(node, adapter, sourceCode, context, methods);
|
|
881
|
+
return null;
|
|
778
882
|
}
|
|
779
883
|
/**
|
|
780
884
|
* Check if a call through a member expression uses a DB method
|
|
781
885
|
* (exec, prepare, all, etc.) or an ORM method (find, insert, etc.).
|
|
782
886
|
*/
|
|
783
|
-
function isDBMethodCall(node,
|
|
887
|
+
function isDBMethodCall(node, query) {
|
|
888
|
+
const { adapter, sourceCode, methods } = query;
|
|
784
889
|
// Walk the member expression chain and check each property
|
|
785
890
|
let current = node;
|
|
786
891
|
while (current.type === 'member_expression' ||
|
|
@@ -790,7 +895,7 @@ function isDBMethodCall(node, _adapter, _sourceCode, _context, methods) {
|
|
|
790
895
|
for (const child of children) {
|
|
791
896
|
if (child.type === 'property_identifier' ||
|
|
792
897
|
child.type === 'field_identifier') {
|
|
793
|
-
const propName =
|
|
898
|
+
const propName = adapter.getNodeText(child, sourceCode);
|
|
794
899
|
const lower = propName.toLowerCase();
|
|
795
900
|
if (methods.has(lower) || ORM_METHODS.has(lower)) {
|
|
796
901
|
return true;
|
|
@@ -813,7 +918,8 @@ function isDBMethodCall(node, _adapter, _sourceCode, _context, methods) {
|
|
|
813
918
|
* For `this.xxx.method()` calls — check if the method chain suggests
|
|
814
919
|
* DB access. Used when the receiver is `this` (not directly DB-provenanced).
|
|
815
920
|
*/
|
|
816
|
-
function isDBMethodOnThis(node,
|
|
921
|
+
function isDBMethodOnThis(node, query) {
|
|
922
|
+
const { adapter, sourceCode, methods } = query;
|
|
817
923
|
// Walk the chain: this.db.prepare → check if any property matches DB methods
|
|
818
924
|
let current = node;
|
|
819
925
|
while (current.type === 'member_expression' ||
|
|
@@ -844,6 +950,9 @@ function isDBMethodOnThis(node, adapter, sourceCode, _context, methods) {
|
|
|
844
950
|
* Check if an identifier is validator-provenanced (R4 infrastructure).
|
|
845
951
|
*
|
|
846
952
|
* This will be consumed by Spec 15's validator-bypass detection.
|
|
953
|
+
* @param context
|
|
954
|
+
* @param identifier
|
|
955
|
+
* @returns
|
|
847
956
|
*/
|
|
848
957
|
export function isValidatorProvenanced(identifier, context) {
|
|
849
958
|
return context.validatorProvenanced.has(identifier);
|
|
@@ -860,108 +969,124 @@ export function isValidatorProvenanced(identifier, context) {
|
|
|
860
969
|
* (b) can be traced back to a provenanced source through assignments.
|
|
861
970
|
*
|
|
862
971
|
* Deferred: full implementation in R2 step.
|
|
972
|
+
* @param adapter
|
|
973
|
+
* @param fileAst
|
|
974
|
+
* @param provenancedSet
|
|
975
|
+
* @param sourceCode
|
|
976
|
+
* @returns
|
|
863
977
|
*/
|
|
864
978
|
export function inferReceivers(provenancedSet, fileAst, adapter, sourceCode) {
|
|
865
979
|
const inferred = { identifiers: [], evidence: [] };
|
|
866
980
|
const seen = new Set();
|
|
867
981
|
// Step 1: Build an assignment graph — name → source text of initializer
|
|
868
982
|
const assignmentGraph = buildAssignmentGraph(fileAst, adapter, sourceCode);
|
|
983
|
+
const ctx = {
|
|
984
|
+
adapter, sourceCode, provenancedSet, assignmentGraph, seen, inferred,
|
|
985
|
+
};
|
|
869
986
|
// Step 2: Walk all call expressions whose callee is a member expression
|
|
870
|
-
walkAST(fileAst.root, (node) =>
|
|
871
|
-
if (node.type !== 'call_expression')
|
|
872
|
-
return;
|
|
873
|
-
const callee = getCallExpressionCallee(node, adapter);
|
|
874
|
-
if (!callee || (callee.type !== 'member_expression' && callee.type !== 'selector_expression'))
|
|
875
|
-
return;
|
|
876
|
-
// Extract method name (the property being called)
|
|
877
|
-
const methodName = extractMemberExpressionProperty(callee, adapter, sourceCode);
|
|
878
|
-
if (!methodName || !DB_CALL_METHODS.has(methodName.toLowerCase()))
|
|
879
|
-
return;
|
|
880
|
-
// Get the receiver identifier
|
|
881
|
-
const receiver = getMemberExpressionReceiver(callee, adapter, sourceCode);
|
|
882
|
-
if (!receiver)
|
|
883
|
-
return;
|
|
884
|
-
// Already in the provenanced set → skip (already first-class, not inferred)
|
|
885
|
-
if (provenancedSet.has(receiver))
|
|
886
|
-
return;
|
|
887
|
-
// Already added to inferred
|
|
888
|
-
if (seen.has(receiver))
|
|
889
|
-
return;
|
|
890
|
-
// Step 3: Trace receiver through assignment chain to see if it reaches
|
|
891
|
-
// a provenanced source
|
|
892
|
-
const traced = traceAssignmentChain(receiver, assignmentGraph, provenancedSet);
|
|
893
|
-
if (traced.found) {
|
|
894
|
-
seen.add(receiver);
|
|
895
|
-
inferred.identifiers.push(receiver);
|
|
896
|
-
inferred.evidence.push({
|
|
897
|
-
identifier: receiver,
|
|
898
|
-
reason: `calls .${methodName}() traced to ${traced.chains[0]} — ${traced.reason}`,
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
});
|
|
987
|
+
walkAST(fileAst.root, (node) => inferReceiverFromCall(node, ctx));
|
|
902
988
|
return inferred;
|
|
903
989
|
}
|
|
990
|
+
/** Infer a receiver from a single call_expression node, if it qualifies. */
|
|
991
|
+
function inferReceiverFromCall(node, ctx) {
|
|
992
|
+
const { adapter, sourceCode, provenancedSet, assignmentGraph, seen, inferred } = ctx;
|
|
993
|
+
if (node.type !== 'call_expression')
|
|
994
|
+
return;
|
|
995
|
+
const callee = getCallExpressionCallee(node, adapter);
|
|
996
|
+
if (!callee || (callee.type !== 'member_expression' && callee.type !== 'selector_expression'))
|
|
997
|
+
return;
|
|
998
|
+
const methodName = extractMemberExpressionProperty(callee, adapter, sourceCode);
|
|
999
|
+
if (!methodName || !DB_CALL_METHODS.has(methodName.toLowerCase()))
|
|
1000
|
+
return;
|
|
1001
|
+
const receiver = getMemberExpressionReceiver(callee, adapter, sourceCode);
|
|
1002
|
+
if (!receiver)
|
|
1003
|
+
return;
|
|
1004
|
+
// Already provenanced → first-class, not inferred; already added → skip
|
|
1005
|
+
if (provenancedSet.has(receiver) || seen.has(receiver))
|
|
1006
|
+
return;
|
|
1007
|
+
// Step 3: Trace receiver through assignment chain to a provenanced source
|
|
1008
|
+
const traced = traceAssignmentChain(receiver, assignmentGraph, provenancedSet);
|
|
1009
|
+
if (traced.found) {
|
|
1010
|
+
seen.add(receiver);
|
|
1011
|
+
inferred.identifiers.push(receiver);
|
|
1012
|
+
inferred.evidence.push({
|
|
1013
|
+
identifier: receiver,
|
|
1014
|
+
reason: `calls .${methodName}() traced to ${traced.chains[0]} — ${traced.reason}`,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
904
1018
|
/**
|
|
905
1019
|
* Build a reverse assignment graph from the AST.
|
|
906
1020
|
* Maps variable name → the text of its initializer expression.
|
|
907
1021
|
* Handles: const/let/var declarations, parameter defaults, class fields.
|
|
908
1022
|
*/
|
|
909
1023
|
function buildAssignmentGraph(ast, adapter, sourceCode) {
|
|
910
|
-
const
|
|
1024
|
+
const ctx = {
|
|
1025
|
+
graph: new Map(),
|
|
1026
|
+
adapter,
|
|
1027
|
+
sourceCode,
|
|
1028
|
+
};
|
|
911
1029
|
walkAST(ast.root, (node, parent) => {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
if (nameNode && valueNode) {
|
|
916
|
-
const names = extractPatternNames(nameNode, adapter, sourceCode);
|
|
917
|
-
const valueText = adapter.getNodeText(valueNode, sourceCode);
|
|
918
|
-
for (const name of names) {
|
|
919
|
-
if (!graph.has(name))
|
|
920
|
-
graph.set(name, valueText);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
|
-
// Parameter defaults: function foo(x = <expr>)
|
|
926
|
-
if (node.type === 'assignment_pattern' &&
|
|
927
|
-
parent?.type === 'formal_parameters') {
|
|
928
|
-
const children = adapter.getChildren(node);
|
|
929
|
-
if (children.length >= 2) {
|
|
930
|
-
const leftNode = children[0];
|
|
931
|
-
const rightNode = children[1];
|
|
932
|
-
if (leftNode.type === 'identifier') {
|
|
933
|
-
const paramName = adapter.getNodeText(leftNode, sourceCode);
|
|
934
|
-
const valueText = adapter.getNodeText(rightNode, sourceCode);
|
|
935
|
-
if (!graph.has(paramName))
|
|
936
|
-
graph.set(paramName, valueText);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
// Class field: fieldName = <expr>
|
|
942
|
-
if (node.type === 'public_field_definition' ||
|
|
943
|
-
node.type === 'field_definition') {
|
|
944
|
-
const children = adapter.getChildren(node);
|
|
945
|
-
const nameChild = children.find((c) => c.type === 'property_identifier');
|
|
946
|
-
const valueChild = children.find((c) => c.type !== 'property_identifier' &&
|
|
947
|
-
c.type !== 'decorator' &&
|
|
948
|
-
c.type !== 'private' &&
|
|
949
|
-
c.type !== 'public' &&
|
|
950
|
-
c.type !== 'protected' &&
|
|
951
|
-
c.type !== 'static' &&
|
|
952
|
-
c.type !== 'readonly' &&
|
|
953
|
-
c.type !== 'abstract' &&
|
|
954
|
-
c.type !== '=');
|
|
955
|
-
if (nameChild && valueChild) {
|
|
956
|
-
const fieldName = adapter.getNodeText(nameChild, sourceCode);
|
|
957
|
-
const valueText = adapter.getNodeText(valueChild, sourceCode);
|
|
958
|
-
if (!graph.has(fieldName))
|
|
959
|
-
graph.set(fieldName, valueText);
|
|
960
|
-
}
|
|
961
|
-
return;
|
|
962
|
-
}
|
|
1030
|
+
recordVariableDeclarator(node, ctx);
|
|
1031
|
+
recordParameterDefault(node, parent, ctx);
|
|
1032
|
+
recordClassField(node, ctx);
|
|
963
1033
|
});
|
|
964
|
-
return graph;
|
|
1034
|
+
return ctx.graph;
|
|
1035
|
+
}
|
|
1036
|
+
/** Record `const/let/var x = <expr>` into the assignment graph. */
|
|
1037
|
+
function recordVariableDeclarator(node, ctx) {
|
|
1038
|
+
if (node.type !== 'variable_declarator')
|
|
1039
|
+
return;
|
|
1040
|
+
const { nameNode, valueNode } = splitVariableDeclarator(node, ctx.adapter);
|
|
1041
|
+
if (!nameNode || !valueNode)
|
|
1042
|
+
return;
|
|
1043
|
+
const names = extractPatternNames(nameNode, ctx.adapter, ctx.sourceCode);
|
|
1044
|
+
const valueText = ctx.adapter.getNodeText(valueNode, ctx.sourceCode);
|
|
1045
|
+
for (const name of names) {
|
|
1046
|
+
if (!ctx.graph.has(name))
|
|
1047
|
+
ctx.graph.set(name, valueText);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
/** Record parameter defaults (`function foo(x = <expr>)`) into the graph. */
|
|
1051
|
+
function recordParameterDefault(node, parent, ctx) {
|
|
1052
|
+
if (node.type !== 'assignment_pattern' || parent?.type !== 'formal_parameters') {
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const children = ctx.adapter.getChildren(node);
|
|
1056
|
+
if (children.length < 2)
|
|
1057
|
+
return;
|
|
1058
|
+
const leftNode = children[0];
|
|
1059
|
+
const rightNode = children[1];
|
|
1060
|
+
if (leftNode.type !== 'identifier')
|
|
1061
|
+
return;
|
|
1062
|
+
const paramName = ctx.adapter.getNodeText(leftNode, ctx.sourceCode);
|
|
1063
|
+
const valueText = ctx.adapter.getNodeText(rightNode, ctx.sourceCode);
|
|
1064
|
+
if (!ctx.graph.has(paramName))
|
|
1065
|
+
ctx.graph.set(paramName, valueText);
|
|
1066
|
+
}
|
|
1067
|
+
/** Record class-field initializers (`fieldName = <expr>`) into the graph. */
|
|
1068
|
+
function recordClassField(node, ctx) {
|
|
1069
|
+
if (node.type !== 'public_field_definition' &&
|
|
1070
|
+
node.type !== 'field_definition') {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const children = ctx.adapter.getChildren(node);
|
|
1074
|
+
const nameChild = children.find((c) => c.type === 'property_identifier');
|
|
1075
|
+
const valueChild = children.find((c) => c.type !== 'property_identifier' &&
|
|
1076
|
+
c.type !== 'decorator' &&
|
|
1077
|
+
c.type !== 'private' &&
|
|
1078
|
+
c.type !== 'public' &&
|
|
1079
|
+
c.type !== 'protected' &&
|
|
1080
|
+
c.type !== 'static' &&
|
|
1081
|
+
c.type !== 'readonly' &&
|
|
1082
|
+
c.type !== 'abstract' &&
|
|
1083
|
+
c.type !== '=');
|
|
1084
|
+
if (!nameChild || !valueChild)
|
|
1085
|
+
return;
|
|
1086
|
+
const fieldName = ctx.adapter.getNodeText(nameChild, ctx.sourceCode);
|
|
1087
|
+
const valueText = ctx.adapter.getNodeText(valueChild, ctx.sourceCode);
|
|
1088
|
+
if (!ctx.graph.has(fieldName))
|
|
1089
|
+
ctx.graph.set(fieldName, valueText);
|
|
965
1090
|
}
|
|
966
1091
|
/**
|
|
967
1092
|
* Trace a receiver identifier through the assignment graph to see if it
|
|
@@ -982,10 +1107,8 @@ function traceAssignmentChain(receiver, assignmentGraph, provenancedSet) {
|
|
|
982
1107
|
reason: provenancedSet.get(current).source,
|
|
983
1108
|
};
|
|
984
1109
|
}
|
|
985
|
-
// Also check if any identifier in the initializer text matches a provenanced source
|
|
986
1110
|
const initText = assignmentGraph.get(current);
|
|
987
1111
|
if (initText) {
|
|
988
|
-
// Extract identifiers from the initializer text and check against provenanced set
|
|
989
1112
|
const idents = extractTopLevelIdentifiers(initText);
|
|
990
1113
|
for (const ident of idents) {
|
|
991
1114
|
if (provenancedSet.has(ident)) {
|
|
@@ -998,27 +1121,28 @@ function traceAssignmentChain(receiver, assignmentGraph, provenancedSet) {
|
|
|
998
1121
|
}
|
|
999
1122
|
}
|
|
1000
1123
|
}
|
|
1001
|
-
// Move to the next link in the chain
|
|
1002
1124
|
if (!initText || visited.has(initText))
|
|
1003
1125
|
break;
|
|
1004
1126
|
visited.add(initText);
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
let foundNext = false;
|
|
1008
|
-
for (const ident of idents) {
|
|
1009
|
-
if (assignmentGraph.has(ident) && !visited.has(ident)) {
|
|
1010
|
-
chain.push(ident);
|
|
1011
|
-
current = ident;
|
|
1012
|
-
foundNext = true;
|
|
1013
|
-
break;
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
if (!foundNext)
|
|
1127
|
+
const next = findNextTraceIdentifier(initText, assignmentGraph, visited);
|
|
1128
|
+
if (!next)
|
|
1017
1129
|
break;
|
|
1130
|
+
chain.push(next);
|
|
1131
|
+
current = next;
|
|
1018
1132
|
depth++;
|
|
1019
1133
|
}
|
|
1020
1134
|
return { found: false, chains: [], reason: '' };
|
|
1021
1135
|
}
|
|
1136
|
+
/** Find the next assignment-graph identifier to trace, if any. */
|
|
1137
|
+
function findNextTraceIdentifier(initText, assignmentGraph, visited) {
|
|
1138
|
+
const idents = extractTopLevelIdentifiers(initText);
|
|
1139
|
+
for (const ident of idents) {
|
|
1140
|
+
if (assignmentGraph.has(ident) && !visited.has(ident)) {
|
|
1141
|
+
return ident;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1022
1146
|
/**
|
|
1023
1147
|
* Extract top-level identifier names from an expression text.
|
|
1024
1148
|
* E.g., "db.prepare(sql)" → ["db"], "getConnection()" → ["getConnection"]
|