pi-lens 3.6.2 → 3.6.4
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 +10 -2
- package/package.json +4 -4
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/file-time.test.ts +0 -276
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/format-service.test.ts +0 -339
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/__tests__/formatters.test.ts +0 -401
- package/clients/agent-behavior-client.js +0 -110
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/agent-behavior-client.test.ts +0 -116
- package/clients/amain-types.js +0 -164
- package/clients/architect-client.js +0 -291
- package/clients/ast-grep-client.js +0 -253
- package/clients/ast-grep-parser.js +0 -84
- package/clients/ast-grep-rule-manager.js +0 -89
- package/clients/ast-grep-types.js +0 -9
- package/clients/auto-loop.js +0 -131
- package/clients/biome-client.js +0 -420
- package/clients/biome-client.test.js +0 -144
- package/clients/biome-client.test.ts +0 -163
- package/clients/cache/rule-cache.js +0 -72
- package/clients/cache-manager.js +0 -245
- package/clients/cache-manager.test.js +0 -197
- package/clients/cache-manager.test.ts +0 -299
- package/clients/complexity-client.js +0 -675
- package/clients/complexity-client.test.js +0 -234
- package/clients/complexity-client.test.ts +0 -255
- package/clients/config-validator.js +0 -465
- package/clients/dependency-checker.js +0 -325
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dependency-checker.test.ts +0 -71
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
- package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
- package/clients/dispatch/debug.log +0 -1
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.edge.test.ts +0 -100
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.format.test.ts +0 -58
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.inline.test.ts +0 -93
- package/clients/dispatch/dispatcher.js +0 -381
- package/clients/dispatch/dispatcher.test.js +0 -116
- package/clients/dispatch/dispatcher.test.ts +0 -149
- package/clients/dispatch/integration.js +0 -108
- package/clients/dispatch/plan.js +0 -183
- package/clients/dispatch/runners/architect.js +0 -83
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/architect.test.ts +0 -162
- package/clients/dispatch/runners/ast-grep-napi.js +0 -405
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
- package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
- package/clients/dispatch/runners/ast-grep.js +0 -157
- package/clients/dispatch/runners/biome.js +0 -55
- package/clients/dispatch/runners/config-validation.js +0 -67
- package/clients/dispatch/runners/go-vet.js +0 -48
- package/clients/dispatch/runners/index.js +0 -47
- package/clients/dispatch/runners/lsp.js +0 -102
- package/clients/dispatch/runners/oxlint.js +0 -67
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/oxlint.test.ts +0 -303
- package/clients/dispatch/runners/pyright.js +0 -100
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/pyright.test.ts +0 -121
- package/clients/dispatch/runners/python-slop.js +0 -97
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/python-slop.test.ts +0 -298
- package/clients/dispatch/runners/ruff.js +0 -48
- package/clients/dispatch/runners/rust-clippy.js +0 -102
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
- package/clients/dispatch/runners/shellcheck.js +0 -147
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/shellcheck.test.ts +0 -129
- package/clients/dispatch/runners/similarity.js +0 -230
- package/clients/dispatch/runners/spellcheck.js +0 -106
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/spellcheck.test.ts +0 -214
- package/clients/dispatch/runners/tree-sitter.js +0 -246
- package/clients/dispatch/runners/ts-lsp.js +0 -125
- package/clients/dispatch/runners/ts-slop.js +0 -113
- package/clients/dispatch/runners/type-safety.js +0 -142
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
- package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
- package/clients/dispatch/runners/utils.js +0 -51
- package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
- package/clients/dispatch/types.js +0 -16
- package/clients/dispatch/utils/format-utils.js +0 -44
- package/clients/dogfood.test.js +0 -201
- package/clients/dogfood.test.ts +0 -269
- package/clients/file-kinds.js +0 -177
- package/clients/file-kinds.test.js +0 -169
- package/clients/file-kinds.test.ts +0 -210
- package/clients/file-time.js +0 -152
- package/clients/file-utils.js +0 -40
- package/clients/fix-scanners.js +0 -204
- package/clients/format-service.js +0 -184
- package/clients/formatters.js +0 -488
- package/clients/go-client.js +0 -203
- package/clients/go-client.test.js +0 -127
- package/clients/go-client.test.ts +0 -143
- package/clients/installer/index.js +0 -403
- package/clients/interviewer-templates.js +0 -75
- package/clients/interviewer.js +0 -173
- package/clients/jscpd-client.js +0 -196
- package/clients/jscpd-client.test.js +0 -127
- package/clients/jscpd-client.test.ts +0 -145
- package/clients/knip-client.js +0 -239
- package/clients/knip-client.test.js +0 -112
- package/clients/knip-client.test.ts +0 -128
- package/clients/latency-logger.js +0 -40
- package/clients/lsp/__tests__/client.test.js +0 -310
- package/clients/lsp/__tests__/client.test.ts +0 -412
- package/clients/lsp/__tests__/config.test.js +0 -167
- package/clients/lsp/__tests__/config.test.ts +0 -217
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/integration.test.ts +0 -160
- package/clients/lsp/__tests__/launch.test.js +0 -313
- package/clients/lsp/__tests__/launch.test.ts +0 -394
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/server.test.ts +0 -332
- package/clients/lsp/__tests__/service.test.js +0 -438
- package/clients/lsp/__tests__/service.test.ts +0 -530
- package/clients/lsp/client.js +0 -350
- package/clients/lsp/config.js +0 -112
- package/clients/lsp/index.js +0 -318
- package/clients/lsp/installer/index.js +0 -391
- package/clients/lsp/interactive-install.js +0 -221
- package/clients/lsp/language.js +0 -170
- package/clients/lsp/launch.js +0 -329
- package/clients/lsp/lsp/launch.js +0 -116
- package/clients/lsp/lsp/server.js +0 -532
- package/clients/lsp/lsp-index.js +0 -10
- package/clients/lsp/path-utils.js +0 -5
- package/clients/lsp/server.js +0 -725
- package/clients/lsp/test-py-spawn/requirements.txt +0 -1
- package/clients/lsp/test-py-spawn/test.py +0 -3
- package/clients/lsp/test-py-svc/requirements.txt +0 -1
- package/clients/lsp/test-py-svc/test.py +0 -3
- package/clients/lsp/test-python-project/requirements.txt +0 -1
- package/clients/lsp/test-python-project/test.py +0 -5
- package/clients/metrics-client.js +0 -107
- package/clients/metrics-client.test.js +0 -128
- package/clients/metrics-client.test.ts +0 -163
- package/clients/metrics-history.js +0 -367
- package/clients/path-utils.js +0 -142
- package/clients/pipeline.js +0 -272
- package/clients/production-readiness.js +0 -522
- package/clients/project-index.js +0 -255
- package/clients/project-metadata.js +0 -531
- package/clients/ruff-client.js +0 -325
- package/clients/ruff-client.test.js +0 -132
- package/clients/ruff-client.test.ts +0 -153
- package/clients/rules-scanner.js +0 -97
- package/clients/runner-tracker.js +0 -152
- package/clients/rust-client.js +0 -205
- package/clients/rust-client.test.js +0 -108
- package/clients/rust-client.test.ts +0 -130
- package/clients/safe-spawn-async.js +0 -163
- package/clients/safe-spawn.js +0 -241
- package/clients/sanitize.js +0 -291
- package/clients/sanitize.test.js +0 -177
- package/clients/sanitize.test.ts +0 -223
- package/clients/scan-architectural-debt.js +0 -167
- package/clients/scan-utils.js +0 -83
- package/clients/secrets-scanner.js +0 -119
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/secrets-scanner.test.ts +0 -113
- package/clients/sg-runner.js +0 -292
- package/clients/state-matrix.js +0 -160
- package/clients/subprocess-client.js +0 -65
- package/clients/symbol-types.js +0 -5
- package/clients/test-runner-client.js +0 -523
- package/clients/test-runner-client.test.js +0 -192
- package/clients/test-runner-client.test.ts +0 -253
- package/clients/test-utils.js +0 -27
- package/clients/test-utils.ts +0 -36
- package/clients/todo-scanner.js +0 -200
- package/clients/todo-scanner.test.js +0 -301
- package/clients/todo-scanner.test.ts +0 -352
- package/clients/tool-availability.js +0 -207
- package/clients/tree-sitter-client.js +0 -601
- package/clients/tree-sitter-query-loader.js +0 -355
- package/clients/tree-sitter-symbol-extractor.js +0 -289
- package/clients/ts-service.js +0 -129
- package/clients/type-coverage-client.js +0 -127
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/type-coverage-client.test.ts +0 -125
- package/clients/type-safety-client.js +0 -138
- package/clients/types.js +0 -11
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.codefix.test.ts +0 -186
- package/clients/typescript-client.js +0 -509
- package/clients/typescript-client.test.js +0 -105
- package/clients/typescript-client.test.ts +0 -126
- package/commands/booboo.js +0 -1007
- package/commands/fix-from-booboo.js +0 -398
- package/commands/fix-simplified.js +0 -618
- package/commands/rate.js +0 -281
- package/commands/rate.test.js +0 -119
- package/commands/rate.test.ts +0 -131
- package/commands/refactor.js +0 -130
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Config Validation via Tree-sitter
|
|
3
|
-
*
|
|
4
|
-
* Detects config/environment variable access in code and validates against
|
|
5
|
-
* actual config files (INI, YAML, JSON, .env).
|
|
6
|
-
*
|
|
7
|
-
* Catches:
|
|
8
|
-
* - Undefined config keys
|
|
9
|
-
* - Typos in config keys
|
|
10
|
-
* - Missing environment variables
|
|
11
|
-
* - Deprecated/renamed keys
|
|
12
|
-
*
|
|
13
|
-
* Supported patterns:
|
|
14
|
-
* - Python: config.get("section.key"), os.environ.get("VAR")
|
|
15
|
-
* - JS/TS: process.env.VAR, config.get("key")
|
|
16
|
-
* - Go: os.Getenv("VAR")
|
|
17
|
-
* - Rust: env::var("VAR")
|
|
18
|
-
*/
|
|
19
|
-
import * as fs from "node:fs/promises";
|
|
20
|
-
import * as path from "node:path";
|
|
21
|
-
import { TreeSitterClient } from "./tree-sitter-client.js";
|
|
22
|
-
// --- Tree-sitter Queries for Config Access Patterns ---
|
|
23
|
-
const CONFIG_QUERIES = {
|
|
24
|
-
// Python: config.get("section.key") or os.environ.get("VAR")
|
|
25
|
-
python: `
|
|
26
|
-
; Config object access: config.get("key")
|
|
27
|
-
(call
|
|
28
|
-
function: (attribute
|
|
29
|
-
object: (identifier) @config_obj
|
|
30
|
-
attribute: (identifier) @method (#eq? @method "get") )
|
|
31
|
-
arguments: (argument_list
|
|
32
|
-
(string
|
|
33
|
-
(string_content) @config_key
|
|
34
|
-
)
|
|
35
|
-
)
|
|
36
|
-
)
|
|
37
|
-
(#match? @config_obj "^(config|cfg|settings|conf)$")
|
|
38
|
-
|
|
39
|
-
; os.environ.get("VAR")
|
|
40
|
-
(call
|
|
41
|
-
function: (attribute
|
|
42
|
-
object: (attribute
|
|
43
|
-
object: (identifier) @os (#eq? @os "os")
|
|
44
|
-
attribute: (identifier) @environ (#eq? @environ "environ")
|
|
45
|
-
)
|
|
46
|
-
attribute: (identifier) @method (#eq? @method "get")
|
|
47
|
-
)
|
|
48
|
-
arguments: (argument_list
|
|
49
|
-
(string (string_content) @env_var)
|
|
50
|
-
)
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
; os.getenv("VAR")
|
|
54
|
-
(call
|
|
55
|
-
function: (attribute
|
|
56
|
-
object: (identifier) @os (#eq? @os "os")
|
|
57
|
-
attribute: (identifier) @getenv (#eq? @getenv "getenv")
|
|
58
|
-
)
|
|
59
|
-
arguments: (argument_list
|
|
60
|
-
(string (string_content) @env_var)
|
|
61
|
-
)
|
|
62
|
-
)
|
|
63
|
-
`,
|
|
64
|
-
// JavaScript/TypeScript: process.env.VAR or config.get("key")
|
|
65
|
-
javascript: `
|
|
66
|
-
; process.env.VAR or process.env["VAR"]
|
|
67
|
-
(member_expression
|
|
68
|
-
object: (member_expression
|
|
69
|
-
object: (identifier) @process (#eq? @process "process")
|
|
70
|
-
property: (property_identifier) @env (#eq? @env "env")
|
|
71
|
-
)
|
|
72
|
-
property: (property_identifier) @env_var
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
; process.env["VAR"]
|
|
76
|
-
(member_expression
|
|
77
|
-
object: (member_expression
|
|
78
|
-
object: (identifier) @process (#eq? @process "process")
|
|
79
|
-
property: (property_identifier) @env (#eq? @env "env")
|
|
80
|
-
)
|
|
81
|
-
property: (computed_property_name
|
|
82
|
-
(string (string_fragment) @env_var)
|
|
83
|
-
)
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
; config.get("key") or cfg.get("key")
|
|
87
|
-
(call_expression
|
|
88
|
-
function: (member_expression
|
|
89
|
-
object: (identifier) @config_obj
|
|
90
|
-
property: (property_identifier) @method (#eq? @method "get")
|
|
91
|
-
)
|
|
92
|
-
arguments: (arguments
|
|
93
|
-
(string (string_fragment) @config_key)
|
|
94
|
-
)
|
|
95
|
-
)
|
|
96
|
-
(#match? @config_obj "^(config|cfg|settings|conf)$")
|
|
97
|
-
`,
|
|
98
|
-
// Same for TypeScript (tsx)
|
|
99
|
-
tsx: `
|
|
100
|
-
; process.env.VAR
|
|
101
|
-
(member_expression
|
|
102
|
-
object: (member_expression
|
|
103
|
-
object: (identifier) @process (#eq? @process "process")
|
|
104
|
-
property: (property_identifier) @env (#eq? @env "env")
|
|
105
|
-
)
|
|
106
|
-
property: (property_identifier) @env_var
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
; config.get("key")
|
|
110
|
-
(call_expression
|
|
111
|
-
function: (member_expression
|
|
112
|
-
object: (identifier) @config_obj
|
|
113
|
-
property: (property_identifier) @method (#eq? @method "get")
|
|
114
|
-
)
|
|
115
|
-
arguments: (arguments
|
|
116
|
-
(string (string_fragment) @config_key)
|
|
117
|
-
)
|
|
118
|
-
)
|
|
119
|
-
(#match? @config_obj "^(config|cfg|settings|conf)$")
|
|
120
|
-
`,
|
|
121
|
-
// Go: os.Getenv("VAR")
|
|
122
|
-
go: `
|
|
123
|
-
(call_expression
|
|
124
|
-
function: (selector_expression
|
|
125
|
-
operand: (identifier) @os (#eq? @os "os")
|
|
126
|
-
field: (field_identifier) @getenv (#eq? @getenv "Getenv")
|
|
127
|
-
)
|
|
128
|
-
arguments: (argument_list
|
|
129
|
-
(raw_string_literal) @env_var
|
|
130
|
-
)
|
|
131
|
-
)
|
|
132
|
-
`,
|
|
133
|
-
// Rust: env::var("VAR") or std::env::var("VAR")
|
|
134
|
-
rust: `
|
|
135
|
-
(call_expression
|
|
136
|
-
function: (scoped_identifier
|
|
137
|
-
path: (identifier) @env (#eq? @env "env")
|
|
138
|
-
name: (identifier) @var (#eq? @var "var")
|
|
139
|
-
)
|
|
140
|
-
arguments: (arguments
|
|
141
|
-
(string_literal) @env_var
|
|
142
|
-
)
|
|
143
|
-
)
|
|
144
|
-
`,
|
|
145
|
-
};
|
|
146
|
-
// --- Config File Parsers ---
|
|
147
|
-
async function parseEnvFile(filePath) {
|
|
148
|
-
const keys = [];
|
|
149
|
-
try {
|
|
150
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
151
|
-
const lines = content.split("\n");
|
|
152
|
-
for (let i = 0; i < lines.length; i++) {
|
|
153
|
-
const line = lines[i].trim();
|
|
154
|
-
// Skip comments and empty lines
|
|
155
|
-
if (line.startsWith("#") || line.startsWith("//") || !line)
|
|
156
|
-
continue;
|
|
157
|
-
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
158
|
-
if (match) {
|
|
159
|
-
keys.push({
|
|
160
|
-
key: match[1],
|
|
161
|
-
file: filePath,
|
|
162
|
-
line: i + 1,
|
|
163
|
-
value: match[2].trim(),
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
// File doesn't exist or can't be read
|
|
170
|
-
}
|
|
171
|
-
return keys;
|
|
172
|
-
}
|
|
173
|
-
async function parseIniFile(filePath) {
|
|
174
|
-
const keys = [];
|
|
175
|
-
try {
|
|
176
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
177
|
-
const lines = content.split("\n");
|
|
178
|
-
let currentSection = "";
|
|
179
|
-
for (let i = 0; i < lines.length; i++) {
|
|
180
|
-
const line = lines[i].trim();
|
|
181
|
-
if (!line || line.startsWith(";") || line.startsWith("#"))
|
|
182
|
-
continue;
|
|
183
|
-
// Section header: [section]
|
|
184
|
-
const sectionMatch = line.match(/^\[([^\]]+)\]$/);
|
|
185
|
-
if (sectionMatch) {
|
|
186
|
-
currentSection = sectionMatch[1];
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
// Key = value
|
|
190
|
-
const keyMatch = line.match(/^([^=]+)\s*=\s*(.*)$/);
|
|
191
|
-
if (keyMatch) {
|
|
192
|
-
const key = keyMatch[1].trim();
|
|
193
|
-
const fullKey = currentSection ? `${currentSection}.${key}` : key;
|
|
194
|
-
keys.push({
|
|
195
|
-
key: fullKey,
|
|
196
|
-
file: filePath,
|
|
197
|
-
line: i + 1,
|
|
198
|
-
value: keyMatch[2].trim(),
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
// File doesn't exist or can't be read
|
|
205
|
-
}
|
|
206
|
-
return keys;
|
|
207
|
-
}
|
|
208
|
-
async function parseYamlConfig(filePath) {
|
|
209
|
-
const keys = [];
|
|
210
|
-
try {
|
|
211
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
212
|
-
// Simple YAML parser for flat key: value or section.key format
|
|
213
|
-
const lines = content.split("\n");
|
|
214
|
-
let indentStack = [];
|
|
215
|
-
for (let i = 0; i < lines.length; i++) {
|
|
216
|
-
const line = lines[i];
|
|
217
|
-
const trimmed = line.trim();
|
|
218
|
-
// Skip comments and empty lines
|
|
219
|
-
if (!trimmed || trimmed.startsWith("#"))
|
|
220
|
-
continue;
|
|
221
|
-
// Calculate indent
|
|
222
|
-
const indent = line.search(/\S/);
|
|
223
|
-
const _indentMatch = indentStack.find((s) => s.indent === indent);
|
|
224
|
-
// key: value pattern
|
|
225
|
-
const match = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*):\s*(.*)$/);
|
|
226
|
-
if (match) {
|
|
227
|
-
const key = match[1];
|
|
228
|
-
const value = match[2].trim();
|
|
229
|
-
// Build full key path
|
|
230
|
-
const parentKeys = indentStack
|
|
231
|
-
.filter((s) => s.indent < indent)
|
|
232
|
-
.map((s) => s.key);
|
|
233
|
-
const fullKey = [...parentKeys, key].join(".");
|
|
234
|
-
// If has value, it's a config key
|
|
235
|
-
if (value && !value.endsWith(":")) {
|
|
236
|
-
keys.push({
|
|
237
|
-
key: fullKey,
|
|
238
|
-
file: filePath,
|
|
239
|
-
line: i + 1,
|
|
240
|
-
value: value,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
// Update indent stack
|
|
244
|
-
indentStack = indentStack.filter((s) => s.indent < indent);
|
|
245
|
-
indentStack.push({ indent, key });
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
// File doesn't exist or can't be read
|
|
251
|
-
}
|
|
252
|
-
return keys;
|
|
253
|
-
}
|
|
254
|
-
async function parseJsonConfig(filePath) {
|
|
255
|
-
const keys = [];
|
|
256
|
-
try {
|
|
257
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
258
|
-
const obj = JSON.parse(content);
|
|
259
|
-
function traverse(obj, path = []) {
|
|
260
|
-
if (typeof obj === "object" && obj !== null) {
|
|
261
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
262
|
-
const newPath = [...path, key];
|
|
263
|
-
if (typeof value === "string" ||
|
|
264
|
-
typeof value === "number" ||
|
|
265
|
-
typeof value === "boolean") {
|
|
266
|
-
keys.push({
|
|
267
|
-
key: newPath.join("."),
|
|
268
|
-
file: filePath,
|
|
269
|
-
line: 0, // JSON doesn't preserve line numbers easily
|
|
270
|
-
value: String(value),
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
else if (typeof value === "object") {
|
|
274
|
-
traverse(value, newPath);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
traverse(obj);
|
|
280
|
-
}
|
|
281
|
-
catch {
|
|
282
|
-
// File doesn't exist or invalid JSON
|
|
283
|
-
}
|
|
284
|
-
return keys;
|
|
285
|
-
}
|
|
286
|
-
// --- Main Config Validator ---
|
|
287
|
-
export class ConfigValidator {
|
|
288
|
-
constructor() {
|
|
289
|
-
this.availableKeys = new Map();
|
|
290
|
-
this.client = new TreeSitterClient();
|
|
291
|
-
}
|
|
292
|
-
async init() {
|
|
293
|
-
await this.client.init();
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Scan project for config files
|
|
297
|
-
*/
|
|
298
|
-
async scanConfigFiles(cwd) {
|
|
299
|
-
const configFiles = [
|
|
300
|
-
{ pattern: ".env", parser: parseEnvFile },
|
|
301
|
-
{ pattern: ".env.local", parser: parseEnvFile },
|
|
302
|
-
{ pattern: ".env.development", parser: parseEnvFile },
|
|
303
|
-
{ pattern: ".env.production", parser: parseEnvFile },
|
|
304
|
-
{ pattern: "config.ini", parser: parseIniFile },
|
|
305
|
-
{ pattern: "config.yaml", parser: parseYamlConfig },
|
|
306
|
-
{ pattern: "config.yml", parser: parseYamlConfig },
|
|
307
|
-
{ pattern: "config.json", parser: parseJsonConfig },
|
|
308
|
-
{ pattern: "pyproject.toml", parser: parseIniFile }, // Simplified
|
|
309
|
-
{ pattern: "package.json", parser: parseJsonConfig },
|
|
310
|
-
{ pattern: "app.yaml", parser: parseYamlConfig },
|
|
311
|
-
{ pattern: "application.yaml", parser: parseYamlConfig },
|
|
312
|
-
];
|
|
313
|
-
for (const { pattern, parser } of configFiles) {
|
|
314
|
-
const filePath = path.join(cwd, pattern);
|
|
315
|
-
const keys = await parser(filePath);
|
|
316
|
-
if (keys.length > 0) {
|
|
317
|
-
this.availableKeys.set(pattern, keys);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
// Also scan for any .env.* files
|
|
321
|
-
try {
|
|
322
|
-
const entries = await fs.readdir(cwd);
|
|
323
|
-
for (const entry of entries) {
|
|
324
|
-
if (entry.startsWith(".env.")) {
|
|
325
|
-
const filePath = path.join(cwd, entry);
|
|
326
|
-
const keys = await parseEnvFile(filePath);
|
|
327
|
-
if (keys.length > 0) {
|
|
328
|
-
this.availableKeys.set(entry, keys);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
catch {
|
|
334
|
-
// Can't read directory
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Validate config access in a source file
|
|
339
|
-
*/
|
|
340
|
-
async validateFile(filePath) {
|
|
341
|
-
const languageId = this.getLanguageId(filePath);
|
|
342
|
-
if (!languageId || !CONFIG_QUERIES[languageId]) {
|
|
343
|
-
return { undefined: [], typos: [], available: [] };
|
|
344
|
-
}
|
|
345
|
-
// Get all config accesses in the file
|
|
346
|
-
const accesses = await this.findConfigAccesses(filePath, languageId);
|
|
347
|
-
// Get all available keys
|
|
348
|
-
const allAvailable = [];
|
|
349
|
-
for (const keys of this.availableKeys.values()) {
|
|
350
|
-
allAvailable.push(...keys);
|
|
351
|
-
}
|
|
352
|
-
const undefined = [];
|
|
353
|
-
const typos = [];
|
|
354
|
-
for (const access of accesses) {
|
|
355
|
-
// Check if key exists
|
|
356
|
-
const exactMatch = allAvailable.find((k) => k.key.toLowerCase() === access.key.toLowerCase());
|
|
357
|
-
if (!exactMatch) {
|
|
358
|
-
// Check for typos using fuzzy matching
|
|
359
|
-
const suggestion = this.findClosestMatch(access.key, allAvailable.map((k) => k.key));
|
|
360
|
-
if (suggestion &&
|
|
361
|
-
this.calculateSimilarity(access.key, suggestion) > 0.7) {
|
|
362
|
-
typos.push({ access, suggestion });
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
undefined.push(access);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return { undefined, typos, available: allAvailable };
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Find all config accesses in a file using tree-sitter
|
|
373
|
-
*/
|
|
374
|
-
async findConfigAccesses(filePath, languageId) {
|
|
375
|
-
const query = CONFIG_QUERIES[languageId];
|
|
376
|
-
const matches = await this.client.structuralSearch(query, languageId, path.dirname(filePath), { fileFilter: (f) => f === filePath });
|
|
377
|
-
const accesses = [];
|
|
378
|
-
for (const match of matches) {
|
|
379
|
-
const configKeyCapture = match.captures.config_key || match.captures.env_var;
|
|
380
|
-
if (configKeyCapture) {
|
|
381
|
-
// Clean up the key (remove quotes, etc.)
|
|
382
|
-
const key = configKeyCapture.replace(/^["'`]|["'`]$/g, "");
|
|
383
|
-
accesses.push({
|
|
384
|
-
key,
|
|
385
|
-
file: filePath,
|
|
386
|
-
line: match.line,
|
|
387
|
-
column: match.column,
|
|
388
|
-
pattern: match.matchedText,
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return accesses;
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* Calculate string similarity (Levenshtein-based)
|
|
396
|
-
*/
|
|
397
|
-
calculateSimilarity(a, b) {
|
|
398
|
-
const matrix = [];
|
|
399
|
-
for (let i = 0; i <= b.length; i++) {
|
|
400
|
-
matrix[i] = [i];
|
|
401
|
-
}
|
|
402
|
-
for (let j = 0; j <= a.length; j++) {
|
|
403
|
-
matrix[0][j] = j;
|
|
404
|
-
}
|
|
405
|
-
for (let i = 1; i <= b.length; i++) {
|
|
406
|
-
for (let j = 1; j <= a.length; j++) {
|
|
407
|
-
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
408
|
-
matrix[i][j] = matrix[i - 1][j - 1];
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
|
|
412
|
-
matrix[i][j - 1] + 1, // insertion
|
|
413
|
-
matrix[i - 1][j] + 1);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
const distance = matrix[b.length][a.length];
|
|
418
|
-
const maxLength = Math.max(a.length, b.length);
|
|
419
|
-
return 1 - distance / maxLength;
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Find the closest matching key
|
|
423
|
-
*/
|
|
424
|
-
findClosestMatch(key, candidates) {
|
|
425
|
-
let bestMatch;
|
|
426
|
-
let bestScore = 0;
|
|
427
|
-
for (const candidate of candidates) {
|
|
428
|
-
const score = this.calculateSimilarity(key, candidate);
|
|
429
|
-
if (score > bestScore && score > 0.5) {
|
|
430
|
-
bestScore = score;
|
|
431
|
-
bestMatch = candidate;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
return bestMatch;
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* Map file extension to language ID
|
|
438
|
-
*/
|
|
439
|
-
getLanguageId(filePath) {
|
|
440
|
-
const ext = path.extname(filePath);
|
|
441
|
-
switch (ext) {
|
|
442
|
-
case ".py":
|
|
443
|
-
return "python";
|
|
444
|
-
case ".js":
|
|
445
|
-
return "javascript";
|
|
446
|
-
case ".ts":
|
|
447
|
-
return "typescript";
|
|
448
|
-
case ".tsx":
|
|
449
|
-
return "tsx";
|
|
450
|
-
case ".go":
|
|
451
|
-
return "go";
|
|
452
|
-
case ".rs":
|
|
453
|
-
return "rust";
|
|
454
|
-
default:
|
|
455
|
-
return undefined;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
// --- Simple factory function ---
|
|
460
|
-
export async function createConfigValidator(cwd) {
|
|
461
|
-
const validator = new ConfigValidator();
|
|
462
|
-
await validator.init();
|
|
463
|
-
await validator.scanConfigFiles(cwd);
|
|
464
|
-
return validator;
|
|
465
|
-
}
|