Skip to content

Commit 9ab7caa

Browse files
🎨 Add module level guard for string variables
1 parent 5891cc5 commit 9ab7caa

2 files changed

Lines changed: 59 additions & 0 deletions

File tree

‎src/core/extractors.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,27 @@ function collectNodesByType(node: Node, type: string, results: Node[]): void {
6565
* Collects string variable assignments from the AST for path resolution.
6666
* Handles simple assignments like `WEBHOOK_PATH = "/webhook"`.
6767
*
68+
* Only module-level assignments are collected — function/class-local variables
69+
* are skipped to prevent shadowing module-level constants with the same name.
70+
*
6871
* Examples:
6972
* WEBHOOK_PATH = "/webhook" -> Map { "WEBHOOK_PATH" => "/webhook" }
7073
* BASE = "/api" -> Map { "BASE" => "/api" }
7174
* settings.PREFIX = "/api" -> (skipped, not a simple identifier)
75+
* def f(): BASE = "/local" -> (skipped, inside function)
7276
*/
7377
export function collectStringVariables(rootNode: Node): Map<string, string> {
7478
const variables = new Map<string, string>()
7579
const assignmentNodes = findNodesByType(rootNode, "assignment")
7680

7781
for (const assign of assignmentNodes) {
82+
if (
83+
hasAncestor(assign, "function_definition") ||
84+
hasAncestor(assign, "class_definition")
85+
) {
86+
continue
87+
}
88+
7889
const left = assign.childForFieldName("left")
7990
const right = assign.childForFieldName("right")
8091
if (

‎src/test/core/extractors.test.ts‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as assert from "node:assert"
22
import {
33
collectRecognizedNames,
4+
collectStringVariables,
45
decoratorExtractor,
56
extractPathFromNode,
67
extractStringValue,
@@ -863,6 +864,53 @@ router = f.APIRouter(prefix="/items")
863864
})
864865
})
865866

867+
suite("collectStringVariables", () => {
868+
test("collects module-level string assignments", () => {
869+
const code = `
870+
PREFIX = "/api"
871+
VERSION = "/v1"
872+
`
873+
const tree = parse(code)
874+
const vars = collectStringVariables(tree.rootNode)
875+
assert.strictEqual(vars.get("PREFIX"), "/api")
876+
assert.strictEqual(vars.get("VERSION"), "/v1")
877+
})
878+
879+
test("ignores function-local variables", () => {
880+
const code = `
881+
PREFIX = "/api"
882+
883+
def handler():
884+
PREFIX = "/local"
885+
`
886+
const tree = parse(code)
887+
const vars = collectStringVariables(tree.rootNode)
888+
assert.strictEqual(vars.get("PREFIX"), "/api")
889+
})
890+
891+
test("ignores class-level variables", () => {
892+
const code = `
893+
PREFIX = "/api"
894+
895+
class Config:
896+
PREFIX = "/class-level"
897+
`
898+
const tree = parse(code)
899+
const vars = collectStringVariables(tree.rootNode)
900+
assert.strictEqual(vars.get("PREFIX"), "/api")
901+
})
902+
903+
test("ignores non-string assignments", () => {
904+
const code = `
905+
COUNT = 42
906+
FLAG = True
907+
`
908+
const tree = parse(code)
909+
const vars = collectStringVariables(tree.rootNode)
910+
assert.strictEqual(vars.size, 0)
911+
})
912+
})
913+
866914
suite("extractStringValue", () => {
867915
test("returns null for non-string node", () => {
868916
const code = "x = 42"

0 commit comments

Comments
 (0)