deep-slop 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3899,636 +3899,192 @@ var init_file_cache = __esm({
3899
3899
  }
3900
3900
  });
3901
3901
 
3902
- // src/plugins/loader.ts
3903
- async function loadPlugin(pluginPath) {
3904
- try {
3905
- const mod = await import(pluginPath);
3906
- const engine = mod.default;
3907
- if (!engine || typeof engine !== "object") {
3908
- return null;
3902
+ // src/utils/tree-sitter.ts
3903
+ function clearParseCache() {
3904
+ parseCache.clear();
3905
+ }
3906
+ async function initParser() {
3907
+ if (initDone) return initOk;
3908
+ if (initPromise) return initPromise;
3909
+ initPromise = (async () => {
3910
+ try {
3911
+ const wt = await import("web-tree-sitter");
3912
+ const { dirname: dirname9 } = await import("node:path");
3913
+ const wasmDir = dirname9(
3914
+ __require.resolve("web-tree-sitter/tree-sitter.wasm")
3915
+ );
3916
+ await wt.Parser.init({
3917
+ locateFile: (name) => `${wasmDir}/${name}`
3918
+ });
3919
+ const parser = new wt.Parser();
3920
+ parserInstance = parser;
3921
+ const tsWasm = __require.resolve(
3922
+ "tree-sitter-typescript/tree-sitter-typescript.wasm"
3923
+ );
3924
+ tsLang = await wt.Language.load(tsWasm);
3925
+ const tsxWasm = __require.resolve(
3926
+ "tree-sitter-typescript/tree-sitter-tsx.wasm"
3927
+ );
3928
+ tsxLang = await wt.Language.load(tsxWasm);
3929
+ initDone = true;
3930
+ initOk = true;
3931
+ return true;
3932
+ } catch (err) {
3933
+ initDone = true;
3934
+ initOk = false;
3935
+ return false;
3909
3936
  }
3910
- if (typeof engine.name === "string" && typeof engine.description === "string" && Array.isArray(engine.supportedLanguages) && typeof engine.run === "function") {
3911
- return engine;
3937
+ })();
3938
+ return initPromise;
3939
+ }
3940
+ async function initPythonParser() {
3941
+ if (pyInitDone) return pyInitOk;
3942
+ if (!initOk) {
3943
+ const baseOk = await initParser();
3944
+ if (!baseOk) {
3945
+ pyInitDone = true;
3946
+ pyInitOk = false;
3947
+ return false;
3912
3948
  }
3913
- return null;
3949
+ }
3950
+ try {
3951
+ const pyWasm = __require.resolve(
3952
+ "tree-sitter-python/python.wasm"
3953
+ );
3954
+ const wt = await import("web-tree-sitter");
3955
+ pyLang = await wt.Language.load(pyWasm);
3956
+ pyInitDone = true;
3957
+ pyInitOk = true;
3958
+ return true;
3914
3959
  } catch {
3915
- return null;
3960
+ pyInitDone = true;
3961
+ pyInitOk = false;
3962
+ return false;
3916
3963
  }
3917
3964
  }
3918
- async function loadPlugins(paths) {
3919
- const results = await Promise.allSettled(
3920
- paths.map((p) => loadPlugin(p))
3921
- );
3922
- const engines = [];
3923
- for (const r of results) {
3924
- if (r.status === "fulfilled" && r.value !== null) {
3925
- engines.push(r.value);
3965
+ async function initGoParser() {
3966
+ if (goInitDone) return goInitOk;
3967
+ if (!initOk) {
3968
+ const baseOk = await initParser();
3969
+ if (!baseOk) {
3970
+ goInitDone = true;
3971
+ goInitOk = false;
3972
+ return false;
3926
3973
  }
3927
3974
  }
3928
- return engines;
3929
- }
3930
- var init_loader = __esm({
3931
- "src/plugins/loader.ts"() {
3932
- "use strict";
3975
+ try {
3976
+ const goWasm = __require.resolve(
3977
+ "tree-sitter-go/go.wasm"
3978
+ );
3979
+ const wt = await import("web-tree-sitter");
3980
+ goLang = await wt.Language.load(goWasm);
3981
+ goInitDone = true;
3982
+ goInitOk = true;
3983
+ return true;
3984
+ } catch {
3985
+ goInitDone = true;
3986
+ goInitOk = false;
3987
+ return false;
3933
3988
  }
3934
- });
3935
-
3936
- // src/plugins/registry.ts
3937
- var registry_exports = {};
3938
- __export(registry_exports, {
3939
- PLUGIN_DIR: () => PLUGIN_DIR,
3940
- discoverAndLoadPlugins: () => discoverAndLoadPlugins,
3941
- getPluginDir: () => getPluginDir,
3942
- pluginRegistry: () => pluginRegistry
3943
- });
3944
- import { join as join2 } from "node:path";
3945
- import { readdir } from "node:fs/promises";
3946
- import { existsSync as existsSync2 } from "node:fs";
3947
- function getPluginDir(rootDir) {
3948
- return join2(rootDir, PLUGIN_DIR);
3949
3989
  }
3950
- async function discoverAndLoadPlugins(rootDir) {
3951
- if (pluginRegistry.isLoaded) {
3952
- return pluginRegistry.getEngines();
3990
+ async function initRustParser() {
3991
+ if (rustInitDone) return rustInitOk;
3992
+ if (!initOk) {
3993
+ const baseOk = await initParser();
3994
+ if (!baseOk) {
3995
+ rustInitDone = true;
3996
+ rustInitOk = false;
3997
+ return false;
3998
+ }
3953
3999
  }
3954
- const pluginDir = getPluginDir(rootDir);
3955
- if (!existsSync2(pluginDir)) {
3956
- pluginRegistry.setLoaded(true);
3957
- return [];
4000
+ try {
4001
+ const rustWasm = __require.resolve(
4002
+ "tree-sitter-rust/rust.wasm"
4003
+ );
4004
+ const wt = await import("web-tree-sitter");
4005
+ rustLang = await wt.Language.load(rustWasm);
4006
+ rustInitDone = true;
4007
+ rustInitOk = true;
4008
+ return true;
4009
+ } catch {
4010
+ rustInitDone = true;
4011
+ rustInitOk = false;
4012
+ return false;
4013
+ }
4014
+ }
4015
+ async function initPhpParser() {
4016
+ if (phpInitDone) return phpInitOk;
4017
+ if (!initOk) {
4018
+ const baseOk = await initParser();
4019
+ if (!baseOk) {
4020
+ phpInitDone = true;
4021
+ phpInitOk = false;
4022
+ return false;
4023
+ }
3958
4024
  }
3959
4025
  try {
3960
- const files = await readdir(pluginDir);
3961
- const pluginPaths = files.filter((f) => f.endsWith(".js") || f.endsWith(".mjs")).map((f) => join2(pluginDir, f));
3962
- const engines = await loadPlugins(pluginPaths);
3963
- for (let i = 0; i < engines.length; i++) {
3964
- const engine = engines[i];
3965
- pluginRegistry.register({
3966
- id: engine.name,
3967
- path: pluginPaths[i],
3968
- engine,
3969
- loaded: true
3970
- });
4026
+ const phpWasm = __require.resolve(
4027
+ "tree-sitter-php/php.wasm"
4028
+ );
4029
+ const wt = await import("web-tree-sitter");
4030
+ phpLang = await wt.Language.load(phpWasm);
4031
+ phpInitDone = true;
4032
+ phpInitOk = true;
4033
+ return true;
4034
+ } catch {
4035
+ phpInitDone = true;
4036
+ phpInitOk = false;
4037
+ return false;
4038
+ }
4039
+ }
4040
+ async function initCsharpParser() {
4041
+ if (csharpInitDone) return csharpInitOk;
4042
+ if (!initOk) {
4043
+ const baseOk = await initParser();
4044
+ if (!baseOk) {
4045
+ csharpInitDone = true;
4046
+ csharpInitOk = false;
4047
+ return false;
3971
4048
  }
3972
- for (let i = 0; i < pluginPaths.length; i++) {
3973
- const path2 = pluginPaths[i];
3974
- const engine = engines.find((e) => {
3975
- const entry = pluginRegistry.getAll().find((ent) => ent.path === path2);
3976
- return entry?.engine;
3977
- });
3978
- if (!engine && !pluginRegistry.has(path2)) {
3979
- pluginRegistry.register({
3980
- id: `plugin-${i}`,
3981
- path: path2,
3982
- engine: null,
3983
- loaded: false,
3984
- error: "Failed to load plugin module"
3985
- });
3986
- }
4049
+ }
4050
+ try {
4051
+ const csharpWasm = __require.resolve(
4052
+ "tree-sitter-c-sharp/c_sharp.wasm"
4053
+ );
4054
+ const wt = await import("web-tree-sitter");
4055
+ csharpLang = await wt.Language.load(csharpWasm);
4056
+ csharpInitDone = true;
4057
+ csharpInitOk = true;
4058
+ return true;
4059
+ } catch {
4060
+ csharpInitDone = true;
4061
+ csharpInitOk = false;
4062
+ return false;
4063
+ }
4064
+ }
4065
+ async function initSwiftParser() {
4066
+ if (swiftInitDone) return swiftInitOk;
4067
+ if (!initOk) {
4068
+ const baseOk = await initParser();
4069
+ if (!baseOk) {
4070
+ swiftInitDone = true;
4071
+ swiftInitOk = false;
4072
+ return false;
3987
4073
  }
3988
- pluginRegistry.setLoaded(true);
3989
- return engines;
4074
+ }
4075
+ try {
4076
+ const swiftWasm = __require.resolve(
4077
+ "tree-sitter-swift/swift.wasm"
4078
+ );
4079
+ const wt = await import("web-tree-sitter");
4080
+ swiftLang = await wt.Language.load(swiftWasm);
4081
+ swiftInitDone = true;
4082
+ swiftInitOk = true;
4083
+ return true;
3990
4084
  } catch {
3991
- pluginRegistry.setLoaded(true);
3992
- return [];
3993
- }
3994
- }
3995
- var PluginRegistry, pluginRegistry, PLUGIN_DIR;
3996
- var init_registry = __esm({
3997
- "src/plugins/registry.ts"() {
3998
- "use strict";
3999
- init_loader();
4000
- PluginRegistry = class {
4001
- entries = /* @__PURE__ */ new Map();
4002
- loaded = false;
4003
- /** Get all registered plugins */
4004
- getAll() {
4005
- return [...this.entries.values()];
4006
- }
4007
- /** Get all successfully loaded engines */
4008
- getEngines() {
4009
- return [...this.entries.values()].filter((e) => e.loaded).map((e) => e.engine);
4010
- }
4011
- /** Get a specific plugin by id */
4012
- get(id) {
4013
- return this.entries.get(id);
4014
- }
4015
- /** Get a loaded engine by name */
4016
- getEngine(name) {
4017
- const entry = this.entries.get(name);
4018
- return entry?.loaded ? entry.engine : void 0;
4019
- }
4020
- /** Register a plugin entry */
4021
- register(entry) {
4022
- this.entries.set(entry.id, entry);
4023
- }
4024
- /** Remove a plugin by id */
4025
- remove(id) {
4026
- return this.entries.delete(id);
4027
- }
4028
- /** Check if a plugin is registered */
4029
- has(id) {
4030
- return this.entries.has(id);
4031
- }
4032
- /** Number of registered plugins */
4033
- get size() {
4034
- return this.entries.size;
4035
- }
4036
- /** Whether the registry has been loaded from disk */
4037
- get isLoaded() {
4038
- return this.loaded;
4039
- }
4040
- /** Mark the registry as loaded */
4041
- setLoaded(loaded) {
4042
- this.loaded = loaded;
4043
- }
4044
- /** Clear all entries */
4045
- clear() {
4046
- this.entries.clear();
4047
- this.loaded = false;
4048
- }
4049
- };
4050
- pluginRegistry = new PluginRegistry();
4051
- PLUGIN_DIR = ".deep-slop/plugins";
4052
- }
4053
- });
4054
-
4055
- // src/utils/suppress.ts
4056
- import { join as join3 } from "node:path";
4057
- import { readFileSync as readFileSync2 } from "node:fs";
4058
- function parseSuppressDirectives(content) {
4059
- const entries = [];
4060
- const lines = content.split("\n");
4061
- for (let i = 0; i < lines.length; i++) {
4062
- const line = lines[i].trim();
4063
- const lineNum = i + 1;
4064
- const ignoreNextMatch = line.match(
4065
- /\/\/\s*deep-slop-ignore-next(?:\s+(.+))?$/
4066
- );
4067
- if (ignoreNextMatch) {
4068
- entries.push({
4069
- directiveLine: lineNum,
4070
- targetLine: lineNum + 1,
4071
- rules: parseRuleList(ignoreNextMatch[1]),
4072
- type: "next-line"
4073
- });
4074
- continue;
4075
- }
4076
- const ignoreLineMatch = line.match(
4077
- /\/\/\s*deep-slop-ignore-line(?:\s+(.+))?$/
4078
- );
4079
- if (ignoreLineMatch) {
4080
- entries.push({
4081
- directiveLine: lineNum,
4082
- targetLine: lineNum,
4083
- rules: parseRuleList(ignoreLineMatch[1]),
4084
- type: "line"
4085
- });
4086
- continue;
4087
- }
4088
- const ignoreRuleMatch = line.match(
4089
- /\/\/\s*deep-slop-ignore(?:\s+(.+))?$/
4090
- );
4091
- if (ignoreRuleMatch) {
4092
- const rulePart = ignoreRuleMatch[1];
4093
- if (rulePart !== "start" && rulePart !== "end") {
4094
- entries.push({
4095
- directiveLine: lineNum,
4096
- targetLine: lineNum + 1,
4097
- rules: parseRuleList(rulePart),
4098
- type: "next-line"
4099
- });
4100
- continue;
4101
- }
4102
- }
4103
- const ignoreStartMatch = line.match(
4104
- /\/\/\s*deep-slop-ignore-start(?:\s+(.+))?$/
4105
- );
4106
- if (ignoreStartMatch) {
4107
- entries.push({
4108
- directiveLine: lineNum,
4109
- targetLine: lineNum,
4110
- rules: parseRuleList(ignoreStartMatch[1]),
4111
- type: "block-start"
4112
- });
4113
- continue;
4114
- }
4115
- const ignoreEndMatch = line.match(
4116
- /\/\/\s*deep-slop-ignore-end/
4117
- );
4118
- if (ignoreEndMatch) {
4119
- entries.push({
4120
- directiveLine: lineNum,
4121
- targetLine: lineNum,
4122
- rules: /* @__PURE__ */ new Set(),
4123
- type: "block-end"
4124
- });
4125
- continue;
4126
- }
4127
- const nextLineMatch = line.match(
4128
- /\/\/\s*deep-slop-disable-next-line(?:\s+(.+))?$/
4129
- );
4130
- if (nextLineMatch) {
4131
- entries.push({
4132
- directiveLine: lineNum,
4133
- targetLine: lineNum + 1,
4134
- rules: parseRuleList(nextLineMatch[1]),
4135
- type: "next-line"
4136
- });
4137
- continue;
4138
- }
4139
- const lineMatch = line.match(
4140
- /\/\/\s*deep-slop-disable-line(?:\s+(.+))?$/
4141
- );
4142
- if (lineMatch) {
4143
- entries.push({
4144
- directiveLine: lineNum,
4145
- targetLine: lineNum,
4146
- rules: parseRuleList(lineMatch[1]),
4147
- type: "line"
4148
- });
4149
- continue;
4150
- }
4151
- const blockStart = line.match(
4152
- /\/\*\s*deep-slop-disable(?:\s+(.+))?\s*\//
4153
- );
4154
- if (blockStart) {
4155
- entries.push({
4156
- directiveLine: lineNum,
4157
- targetLine: lineNum,
4158
- rules: parseRuleList(blockStart[1]),
4159
- type: "block-start"
4160
- });
4161
- continue;
4162
- }
4163
- const blockEnd = line.match(/\/\*\s*deep-slop-enable\s*\//);
4164
- if (blockEnd) {
4165
- entries.push({
4166
- directiveLine: lineNum,
4167
- targetLine: lineNum,
4168
- rules: /* @__PURE__ */ new Set(),
4169
- type: "block-end"
4170
- });
4171
- }
4172
- }
4173
- return entries;
4174
- }
4175
- function parseRuleList(rulesStr) {
4176
- if (!rulesStr) return /* @__PURE__ */ new Set();
4177
- const rules = rulesStr.split(/[,\\s]+/).map((r) => r.trim()).filter(Boolean);
4178
- return new Set(rules);
4179
- }
4180
- function buildSuppressChecker(entries) {
4181
- const blockRanges = [];
4182
- let currentStart = null;
4183
- for (const entry of entries) {
4184
- if (entry.type === "block-start") {
4185
- currentStart = { line: entry.directiveLine, rules: entry.rules };
4186
- } else if (entry.type === "block-end" && currentStart) {
4187
- blockRanges.push({
4188
- startLine: currentStart.line,
4189
- endLine: entry.directiveLine,
4190
- rules: currentStart.rules
4191
- });
4192
- currentStart = null;
4193
- }
4194
- }
4195
- if (currentStart) {
4196
- blockRanges.push({
4197
- startLine: currentStart.line,
4198
- endLine: 999999,
4199
- rules: currentStart.rules
4200
- });
4201
- }
4202
- return (line, rule) => {
4203
- for (const entry of entries) {
4204
- if (entry.type === "next-line" || entry.type === "line") {
4205
- if (entry.targetLine === line) {
4206
- if (entry.rules.size === 0 || entry.rules.has(rule)) {
4207
- return true;
4208
- }
4209
- }
4210
- }
4211
- }
4212
- for (const range2 of blockRanges) {
4213
- if (line >= range2.startLine && line <= range2.endLine) {
4214
- if (range2.rules.size === 0 || range2.rules.has(rule)) {
4215
- return true;
4216
- }
4217
- }
4218
- }
4219
- return false;
4220
- };
4221
- }
4222
- function buildSuppressMap(content) {
4223
- const entries = parseSuppressDirectives(content);
4224
- const isSuppressed = buildSuppressChecker(entries);
4225
- return { entries, isSuppressed };
4226
- }
4227
- function loadIgnoreFile(rootDir) {
4228
- const ignorePath = join3(rootDir, ".deep-slop", ".deep-slop-ignore");
4229
- try {
4230
- const content = readFileSync2(ignorePath, "utf-8");
4231
- return content.split("\n").map((line) => line.split("#")[0].trim()).filter((line) => line && !line.startsWith("file:"));
4232
- } catch {
4233
- return [];
4234
- }
4235
- }
4236
- function applySuppressDirectives(diagnostics, fileContents, globallySuppressed = /* @__PURE__ */ new Set()) {
4237
- const suppressMaps = /* @__PURE__ */ new Map();
4238
- for (const [filePath, content] of fileContents) {
4239
- suppressMaps.set(filePath, buildSuppressMap(content));
4240
- }
4241
- const filtered = [];
4242
- let suppressedCount = 0;
4243
- for (const diag8 of diagnostics) {
4244
- if (globallySuppressed.has(diag8.rule)) {
4245
- suppressedCount++;
4246
- continue;
4247
- }
4248
- const map = suppressMaps.get(diag8.filePath);
4249
- if (map && map.isSuppressed(diag8.line, diag8.rule)) {
4250
- suppressedCount++;
4251
- } else {
4252
- filtered.push(diag8);
4253
- }
4254
- }
4255
- return { filtered, suppressedCount };
4256
- }
4257
- var init_suppress = __esm({
4258
- "src/utils/suppress.ts"() {
4259
- "use strict";
4260
- }
4261
- });
4262
-
4263
- // src/utils/file-utils.ts
4264
- import { readFile as readFile2 } from "node:fs/promises";
4265
- async function readFileContent(filePath) {
4266
- const buffer = await readFile2(filePath);
4267
- let content = buffer.toString("utf-8");
4268
- if (content.charCodeAt(0) === 65279) {
4269
- content = content.slice(1);
4270
- }
4271
- return content;
4272
- }
4273
- function detectEncodingAnomalies(content) {
4274
- const hasBom = content.charCodeAt(0) === 65279;
4275
- const hasCrlf = content.includes("\r\n");
4276
- const hasZwnbsp = content.includes("\uFEFF");
4277
- const lfOnly = (content.match(/(?<!\r)\n/g) ?? []).length;
4278
- const crlfCount = (content.match(/\r\n/g) ?? []).length;
4279
- const lineEnding = crlfCount > 0 && lfOnly > crlfCount ? "mixed" : crlfCount > 0 ? "crlf" : "lf";
4280
- return { hasBom, hasCrlf, hasZwnbsp, lineEnding };
4281
- }
4282
- function toLines(content) {
4283
- return content.split("\n").map((text, i) => ({ num: i + 1, text }));
4284
- }
4285
- function extractImports(content, language) {
4286
- const imports = [];
4287
- const lines = toLines(content);
4288
- for (const { num, text } of lines) {
4289
- const trimmed = text.trim();
4290
- if (language === "typescript" || language === "javascript") {
4291
- const jsMatch = trimmed.match(
4292
- /^import\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/
4293
- );
4294
- if (jsMatch) {
4295
- imports.push({
4296
- line: num,
4297
- source: jsMatch[1],
4298
- raw: trimmed,
4299
- isTypeOnly: trimmed.includes("import type"),
4300
- isDefault: !trimmed.includes("{")
4301
- });
4302
- }
4303
- const dynMatch = trimmed.match(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/);
4304
- if (dynMatch) {
4305
- imports.push({
4306
- line: num,
4307
- source: dynMatch[1],
4308
- raw: trimmed,
4309
- isTypeOnly: false,
4310
- isDynamic: true
4311
- });
4312
- }
4313
- const reqMatch = trimmed.match(/(?:const|let|var)\s+[^=]*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
4314
- if (reqMatch) {
4315
- imports.push({
4316
- line: num,
4317
- source: reqMatch[1],
4318
- raw: trimmed,
4319
- isTypeOnly: false,
4320
- isRequire: true
4321
- });
4322
- }
4323
- }
4324
- if (language === "python") {
4325
- const pyMatch = trimmed.match(/^from\s+([^\s]+)\s+import/);
4326
- if (pyMatch) {
4327
- imports.push({ line: num, source: pyMatch[1], raw: trimmed, isTypeOnly: false });
4328
- }
4329
- const pyImport = trimmed.match(/^import\s+([^\s]+)/);
4330
- if (pyImport) {
4331
- imports.push({ line: num, source: pyImport[1], raw: trimmed, isTypeOnly: false });
4332
- }
4333
- }
4334
- if (language === "go") {
4335
- const goMatch = trimmed.match(/"([^"]+)"/);
4336
- if (trimmed.startsWith("import") && goMatch) {
4337
- imports.push({ line: num, source: goMatch[1], raw: trimmed, isTypeOnly: false });
4338
- }
4339
- }
4340
- }
4341
- return imports;
4342
- }
4343
- var init_file_utils = __esm({
4344
- "src/utils/file-utils.ts"() {
4345
- "use strict";
4346
- }
4347
- });
4348
-
4349
- // src/utils/tree-sitter.ts
4350
- async function initParser() {
4351
- if (initDone) return initOk;
4352
- if (initPromise) return initPromise;
4353
- initPromise = (async () => {
4354
- try {
4355
- const wt = await import("web-tree-sitter");
4356
- const { dirname: dirname9 } = await import("node:path");
4357
- const wasmDir = dirname9(
4358
- __require.resolve("web-tree-sitter/tree-sitter.wasm")
4359
- );
4360
- await wt.Parser.init({
4361
- locateFile: (name) => `${wasmDir}/${name}`
4362
- });
4363
- const parser = new wt.Parser();
4364
- parserInstance = parser;
4365
- const tsWasm = __require.resolve(
4366
- "tree-sitter-typescript/tree-sitter-typescript.wasm"
4367
- );
4368
- tsLang = await wt.Language.load(tsWasm);
4369
- const tsxWasm = __require.resolve(
4370
- "tree-sitter-typescript/tree-sitter-tsx.wasm"
4371
- );
4372
- tsxLang = await wt.Language.load(tsxWasm);
4373
- initDone = true;
4374
- initOk = true;
4375
- return true;
4376
- } catch (err) {
4377
- initDone = true;
4378
- initOk = false;
4379
- return false;
4380
- }
4381
- })();
4382
- return initPromise;
4383
- }
4384
- async function initPythonParser() {
4385
- if (pyInitDone) return pyInitOk;
4386
- if (!initOk) {
4387
- const baseOk = await initParser();
4388
- if (!baseOk) {
4389
- pyInitDone = true;
4390
- pyInitOk = false;
4391
- return false;
4392
- }
4393
- }
4394
- try {
4395
- const pyWasm = __require.resolve(
4396
- "tree-sitter-python/python.wasm"
4397
- );
4398
- const wt = await import("web-tree-sitter");
4399
- pyLang = await wt.Language.load(pyWasm);
4400
- pyInitDone = true;
4401
- pyInitOk = true;
4402
- return true;
4403
- } catch {
4404
- pyInitDone = true;
4405
- pyInitOk = false;
4406
- return false;
4407
- }
4408
- }
4409
- async function initGoParser() {
4410
- if (goInitDone) return goInitOk;
4411
- if (!initOk) {
4412
- const baseOk = await initParser();
4413
- if (!baseOk) {
4414
- goInitDone = true;
4415
- goInitOk = false;
4416
- return false;
4417
- }
4418
- }
4419
- try {
4420
- const goWasm = __require.resolve(
4421
- "tree-sitter-go/go.wasm"
4422
- );
4423
- const wt = await import("web-tree-sitter");
4424
- goLang = await wt.Language.load(goWasm);
4425
- goInitDone = true;
4426
- goInitOk = true;
4427
- return true;
4428
- } catch {
4429
- goInitDone = true;
4430
- goInitOk = false;
4431
- return false;
4432
- }
4433
- }
4434
- async function initRustParser() {
4435
- if (rustInitDone) return rustInitOk;
4436
- if (!initOk) {
4437
- const baseOk = await initParser();
4438
- if (!baseOk) {
4439
- rustInitDone = true;
4440
- rustInitOk = false;
4441
- return false;
4442
- }
4443
- }
4444
- try {
4445
- const rustWasm = __require.resolve(
4446
- "tree-sitter-rust/rust.wasm"
4447
- );
4448
- const wt = await import("web-tree-sitter");
4449
- rustLang = await wt.Language.load(rustWasm);
4450
- rustInitDone = true;
4451
- rustInitOk = true;
4452
- return true;
4453
- } catch {
4454
- rustInitDone = true;
4455
- rustInitOk = false;
4456
- return false;
4457
- }
4458
- }
4459
- async function initPhpParser() {
4460
- if (phpInitDone) return phpInitOk;
4461
- if (!initOk) {
4462
- const baseOk = await initParser();
4463
- if (!baseOk) {
4464
- phpInitDone = true;
4465
- phpInitOk = false;
4466
- return false;
4467
- }
4468
- }
4469
- try {
4470
- const phpWasm = __require.resolve(
4471
- "tree-sitter-php/php.wasm"
4472
- );
4473
- const wt = await import("web-tree-sitter");
4474
- phpLang = await wt.Language.load(phpWasm);
4475
- phpInitDone = true;
4476
- phpInitOk = true;
4477
- return true;
4478
- } catch {
4479
- phpInitDone = true;
4480
- phpInitOk = false;
4481
- return false;
4482
- }
4483
- }
4484
- async function initCsharpParser() {
4485
- if (csharpInitDone) return csharpInitOk;
4486
- if (!initOk) {
4487
- const baseOk = await initParser();
4488
- if (!baseOk) {
4489
- csharpInitDone = true;
4490
- csharpInitOk = false;
4491
- return false;
4492
- }
4493
- }
4494
- try {
4495
- const csharpWasm = __require.resolve(
4496
- "tree-sitter-c-sharp/c_sharp.wasm"
4497
- );
4498
- const wt = await import("web-tree-sitter");
4499
- csharpLang = await wt.Language.load(csharpWasm);
4500
- csharpInitDone = true;
4501
- csharpInitOk = true;
4502
- return true;
4503
- } catch {
4504
- csharpInitDone = true;
4505
- csharpInitOk = false;
4506
- return false;
4507
- }
4508
- }
4509
- async function initSwiftParser() {
4510
- if (swiftInitDone) return swiftInitOk;
4511
- if (!initOk) {
4512
- const baseOk = await initParser();
4513
- if (!baseOk) {
4514
- swiftInitDone = true;
4515
- swiftInitOk = false;
4516
- return false;
4517
- }
4518
- }
4519
- try {
4520
- const swiftWasm = __require.resolve(
4521
- "tree-sitter-swift/swift.wasm"
4522
- );
4523
- const wt = await import("web-tree-sitter");
4524
- swiftLang = await wt.Language.load(swiftWasm);
4525
- swiftInitDone = true;
4526
- swiftInitOk = true;
4527
- return true;
4528
- } catch {
4529
- swiftInitDone = true;
4530
- swiftInitOk = false;
4531
- return false;
4085
+ swiftInitDone = true;
4086
+ swiftInitOk = false;
4087
+ return false;
4532
4088
  }
4533
4089
  }
4534
4090
  function isAvailable() {
@@ -4538,14 +4094,19 @@ function isPythonAvailable() {
4538
4094
  return pyInitOk && pyLang !== null;
4539
4095
  }
4540
4096
  async function parseFile(content, isTsx = false, filePath) {
4097
+ if (filePath) {
4098
+ const cacheKey = `${filePath}:${isTsx ? "tsx" : "ts"}`;
4099
+ const cached = parseCache.get(cacheKey);
4100
+ if (cached !== void 0) return cached;
4101
+ }
4541
4102
  if (filePath) {
4542
4103
  const ext2 = filePath.toLowerCase();
4543
- if (ext2.endsWith(".go")) return parseGoFile(content);
4544
- if (ext2.endsWith(".rs")) return parseRustFile(content);
4545
- if (ext2.endsWith(".php")) return parsePhpFile(content);
4546
- if (ext2.endsWith(".cs")) return parseCsharpFile(content);
4547
- if (ext2.endsWith(".swift")) return parseSwiftFile(content);
4548
- if (ext2.endsWith(".py")) return parsePython(content);
4104
+ if (ext2.endsWith(".go")) return parseGoFile(content, filePath);
4105
+ if (ext2.endsWith(".rs")) return parseRustFile(content, filePath);
4106
+ if (ext2.endsWith(".php")) return parsePhpFile(content, filePath);
4107
+ if (ext2.endsWith(".cs")) return parseCsharpFile(content, filePath);
4108
+ if (ext2.endsWith(".swift")) return parseSwiftFile(content, filePath);
4109
+ if (ext2.endsWith(".py")) return parsePython(content, filePath);
4549
4110
  }
4550
4111
  if (!parserInstance || !tsLang && !tsxLang) {
4551
4112
  const ok = await initParser();
@@ -4556,12 +4117,21 @@ async function parseFile(content, isTsx = false, filePath) {
4556
4117
  parserInstance.setLanguage(lang);
4557
4118
  const tree = parserInstance.parse(content);
4558
4119
  if (!tree) return null;
4559
- return convertNode(tree.rootNode, null);
4120
+ const result = convertNode(tree.rootNode, null);
4121
+ if (filePath) {
4122
+ parseCache.set(`${filePath}:${isTsx ? "tsx" : "ts"}`, result);
4123
+ }
4124
+ return result;
4560
4125
  } catch {
4126
+ if (filePath) parseCache.set(`${filePath}:${isTsx ? "tsx" : "ts"}`, null);
4561
4127
  return null;
4562
4128
  }
4563
4129
  }
4564
- async function parsePython(content) {
4130
+ async function parsePython(content, filePath) {
4131
+ if (filePath) {
4132
+ const cached = parseCache.get(`py:${filePath}`);
4133
+ if (cached !== void 0) return cached;
4134
+ }
4565
4135
  if (!pyLang) {
4566
4136
  const ok = await initPythonParser();
4567
4137
  if (!ok) return null;
@@ -4573,480 +4143,984 @@ async function parsePython(content) {
4573
4143
  try {
4574
4144
  parserInstance.setLanguage(pyLang);
4575
4145
  const tree = parserInstance.parse(content);
4576
- if (!tree) return null;
4577
- return convertNode(tree.rootNode, null);
4146
+ if (!tree) {
4147
+ if (filePath) parseCache.set(`py:${filePath}`, null);
4148
+ return null;
4149
+ }
4150
+ const result = convertNode(tree.rootNode, null);
4151
+ if (filePath) parseCache.set(`py:${filePath}`, result);
4152
+ return result;
4153
+ } catch {
4154
+ if (filePath) parseCache.set(`py:${filePath}`, null);
4155
+ return null;
4156
+ }
4157
+ }
4158
+ async function parseGoFile(content, filePath) {
4159
+ if (filePath) {
4160
+ const cached = parseCache.get(`go:${filePath}`);
4161
+ if (cached !== void 0) return cached;
4162
+ }
4163
+ if (!goLang) {
4164
+ const ok = await initGoParser();
4165
+ if (!ok) return null;
4166
+ }
4167
+ if (!parserInstance) {
4168
+ const ok = await initParser();
4169
+ if (!ok) return null;
4170
+ }
4171
+ try {
4172
+ parserInstance.setLanguage(goLang);
4173
+ const tree = parserInstance.parse(content);
4174
+ if (!tree) {
4175
+ if (filePath) parseCache.set(`go:${filePath}`, null);
4176
+ return null;
4177
+ }
4178
+ const result = convertNode(tree.rootNode, null);
4179
+ if (filePath) parseCache.set(`go:${filePath}`, result);
4180
+ return result;
4181
+ } catch {
4182
+ if (filePath) parseCache.set(`go:${filePath}`, null);
4183
+ return null;
4184
+ }
4185
+ }
4186
+ async function parseRustFile(content, filePath) {
4187
+ if (filePath) {
4188
+ const cached = parseCache.get(`rs:${filePath}`);
4189
+ if (cached !== void 0) return cached;
4190
+ }
4191
+ if (!rustLang) {
4192
+ const ok = await initRustParser();
4193
+ if (!ok) return null;
4194
+ }
4195
+ if (!parserInstance) {
4196
+ const ok = await initParser();
4197
+ if (!ok) return null;
4198
+ }
4199
+ try {
4200
+ parserInstance.setLanguage(rustLang);
4201
+ const tree = parserInstance.parse(content);
4202
+ if (!tree) {
4203
+ if (filePath) parseCache.set(`rs:${filePath}`, null);
4204
+ return null;
4205
+ }
4206
+ const result = convertNode(tree.rootNode, null);
4207
+ if (filePath) parseCache.set(`rs:${filePath}`, result);
4208
+ return result;
4209
+ } catch {
4210
+ if (filePath) parseCache.set(`rs:${filePath}`, null);
4211
+ return null;
4212
+ }
4213
+ }
4214
+ async function parsePhpFile(content, filePath) {
4215
+ if (filePath) {
4216
+ const cached = parseCache.get(`php:${filePath}`);
4217
+ if (cached !== void 0) return cached;
4218
+ }
4219
+ if (!phpLang) {
4220
+ const ok = await initPhpParser();
4221
+ if (!ok) return null;
4222
+ }
4223
+ if (!parserInstance) {
4224
+ const ok = await initParser();
4225
+ if (!ok) return null;
4226
+ }
4227
+ try {
4228
+ parserInstance.setLanguage(phpLang);
4229
+ const tree = parserInstance.parse(content);
4230
+ if (!tree) {
4231
+ if (filePath) parseCache.set(`php:${filePath}`, null);
4232
+ return null;
4233
+ }
4234
+ const result = convertNode(tree.rootNode, null);
4235
+ if (filePath) parseCache.set(`php:${filePath}`, result);
4236
+ return result;
4237
+ } catch {
4238
+ if (filePath) parseCache.set(`php:${filePath}`, null);
4239
+ return null;
4240
+ }
4241
+ }
4242
+ async function parseCsharpFile(content, filePath) {
4243
+ if (filePath) {
4244
+ const cached = parseCache.get(`cs:${filePath}`);
4245
+ if (cached !== void 0) return cached;
4246
+ }
4247
+ if (!csharpLang) {
4248
+ const ok = await initCsharpParser();
4249
+ if (!ok) return null;
4250
+ }
4251
+ if (!parserInstance) {
4252
+ const ok = await initParser();
4253
+ if (!ok) return null;
4254
+ }
4255
+ try {
4256
+ parserInstance.setLanguage(csharpLang);
4257
+ const tree = parserInstance.parse(content);
4258
+ if (!tree) {
4259
+ if (filePath) parseCache.set(`cs:${filePath}`, null);
4260
+ return null;
4261
+ }
4262
+ const result = convertNode(tree.rootNode, null);
4263
+ if (filePath) parseCache.set(`cs:${filePath}`, result);
4264
+ return result;
4265
+ } catch {
4266
+ if (filePath) parseCache.set(`cs:${filePath}`, null);
4267
+ return null;
4268
+ }
4269
+ }
4270
+ async function parseSwiftFile(content, filePath) {
4271
+ if (filePath) {
4272
+ const cached = parseCache.get(`swift:${filePath}`);
4273
+ if (cached !== void 0) return cached;
4274
+ }
4275
+ if (!swiftLang) {
4276
+ const ok = await initSwiftParser();
4277
+ if (!ok) return null;
4278
+ }
4279
+ if (!parserInstance) {
4280
+ const ok = await initParser();
4281
+ if (!ok) return null;
4282
+ }
4283
+ try {
4284
+ parserInstance.setLanguage(swiftLang);
4285
+ const tree = parserInstance.parse(content);
4286
+ if (!tree) {
4287
+ if (filePath) parseCache.set(`swift:${filePath}`, null);
4288
+ return null;
4289
+ }
4290
+ const result = convertNode(tree.rootNode, null);
4291
+ if (filePath) parseCache.set(`swift:${filePath}`, result);
4292
+ return result;
4578
4293
  } catch {
4294
+ if (filePath) parseCache.set(`swift:${filePath}`, null);
4295
+ return null;
4296
+ }
4297
+ }
4298
+ function findNodesOfType(root, type) {
4299
+ const results = [];
4300
+ function walk(node) {
4301
+ if (node.type === type) results.push(node);
4302
+ for (const child of node.children) walk(child);
4303
+ }
4304
+ walk(root);
4305
+ return results;
4306
+ }
4307
+ function findNodesOfTypes(root, types3) {
4308
+ const typeSet = new Set(types3);
4309
+ const results = [];
4310
+ function walk(node) {
4311
+ if (typeSet.has(node.type)) results.push(node);
4312
+ for (const child of node.children) walk(child);
4313
+ }
4314
+ walk(root);
4315
+ return results;
4316
+ }
4317
+ function walkAST(root, visitor) {
4318
+ function walk(node) {
4319
+ const result = visitor(node);
4320
+ if (result !== false) {
4321
+ for (const child of node.children) walk(child);
4322
+ }
4323
+ }
4324
+ walk(root);
4325
+ }
4326
+ function findAncestor(node, predicate) {
4327
+ let current = node.parent;
4328
+ while (current) {
4329
+ if (predicate(current)) return current;
4330
+ current = current.parent;
4331
+ }
4332
+ return null;
4333
+ }
4334
+ function isInsideCatch(node) {
4335
+ return findAncestor(node, (n) => n.type === "catch_clause") !== null;
4336
+ }
4337
+ function findAncestorOfType(node, type) {
4338
+ return findAncestor(node, (n) => n.type === type);
4339
+ }
4340
+ function isCatchBodyEmpty(catchNode) {
4341
+ if (catchNode.type !== "catch_clause") return false;
4342
+ const body = catchNode.children.find(
4343
+ (c) => c.type === "statement_block" || c.type === "block"
4344
+ );
4345
+ if (!body) return true;
4346
+ const nonTrivial = body.children.filter(
4347
+ (c) => c.type !== "comment" && c.type !== "//" && c.type !== "/*" && c.type !== "{" && c.type !== "}" && c.text.trim() !== ""
4348
+ );
4349
+ return nonTrivial.length === 0;
4350
+ }
4351
+ function getAsExpressionType(node) {
4352
+ if (node.type !== "as_expression") return null;
4353
+ const typeChild = node.children.find((c) => c.fieldName === "type");
4354
+ return typeChild?.text ?? null;
4355
+ }
4356
+ function getAsExpressionContext(node) {
4357
+ if (isInsideCatch(node)) return "catch";
4358
+ const funcAncestor = findAncestor(
4359
+ node,
4360
+ (n) => ["function_declaration", "arrow_function", "method_definition"].includes(n.type)
4361
+ );
4362
+ if (funcAncestor) {
4363
+ const text = funcAncestor.text.toLowerCase();
4364
+ if (/prisma|drizzle|sequelize|mongoose|typeorm|knex|supabase/.test(text))
4365
+ return "orm";
4366
+ if (/json\.parse|parse\(/.test(text)) return "json";
4367
+ }
4368
+ return "unknown";
4369
+ }
4370
+ function extractImportFromNode(node) {
4371
+ if (node.type !== "import_statement" && node.type !== "import_declaration")
4579
4372
  return null;
4373
+ const sourceNode = node.children.find(
4374
+ (c) => c.type === "string" || c.fieldName === "source"
4375
+ );
4376
+ if (!sourceNode) return null;
4377
+ const source = sourceNode.text.replace(/^['"]|['"]$/g, "");
4378
+ const isTypeOnly = node.text.includes("import type ");
4379
+ const symbols = [];
4380
+ const namedImport = node.children.find(
4381
+ (c) => c.type === "named_imports" || c.type === "import_clause"
4382
+ );
4383
+ if (namedImport) {
4384
+ for (const child of namedImport.children) {
4385
+ if (child.type === "identifier" || child.type === "type_identifier" || child.type === "import_specifier") {
4386
+ symbols.push(child.text);
4387
+ }
4388
+ }
4389
+ }
4390
+ return { source, symbols, line: node.startRow + 1, isTypeOnly };
4391
+ }
4392
+ function findPythonFunctions(root) {
4393
+ const funcNodes = findNodesOfTypes(root, [
4394
+ "function_definition",
4395
+ "decorated_definition"
4396
+ ]);
4397
+ const results = [];
4398
+ for (const node of funcNodes) {
4399
+ if (node.type === "decorated_definition") {
4400
+ const inner = node.children.find(
4401
+ (c) => c.type === "function_definition"
4402
+ );
4403
+ if (inner) {
4404
+ results.push(extractPythonFunctionInfo(inner, node));
4405
+ }
4406
+ } else {
4407
+ results.push(extractPythonFunctionInfo(node));
4408
+ }
4409
+ }
4410
+ return results;
4411
+ }
4412
+ function findPythonClasses(root) {
4413
+ const classNodes = findNodesOfTypes(root, [
4414
+ "class_definition",
4415
+ "decorated_definition"
4416
+ ]);
4417
+ const results = [];
4418
+ for (const node of classNodes) {
4419
+ if (node.type === "decorated_definition") {
4420
+ const inner = node.children.find(
4421
+ (c) => c.type === "class_definition"
4422
+ );
4423
+ if (inner) {
4424
+ results.push(extractPythonClassInfo(inner, node));
4425
+ }
4426
+ } else {
4427
+ results.push(extractPythonClassInfo(node));
4428
+ }
4580
4429
  }
4430
+ return results;
4431
+ }
4432
+ function findPythonImports(root) {
4433
+ const importNodes = findNodesOfTypes(root, [
4434
+ "import_statement",
4435
+ "import_from_statement"
4436
+ ]);
4437
+ return importNodes.map(extractPythonImportInfo);
4581
4438
  }
4582
- async function parseGoFile(content) {
4583
- if (!goLang) {
4584
- const ok = await initGoParser();
4585
- if (!ok) return null;
4439
+ function isPythonFunctionStub(funcInfo) {
4440
+ const text = funcInfo.text;
4441
+ const bodyLines = text.split("\n").slice(1).join("\n").trim();
4442
+ if (/^(\s*(pass|\.\.\.)\s*)$/.test(bodyLines)) return true;
4443
+ const docstringOnly = bodyLines.replace(/"""[\s\S]*?"""/g, "").replace(/'''[\s\S]*?'''/g, "").trim();
4444
+ if (docstringOnly === "" || docstringOnly === "pass" || docstringOnly === "...") {
4445
+ return true;
4586
4446
  }
4587
- if (!parserInstance) {
4588
- const ok = await initParser();
4589
- if (!ok) return null;
4447
+ return false;
4448
+ }
4449
+ function detectPythonAIPatterns(root) {
4450
+ const findings = [];
4451
+ const functions = findPythonFunctions(root);
4452
+ for (const fn of functions) {
4453
+ if (isPythonFunctionStub(fn)) {
4454
+ findings.push({
4455
+ type: "python-stub-function",
4456
+ message: `Function '${fn.name}' is a stub (only pass/ellipsis)`,
4457
+ line: fn.line
4458
+ });
4459
+ }
4590
4460
  }
4591
- try {
4592
- parserInstance.setLanguage(goLang);
4593
- const tree = parserInstance.parse(content);
4594
- if (!tree) return null;
4595
- return convertNode(tree.rootNode, null);
4596
- } catch {
4597
- return null;
4461
+ const tryNodes = findNodesOfType(root, "try_statement");
4462
+ for (const tryNode of tryNodes) {
4463
+ const exceptNodes = tryNode.children.filter(
4464
+ (c) => c.type === "except_clause"
4465
+ );
4466
+ for (const exceptNode of exceptNodes) {
4467
+ const hasSpecificType = exceptNode.children.some(
4468
+ (c) => c.type === "identifier" || c.type === "tuple"
4469
+ );
4470
+ if (!hasSpecificType) {
4471
+ const bareExcept = exceptNode.children.find(
4472
+ (c) => c.text && c.text.includes("except")
4473
+ );
4474
+ findings.push({
4475
+ type: "python-bare-except",
4476
+ message: "Bare except clause catches all exceptions",
4477
+ line: exceptNode.startRow + 1
4478
+ });
4479
+ }
4480
+ }
4598
4481
  }
4599
- }
4600
- async function parseRustFile(content) {
4601
- if (!rustLang) {
4602
- const ok = await initRustParser();
4603
- if (!ok) return null;
4482
+ const commentNodes = findNodesOfType(root, "comment");
4483
+ for (const comment of commentNodes) {
4484
+ const text = comment.text.toLowerCase();
4485
+ if (/todo|fixme|hack|xxx/.test(text)) {
4486
+ findings.push({
4487
+ type: "python-todo-stub",
4488
+ message: `TODO/FIXME comment: ${comment.text.trim()}`,
4489
+ line: comment.startRow + 1
4490
+ });
4491
+ }
4604
4492
  }
4605
- if (!parserInstance) {
4606
- const ok = await initParser();
4607
- if (!ok) return null;
4493
+ const callNodes = findNodesOfType(root, "call");
4494
+ for (const call of callNodes) {
4495
+ const func = call.children[0];
4496
+ if (func && func.text === "print") {
4497
+ findings.push({
4498
+ type: "python-print-leftover",
4499
+ message: "print() statement \u2014 likely debug leftover",
4500
+ line: call.startRow + 1
4501
+ });
4502
+ }
4608
4503
  }
4609
- try {
4610
- parserInstance.setLanguage(rustLang);
4611
- const tree = parserInstance.parse(content);
4612
- if (!tree) return null;
4613
- return convertNode(tree.rootNode, null);
4614
- } catch {
4615
- return null;
4504
+ return findings;
4505
+ }
4506
+ function extractPythonFunctionInfo(funcNode, decoratedParent) {
4507
+ const nameNode = funcNode.children.find((c) => c.fieldName === "name") ?? funcNode.children.find((c) => c.type === "identifier");
4508
+ const name = nameNode?.text ?? "(anonymous)";
4509
+ const params = funcNode.children.find(
4510
+ (c) => c.type === "parameters"
4511
+ );
4512
+ const parameters = params ? params.children.filter((c) => c.type === "identifier" || c.type === "typed_parameter" || c.type === "default_parameter").map((c) => c.children[0]?.text ?? c.text) : [];
4513
+ const isAsync = funcNode.children.some(
4514
+ (c) => c.type === "async"
4515
+ );
4516
+ const decorators = [];
4517
+ if (decoratedParent) {
4518
+ for (const child of decoratedParent.children) {
4519
+ if (child.type === "decorator") {
4520
+ decorators.push(child.text.replace("@", ""));
4521
+ }
4522
+ }
4616
4523
  }
4524
+ return {
4525
+ name,
4526
+ decorators,
4527
+ parameters,
4528
+ isAsync,
4529
+ line: funcNode.startRow + 1,
4530
+ endLine: funcNode.endRow + 1,
4531
+ text: funcNode.text
4532
+ };
4617
4533
  }
4618
- async function parsePhpFile(content) {
4619
- if (!phpLang) {
4620
- const ok = await initPhpParser();
4621
- if (!ok) return null;
4534
+ function extractPythonClassInfo(classNode, decoratedParent) {
4535
+ const nameNode = classNode.children.find((c) => c.fieldName === "name") ?? classNode.children.find((c) => c.type === "identifier");
4536
+ const name = nameNode?.text ?? "(anonymous)";
4537
+ const argList = classNode.children.find(
4538
+ (c) => c.type === "argument_list"
4539
+ );
4540
+ const bases = argList ? argList.children.filter((c) => c.type === "identifier" || c.type === "attribute").map((c) => c.text) : [];
4541
+ const decorators = [];
4542
+ if (decoratedParent) {
4543
+ for (const child of decoratedParent.children) {
4544
+ if (child.type === "decorator") {
4545
+ decorators.push(child.text.replace("@", ""));
4546
+ }
4547
+ }
4622
4548
  }
4623
- if (!parserInstance) {
4624
- const ok = await initParser();
4625
- if (!ok) return null;
4549
+ const body = classNode.children.find(
4550
+ (c) => c.type === "block"
4551
+ );
4552
+ const methods = [];
4553
+ if (body) {
4554
+ for (const child of body.children) {
4555
+ if (child.type === "function_definition") {
4556
+ methods.push(extractPythonFunctionInfo(child));
4557
+ } else if (child.type === "decorated_definition") {
4558
+ const inner = child.children.find(
4559
+ (c) => c.type === "function_definition"
4560
+ );
4561
+ if (inner) {
4562
+ methods.push(extractPythonFunctionInfo(inner, child));
4563
+ }
4564
+ }
4565
+ }
4626
4566
  }
4627
- try {
4628
- parserInstance.setLanguage(phpLang);
4629
- const tree = parserInstance.parse(content);
4630
- if (!tree) return null;
4631
- return convertNode(tree.rootNode, null);
4632
- } catch {
4633
- return null;
4567
+ return {
4568
+ name,
4569
+ bases,
4570
+ decorators,
4571
+ methods,
4572
+ line: classNode.startRow + 1,
4573
+ endLine: classNode.endRow + 1,
4574
+ text: classNode.text
4575
+ };
4576
+ }
4577
+ function extractPythonImportInfo(importNode) {
4578
+ const isFromImport = importNode.type === "import_from_statement";
4579
+ let module = "";
4580
+ const symbols = [];
4581
+ if (isFromImport) {
4582
+ const moduleNode = importNode.children.find(
4583
+ (c) => c.fieldName === "module_name" || c.type === "dotted_name" && c.fieldName !== "name" || c.type === "identifier" && c.fieldName !== "name"
4584
+ );
4585
+ module = moduleNode?.text ?? "";
4586
+ const nameList = importNode.children.find(
4587
+ (c) => c.type === "dotted_name" && c !== moduleNode
4588
+ );
4589
+ const identifierChildren = importNode.children.filter(
4590
+ (c) => c.type === "identifier" && c !== moduleNode
4591
+ );
4592
+ if (nameList) {
4593
+ symbols.push(nameList.text);
4594
+ }
4595
+ for (const id of identifierChildren) {
4596
+ if (id.text !== "from" && id.text !== "import" && id.text !== module) {
4597
+ symbols.push(id.text);
4598
+ }
4599
+ }
4600
+ } else {
4601
+ const dottedNames = importNode.children.filter(
4602
+ (c) => c.type === "dotted_name"
4603
+ );
4604
+ const identifiers = importNode.children.filter(
4605
+ (c) => c.type === "identifier"
4606
+ );
4607
+ for (const dn of dottedNames) {
4608
+ symbols.push(dn.text);
4609
+ }
4610
+ for (const id of identifiers) {
4611
+ if (id.text !== "import") {
4612
+ symbols.push(id.text);
4613
+ }
4614
+ }
4615
+ module = symbols[0] ?? "";
4634
4616
  }
4617
+ return {
4618
+ module,
4619
+ symbols,
4620
+ isFromImport,
4621
+ line: importNode.startRow + 1,
4622
+ text: importNode.text
4623
+ };
4635
4624
  }
4636
- async function parseCsharpFile(content) {
4637
- if (!csharpLang) {
4638
- const ok = await initCsharpParser();
4639
- if (!ok) return null;
4625
+ function convertNode(node, parent) {
4626
+ const children = [];
4627
+ const astNode = {
4628
+ type: node.type,
4629
+ text: node.text,
4630
+ startRow: node.startPosition.row,
4631
+ startCol: node.startPosition.column,
4632
+ endRow: node.endPosition.row,
4633
+ endCol: node.endPosition.column,
4634
+ children,
4635
+ parent,
4636
+ fieldName: null
4637
+ };
4638
+ for (let i = 0; i < node.childCount; i++) {
4639
+ const child = node.child(i);
4640
+ if (child) {
4641
+ const converted = convertNode(child, astNode);
4642
+ converted.fieldName = node.fieldNameForChild(i) ?? null;
4643
+ children.push(converted);
4644
+ }
4640
4645
  }
4641
- if (!parserInstance) {
4642
- const ok = await initParser();
4643
- if (!ok) return null;
4646
+ return astNode;
4647
+ }
4648
+ var parseCache, parserInstance, tsLang, tsxLang, initPromise, initDone, initOk, pyLang, pyInitDone, pyInitOk, goLang, goInitDone, goInitOk, rustLang, rustInitDone, rustInitOk, phpLang, phpInitDone, phpInitOk, csharpLang, csharpInitDone, csharpInitOk, swiftLang, swiftInitDone, swiftInitOk;
4649
+ var init_tree_sitter = __esm({
4650
+ "src/utils/tree-sitter.ts"() {
4651
+ "use strict";
4652
+ parseCache = /* @__PURE__ */ new Map();
4653
+ parserInstance = null;
4654
+ tsLang = null;
4655
+ tsxLang = null;
4656
+ initPromise = null;
4657
+ initDone = false;
4658
+ initOk = false;
4659
+ pyLang = null;
4660
+ pyInitDone = false;
4661
+ pyInitOk = false;
4662
+ goLang = null;
4663
+ goInitDone = false;
4664
+ goInitOk = false;
4665
+ rustLang = null;
4666
+ rustInitDone = false;
4667
+ rustInitOk = false;
4668
+ phpLang = null;
4669
+ phpInitDone = false;
4670
+ phpInitOk = false;
4671
+ csharpLang = null;
4672
+ csharpInitDone = false;
4673
+ csharpInitOk = false;
4674
+ swiftLang = null;
4675
+ swiftInitDone = false;
4676
+ swiftInitOk = false;
4644
4677
  }
4678
+ });
4679
+
4680
+ // src/plugins/loader.ts
4681
+ async function loadPlugin(pluginPath) {
4645
4682
  try {
4646
- parserInstance.setLanguage(csharpLang);
4647
- const tree = parserInstance.parse(content);
4648
- if (!tree) return null;
4649
- return convertNode(tree.rootNode, null);
4650
- } catch {
4683
+ const mod = await import(pluginPath);
4684
+ const engine = mod.default;
4685
+ if (!engine || typeof engine !== "object") {
4686
+ return null;
4687
+ }
4688
+ if (typeof engine.name === "string" && typeof engine.description === "string" && Array.isArray(engine.supportedLanguages) && typeof engine.run === "function") {
4689
+ return engine;
4690
+ }
4651
4691
  return null;
4652
- }
4653
- }
4654
- async function parseSwiftFile(content) {
4655
- if (!swiftLang) {
4656
- const ok = await initSwiftParser();
4657
- if (!ok) return null;
4658
- }
4659
- if (!parserInstance) {
4660
- const ok = await initParser();
4661
- if (!ok) return null;
4662
- }
4663
- try {
4664
- parserInstance.setLanguage(swiftLang);
4665
- const tree = parserInstance.parse(content);
4666
- if (!tree) return null;
4667
- return convertNode(tree.rootNode, null);
4668
4692
  } catch {
4669
4693
  return null;
4670
4694
  }
4671
4695
  }
4672
- function findNodesOfType(root, type) {
4673
- const results = [];
4674
- function walk(node) {
4675
- if (node.type === type) results.push(node);
4676
- for (const child of node.children) walk(child);
4677
- }
4678
- walk(root);
4679
- return results;
4680
- }
4681
- function findNodesOfTypes(root, types3) {
4682
- const typeSet = new Set(types3);
4683
- const results = [];
4684
- function walk(node) {
4685
- if (typeSet.has(node.type)) results.push(node);
4686
- for (const child of node.children) walk(child);
4687
- }
4688
- walk(root);
4689
- return results;
4690
- }
4691
- function walkAST(root, visitor) {
4692
- function walk(node) {
4693
- const result = visitor(node);
4694
- if (result !== false) {
4695
- for (const child of node.children) walk(child);
4696
+ async function loadPlugins(paths) {
4697
+ const results = await Promise.allSettled(
4698
+ paths.map((p) => loadPlugin(p))
4699
+ );
4700
+ const engines = [];
4701
+ for (const r of results) {
4702
+ if (r.status === "fulfilled" && r.value !== null) {
4703
+ engines.push(r.value);
4696
4704
  }
4697
4705
  }
4698
- walk(root);
4706
+ return engines;
4699
4707
  }
4700
- function findAncestor(node, predicate) {
4701
- let current = node.parent;
4702
- while (current) {
4703
- if (predicate(current)) return current;
4704
- current = current.parent;
4708
+ var init_loader = __esm({
4709
+ "src/plugins/loader.ts"() {
4710
+ "use strict";
4705
4711
  }
4706
- return null;
4707
- }
4708
- function isInsideCatch(node) {
4709
- return findAncestor(node, (n) => n.type === "catch_clause") !== null;
4710
- }
4711
- function findAncestorOfType(node, type) {
4712
- return findAncestor(node, (n) => n.type === type);
4713
- }
4714
- function isCatchBodyEmpty(catchNode) {
4715
- if (catchNode.type !== "catch_clause") return false;
4716
- const body = catchNode.children.find(
4717
- (c) => c.type === "statement_block" || c.type === "block"
4718
- );
4719
- if (!body) return true;
4720
- const nonTrivial = body.children.filter(
4721
- (c) => c.type !== "comment" && c.type !== "//" && c.type !== "/*" && c.type !== "{" && c.type !== "}" && c.text.trim() !== ""
4722
- );
4723
- return nonTrivial.length === 0;
4724
- }
4725
- function getAsExpressionType(node) {
4726
- if (node.type !== "as_expression") return null;
4727
- const typeChild = node.children.find((c) => c.fieldName === "type");
4728
- return typeChild?.text ?? null;
4712
+ });
4713
+
4714
+ // src/plugins/registry.ts
4715
+ var registry_exports = {};
4716
+ __export(registry_exports, {
4717
+ PLUGIN_DIR: () => PLUGIN_DIR,
4718
+ discoverAndLoadPlugins: () => discoverAndLoadPlugins,
4719
+ getPluginDir: () => getPluginDir,
4720
+ pluginRegistry: () => pluginRegistry
4721
+ });
4722
+ import { join as join2 } from "node:path";
4723
+ import { readdir } from "node:fs/promises";
4724
+ import { existsSync as existsSync2 } from "node:fs";
4725
+ function getPluginDir(rootDir) {
4726
+ return join2(rootDir, PLUGIN_DIR);
4729
4727
  }
4730
- function getAsExpressionContext(node) {
4731
- if (isInsideCatch(node)) return "catch";
4732
- const funcAncestor = findAncestor(
4733
- node,
4734
- (n) => ["function_declaration", "arrow_function", "method_definition"].includes(n.type)
4735
- );
4736
- if (funcAncestor) {
4737
- const text = funcAncestor.text.toLowerCase();
4738
- if (/prisma|drizzle|sequelize|mongoose|typeorm|knex|supabase/.test(text))
4739
- return "orm";
4740
- if (/json\.parse|parse\(/.test(text)) return "json";
4728
+ async function discoverAndLoadPlugins(rootDir) {
4729
+ if (pluginRegistry.isLoaded) {
4730
+ return pluginRegistry.getEngines();
4741
4731
  }
4742
- return "unknown";
4743
- }
4744
- function extractImportFromNode(node) {
4745
- if (node.type !== "import_statement" && node.type !== "import_declaration")
4746
- return null;
4747
- const sourceNode = node.children.find(
4748
- (c) => c.type === "string" || c.fieldName === "source"
4749
- );
4750
- if (!sourceNode) return null;
4751
- const source = sourceNode.text.replace(/^['"]|['"]$/g, "");
4752
- const isTypeOnly = node.text.includes("import type ");
4753
- const symbols = [];
4754
- const namedImport = node.children.find(
4755
- (c) => c.type === "named_imports" || c.type === "import_clause"
4756
- );
4757
- if (namedImport) {
4758
- for (const child of namedImport.children) {
4759
- if (child.type === "identifier" || child.type === "type_identifier" || child.type === "import_specifier") {
4760
- symbols.push(child.text);
4732
+ const pluginDir = getPluginDir(rootDir);
4733
+ if (!existsSync2(pluginDir)) {
4734
+ pluginRegistry.setLoaded(true);
4735
+ return [];
4736
+ }
4737
+ try {
4738
+ const files = await readdir(pluginDir);
4739
+ const pluginPaths = files.filter((f) => f.endsWith(".js") || f.endsWith(".mjs")).map((f) => join2(pluginDir, f));
4740
+ const engines = await loadPlugins(pluginPaths);
4741
+ for (let i = 0; i < engines.length; i++) {
4742
+ const engine = engines[i];
4743
+ pluginRegistry.register({
4744
+ id: engine.name,
4745
+ path: pluginPaths[i],
4746
+ engine,
4747
+ loaded: true
4748
+ });
4749
+ }
4750
+ for (let i = 0; i < pluginPaths.length; i++) {
4751
+ const path2 = pluginPaths[i];
4752
+ const engine = engines.find((e) => {
4753
+ const entry = pluginRegistry.getAll().find((ent) => ent.path === path2);
4754
+ return entry?.engine;
4755
+ });
4756
+ if (!engine && !pluginRegistry.has(path2)) {
4757
+ pluginRegistry.register({
4758
+ id: `plugin-${i}`,
4759
+ path: path2,
4760
+ engine: null,
4761
+ loaded: false,
4762
+ error: "Failed to load plugin module"
4763
+ });
4761
4764
  }
4762
4765
  }
4766
+ pluginRegistry.setLoaded(true);
4767
+ return engines;
4768
+ } catch {
4769
+ pluginRegistry.setLoaded(true);
4770
+ return [];
4763
4771
  }
4764
- return { source, symbols, line: node.startRow + 1, isTypeOnly };
4765
4772
  }
4766
- function findPythonFunctions(root) {
4767
- const funcNodes = findNodesOfTypes(root, [
4768
- "function_definition",
4769
- "decorated_definition"
4770
- ]);
4771
- const results = [];
4772
- for (const node of funcNodes) {
4773
- if (node.type === "decorated_definition") {
4774
- const inner = node.children.find(
4775
- (c) => c.type === "function_definition"
4776
- );
4777
- if (inner) {
4778
- results.push(extractPythonFunctionInfo(inner, node));
4773
+ var PluginRegistry, pluginRegistry, PLUGIN_DIR;
4774
+ var init_registry = __esm({
4775
+ "src/plugins/registry.ts"() {
4776
+ "use strict";
4777
+ init_loader();
4778
+ PluginRegistry = class {
4779
+ entries = /* @__PURE__ */ new Map();
4780
+ loaded = false;
4781
+ /** Get all registered plugins */
4782
+ getAll() {
4783
+ return [...this.entries.values()];
4784
+ }
4785
+ /** Get all successfully loaded engines */
4786
+ getEngines() {
4787
+ return [...this.entries.values()].filter((e) => e.loaded).map((e) => e.engine);
4788
+ }
4789
+ /** Get a specific plugin by id */
4790
+ get(id) {
4791
+ return this.entries.get(id);
4792
+ }
4793
+ /** Get a loaded engine by name */
4794
+ getEngine(name) {
4795
+ const entry = this.entries.get(name);
4796
+ return entry?.loaded ? entry.engine : void 0;
4797
+ }
4798
+ /** Register a plugin entry */
4799
+ register(entry) {
4800
+ this.entries.set(entry.id, entry);
4801
+ }
4802
+ /** Remove a plugin by id */
4803
+ remove(id) {
4804
+ return this.entries.delete(id);
4805
+ }
4806
+ /** Check if a plugin is registered */
4807
+ has(id) {
4808
+ return this.entries.has(id);
4809
+ }
4810
+ /** Number of registered plugins */
4811
+ get size() {
4812
+ return this.entries.size;
4813
+ }
4814
+ /** Whether the registry has been loaded from disk */
4815
+ get isLoaded() {
4816
+ return this.loaded;
4817
+ }
4818
+ /** Mark the registry as loaded */
4819
+ setLoaded(loaded) {
4820
+ this.loaded = loaded;
4779
4821
  }
4780
- } else {
4781
- results.push(extractPythonFunctionInfo(node));
4782
- }
4783
- }
4784
- return results;
4785
- }
4786
- function findPythonClasses(root) {
4787
- const classNodes = findNodesOfTypes(root, [
4788
- "class_definition",
4789
- "decorated_definition"
4790
- ]);
4791
- const results = [];
4792
- for (const node of classNodes) {
4793
- if (node.type === "decorated_definition") {
4794
- const inner = node.children.find(
4795
- (c) => c.type === "class_definition"
4796
- );
4797
- if (inner) {
4798
- results.push(extractPythonClassInfo(inner, node));
4822
+ /** Clear all entries */
4823
+ clear() {
4824
+ this.entries.clear();
4825
+ this.loaded = false;
4799
4826
  }
4800
- } else {
4801
- results.push(extractPythonClassInfo(node));
4802
- }
4803
- }
4804
- return results;
4805
- }
4806
- function findPythonImports(root) {
4807
- const importNodes = findNodesOfTypes(root, [
4808
- "import_statement",
4809
- "import_from_statement"
4810
- ]);
4811
- return importNodes.map(extractPythonImportInfo);
4812
- }
4813
- function isPythonFunctionStub(funcInfo) {
4814
- const text = funcInfo.text;
4815
- const bodyLines = text.split("\n").slice(1).join("\n").trim();
4816
- if (/^(\s*(pass|\.\.\.)\s*)$/.test(bodyLines)) return true;
4817
- const docstringOnly = bodyLines.replace(/"""[\s\S]*?"""/g, "").replace(/'''[\s\S]*?'''/g, "").trim();
4818
- if (docstringOnly === "" || docstringOnly === "pass" || docstringOnly === "...") {
4819
- return true;
4827
+ };
4828
+ pluginRegistry = new PluginRegistry();
4829
+ PLUGIN_DIR = ".deep-slop/plugins";
4820
4830
  }
4821
- return false;
4822
- }
4823
- function detectPythonAIPatterns(root) {
4824
- const findings = [];
4825
- const functions = findPythonFunctions(root);
4826
- for (const fn of functions) {
4827
- if (isPythonFunctionStub(fn)) {
4828
- findings.push({
4829
- type: "python-stub-function",
4830
- message: `Function '${fn.name}' is a stub (only pass/ellipsis)`,
4831
- line: fn.line
4831
+ });
4832
+
4833
+ // src/utils/suppress.ts
4834
+ import { join as join3 } from "node:path";
4835
+ import { readFileSync as readFileSync2 } from "node:fs";
4836
+ function parseSuppressDirectives(content) {
4837
+ const entries = [];
4838
+ const lines = content.split("\n");
4839
+ for (let i = 0; i < lines.length; i++) {
4840
+ const line = lines[i].trim();
4841
+ const lineNum = i + 1;
4842
+ const ignoreNextMatch = line.match(
4843
+ /\/\/\s*deep-slop-ignore-next(?:\s+(.+))?$/
4844
+ );
4845
+ if (ignoreNextMatch) {
4846
+ entries.push({
4847
+ directiveLine: lineNum,
4848
+ targetLine: lineNum + 1,
4849
+ rules: parseRuleList(ignoreNextMatch[1]),
4850
+ type: "next-line"
4832
4851
  });
4852
+ continue;
4833
4853
  }
4834
- }
4835
- const tryNodes = findNodesOfType(root, "try_statement");
4836
- for (const tryNode of tryNodes) {
4837
- const exceptNodes = tryNode.children.filter(
4838
- (c) => c.type === "except_clause"
4854
+ const ignoreLineMatch = line.match(
4855
+ /\/\/\s*deep-slop-ignore-line(?:\s+(.+))?$/
4839
4856
  );
4840
- for (const exceptNode of exceptNodes) {
4841
- const hasSpecificType = exceptNode.children.some(
4842
- (c) => c.type === "identifier" || c.type === "tuple"
4843
- );
4844
- if (!hasSpecificType) {
4845
- const bareExcept = exceptNode.children.find(
4846
- (c) => c.text && c.text.includes("except")
4847
- );
4848
- findings.push({
4849
- type: "python-bare-except",
4850
- message: "Bare except clause catches all exceptions",
4851
- line: exceptNode.startRow + 1
4857
+ if (ignoreLineMatch) {
4858
+ entries.push({
4859
+ directiveLine: lineNum,
4860
+ targetLine: lineNum,
4861
+ rules: parseRuleList(ignoreLineMatch[1]),
4862
+ type: "line"
4863
+ });
4864
+ continue;
4865
+ }
4866
+ const ignoreRuleMatch = line.match(
4867
+ /\/\/\s*deep-slop-ignore(?:\s+(.+))?$/
4868
+ );
4869
+ if (ignoreRuleMatch) {
4870
+ const rulePart = ignoreRuleMatch[1];
4871
+ if (rulePart !== "start" && rulePart !== "end") {
4872
+ entries.push({
4873
+ directiveLine: lineNum,
4874
+ targetLine: lineNum + 1,
4875
+ rules: parseRuleList(rulePart),
4876
+ type: "next-line"
4852
4877
  });
4878
+ continue;
4853
4879
  }
4854
4880
  }
4855
- }
4856
- const commentNodes = findNodesOfType(root, "comment");
4857
- for (const comment of commentNodes) {
4858
- const text = comment.text.toLowerCase();
4859
- if (/todo|fixme|hack|xxx/.test(text)) {
4860
- findings.push({
4861
- type: "python-todo-stub",
4862
- message: `TODO/FIXME comment: ${comment.text.trim()}`,
4863
- line: comment.startRow + 1
4881
+ const ignoreStartMatch = line.match(
4882
+ /\/\/\s*deep-slop-ignore-start(?:\s+(.+))?$/
4883
+ );
4884
+ if (ignoreStartMatch) {
4885
+ entries.push({
4886
+ directiveLine: lineNum,
4887
+ targetLine: lineNum,
4888
+ rules: parseRuleList(ignoreStartMatch[1]),
4889
+ type: "block-start"
4864
4890
  });
4891
+ continue;
4865
4892
  }
4866
- }
4867
- const callNodes = findNodesOfType(root, "call");
4868
- for (const call of callNodes) {
4869
- const func = call.children[0];
4870
- if (func && func.text === "print") {
4871
- findings.push({
4872
- type: "python-print-leftover",
4873
- message: "print() statement \u2014 likely debug leftover",
4874
- line: call.startRow + 1
4893
+ const ignoreEndMatch = line.match(
4894
+ /\/\/\s*deep-slop-ignore-end/
4895
+ );
4896
+ if (ignoreEndMatch) {
4897
+ entries.push({
4898
+ directiveLine: lineNum,
4899
+ targetLine: lineNum,
4900
+ rules: /* @__PURE__ */ new Set(),
4901
+ type: "block-end"
4875
4902
  });
4903
+ continue;
4876
4904
  }
4877
- }
4878
- return findings;
4879
- }
4880
- function extractPythonFunctionInfo(funcNode, decoratedParent) {
4881
- const nameNode = funcNode.children.find((c) => c.fieldName === "name") ?? funcNode.children.find((c) => c.type === "identifier");
4882
- const name = nameNode?.text ?? "(anonymous)";
4883
- const params = funcNode.children.find(
4884
- (c) => c.type === "parameters"
4885
- );
4886
- const parameters = params ? params.children.filter((c) => c.type === "identifier" || c.type === "typed_parameter" || c.type === "default_parameter").map((c) => c.children[0]?.text ?? c.text) : [];
4887
- const isAsync = funcNode.children.some(
4888
- (c) => c.type === "async"
4889
- );
4890
- const decorators = [];
4891
- if (decoratedParent) {
4892
- for (const child of decoratedParent.children) {
4893
- if (child.type === "decorator") {
4894
- decorators.push(child.text.replace("@", ""));
4895
- }
4905
+ const nextLineMatch = line.match(
4906
+ /\/\/\s*deep-slop-disable-next-line(?:\s+(.+))?$/
4907
+ );
4908
+ if (nextLineMatch) {
4909
+ entries.push({
4910
+ directiveLine: lineNum,
4911
+ targetLine: lineNum + 1,
4912
+ rules: parseRuleList(nextLineMatch[1]),
4913
+ type: "next-line"
4914
+ });
4915
+ continue;
4916
+ }
4917
+ const lineMatch = line.match(
4918
+ /\/\/\s*deep-slop-disable-line(?:\s+(.+))?$/
4919
+ );
4920
+ if (lineMatch) {
4921
+ entries.push({
4922
+ directiveLine: lineNum,
4923
+ targetLine: lineNum,
4924
+ rules: parseRuleList(lineMatch[1]),
4925
+ type: "line"
4926
+ });
4927
+ continue;
4928
+ }
4929
+ const blockStart = line.match(
4930
+ /\/\*\s*deep-slop-disable(?:\s+(.+))?\s*\//
4931
+ );
4932
+ if (blockStart) {
4933
+ entries.push({
4934
+ directiveLine: lineNum,
4935
+ targetLine: lineNum,
4936
+ rules: parseRuleList(blockStart[1]),
4937
+ type: "block-start"
4938
+ });
4939
+ continue;
4940
+ }
4941
+ const blockEnd = line.match(/\/\*\s*deep-slop-enable\s*\//);
4942
+ if (blockEnd) {
4943
+ entries.push({
4944
+ directiveLine: lineNum,
4945
+ targetLine: lineNum,
4946
+ rules: /* @__PURE__ */ new Set(),
4947
+ type: "block-end"
4948
+ });
4896
4949
  }
4897
4950
  }
4898
- return {
4899
- name,
4900
- decorators,
4901
- parameters,
4902
- isAsync,
4903
- line: funcNode.startRow + 1,
4904
- endLine: funcNode.endRow + 1,
4905
- text: funcNode.text
4906
- };
4951
+ return entries;
4907
4952
  }
4908
- function extractPythonClassInfo(classNode, decoratedParent) {
4909
- const nameNode = classNode.children.find((c) => c.fieldName === "name") ?? classNode.children.find((c) => c.type === "identifier");
4910
- const name = nameNode?.text ?? "(anonymous)";
4911
- const argList = classNode.children.find(
4912
- (c) => c.type === "argument_list"
4913
- );
4914
- const bases = argList ? argList.children.filter((c) => c.type === "identifier" || c.type === "attribute").map((c) => c.text) : [];
4915
- const decorators = [];
4916
- if (decoratedParent) {
4917
- for (const child of decoratedParent.children) {
4918
- if (child.type === "decorator") {
4919
- decorators.push(child.text.replace("@", ""));
4920
- }
4953
+ function parseRuleList(rulesStr) {
4954
+ if (!rulesStr) return /* @__PURE__ */ new Set();
4955
+ const rules = rulesStr.split(/[,\\s]+/).map((r) => r.trim()).filter(Boolean);
4956
+ return new Set(rules);
4957
+ }
4958
+ function buildSuppressChecker(entries) {
4959
+ const blockRanges = [];
4960
+ let currentStart = null;
4961
+ for (const entry of entries) {
4962
+ if (entry.type === "block-start") {
4963
+ currentStart = { line: entry.directiveLine, rules: entry.rules };
4964
+ } else if (entry.type === "block-end" && currentStart) {
4965
+ blockRanges.push({
4966
+ startLine: currentStart.line,
4967
+ endLine: entry.directiveLine,
4968
+ rules: currentStart.rules
4969
+ });
4970
+ currentStart = null;
4921
4971
  }
4922
4972
  }
4923
- const body = classNode.children.find(
4924
- (c) => c.type === "block"
4925
- );
4926
- const methods = [];
4927
- if (body) {
4928
- for (const child of body.children) {
4929
- if (child.type === "function_definition") {
4930
- methods.push(extractPythonFunctionInfo(child));
4931
- } else if (child.type === "decorated_definition") {
4932
- const inner = child.children.find(
4933
- (c) => c.type === "function_definition"
4934
- );
4935
- if (inner) {
4936
- methods.push(extractPythonFunctionInfo(inner, child));
4973
+ if (currentStart) {
4974
+ blockRanges.push({
4975
+ startLine: currentStart.line,
4976
+ endLine: 999999,
4977
+ rules: currentStart.rules
4978
+ });
4979
+ }
4980
+ return (line, rule) => {
4981
+ for (const entry of entries) {
4982
+ if (entry.type === "next-line" || entry.type === "line") {
4983
+ if (entry.targetLine === line) {
4984
+ if (entry.rules.size === 0 || entry.rules.has(rule)) {
4985
+ return true;
4986
+ }
4937
4987
  }
4938
4988
  }
4939
4989
  }
4940
- }
4941
- return {
4942
- name,
4943
- bases,
4944
- decorators,
4945
- methods,
4946
- line: classNode.startRow + 1,
4947
- endLine: classNode.endRow + 1,
4948
- text: classNode.text
4990
+ for (const range2 of blockRanges) {
4991
+ if (line >= range2.startLine && line <= range2.endLine) {
4992
+ if (range2.rules.size === 0 || range2.rules.has(rule)) {
4993
+ return true;
4994
+ }
4995
+ }
4996
+ }
4997
+ return false;
4949
4998
  };
4950
4999
  }
4951
- function extractPythonImportInfo(importNode) {
4952
- const isFromImport = importNode.type === "import_from_statement";
4953
- let module = "";
4954
- const symbols = [];
4955
- if (isFromImport) {
4956
- const moduleNode = importNode.children.find(
4957
- (c) => c.fieldName === "module_name" || c.type === "dotted_name" && c.fieldName !== "name" || c.type === "identifier" && c.fieldName !== "name"
4958
- );
4959
- module = moduleNode?.text ?? "";
4960
- const nameList = importNode.children.find(
4961
- (c) => c.type === "dotted_name" && c !== moduleNode
4962
- );
4963
- const identifierChildren = importNode.children.filter(
4964
- (c) => c.type === "identifier" && c !== moduleNode
4965
- );
4966
- if (nameList) {
4967
- symbols.push(nameList.text);
5000
+ function buildSuppressMap(content) {
5001
+ const entries = parseSuppressDirectives(content);
5002
+ const isSuppressed = buildSuppressChecker(entries);
5003
+ return { entries, isSuppressed };
5004
+ }
5005
+ function loadIgnoreFile(rootDir) {
5006
+ const ignorePath = join3(rootDir, ".deep-slop", ".deep-slop-ignore");
5007
+ try {
5008
+ const content = readFileSync2(ignorePath, "utf-8");
5009
+ return content.split("\n").map((line) => line.split("#")[0].trim()).filter((line) => line && !line.startsWith("file:"));
5010
+ } catch {
5011
+ return [];
5012
+ }
5013
+ }
5014
+ function applySuppressDirectives(diagnostics, fileContents, globallySuppressed = /* @__PURE__ */ new Set()) {
5015
+ const suppressMaps = /* @__PURE__ */ new Map();
5016
+ for (const [filePath, content] of fileContents) {
5017
+ suppressMaps.set(filePath, buildSuppressMap(content));
5018
+ }
5019
+ const filtered = [];
5020
+ let suppressedCount = 0;
5021
+ for (const diag8 of diagnostics) {
5022
+ if (globallySuppressed.has(diag8.rule)) {
5023
+ suppressedCount++;
5024
+ continue;
4968
5025
  }
4969
- for (const id of identifierChildren) {
4970
- if (id.text !== "from" && id.text !== "import" && id.text !== module) {
4971
- symbols.push(id.text);
4972
- }
5026
+ const map = suppressMaps.get(diag8.filePath);
5027
+ if (map && map.isSuppressed(diag8.line, diag8.rule)) {
5028
+ suppressedCount++;
5029
+ } else {
5030
+ filtered.push(diag8);
4973
5031
  }
4974
- } else {
4975
- const dottedNames = importNode.children.filter(
4976
- (c) => c.type === "dotted_name"
4977
- );
4978
- const identifiers = importNode.children.filter(
4979
- (c) => c.type === "identifier"
4980
- );
4981
- for (const dn of dottedNames) {
4982
- symbols.push(dn.text);
5032
+ }
5033
+ return { filtered, suppressedCount };
5034
+ }
5035
+ var init_suppress = __esm({
5036
+ "src/utils/suppress.ts"() {
5037
+ "use strict";
5038
+ }
5039
+ });
5040
+
5041
+ // src/utils/file-utils.ts
5042
+ import { readFile as readFile2 } from "node:fs/promises";
5043
+ async function readFileContent(filePath) {
5044
+ const buffer = await readFile2(filePath);
5045
+ let content = buffer.toString("utf-8");
5046
+ if (content.charCodeAt(0) === 65279) {
5047
+ content = content.slice(1);
5048
+ }
5049
+ return content;
5050
+ }
5051
+ function detectEncodingAnomalies(content) {
5052
+ const hasBom = content.charCodeAt(0) === 65279;
5053
+ const hasCrlf = content.includes("\r\n");
5054
+ const hasZwnbsp = content.includes("\uFEFF");
5055
+ const lfOnly = (content.match(/(?<!\r)\n/g) ?? []).length;
5056
+ const crlfCount = (content.match(/\r\n/g) ?? []).length;
5057
+ const lineEnding = crlfCount > 0 && lfOnly > crlfCount ? "mixed" : crlfCount > 0 ? "crlf" : "lf";
5058
+ return { hasBom, hasCrlf, hasZwnbsp, lineEnding };
5059
+ }
5060
+ function toLines(content) {
5061
+ return content.split("\n").map((text, i) => ({ num: i + 1, text }));
5062
+ }
5063
+ function extractImports(content, language) {
5064
+ const imports = [];
5065
+ const lines = toLines(content);
5066
+ for (const { num, text } of lines) {
5067
+ const trimmed = text.trim();
5068
+ if (language === "typescript" || language === "javascript") {
5069
+ const jsMatch = trimmed.match(
5070
+ /^import\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/
5071
+ );
5072
+ if (jsMatch) {
5073
+ imports.push({
5074
+ line: num,
5075
+ source: jsMatch[1],
5076
+ raw: trimmed,
5077
+ isTypeOnly: trimmed.includes("import type"),
5078
+ isDefault: !trimmed.includes("{")
5079
+ });
5080
+ }
5081
+ const dynMatch = trimmed.match(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/);
5082
+ if (dynMatch) {
5083
+ imports.push({
5084
+ line: num,
5085
+ source: dynMatch[1],
5086
+ raw: trimmed,
5087
+ isTypeOnly: false,
5088
+ isDynamic: true
5089
+ });
5090
+ }
5091
+ const reqMatch = trimmed.match(/(?:const|let|var)\s+[^=]*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
5092
+ if (reqMatch) {
5093
+ imports.push({
5094
+ line: num,
5095
+ source: reqMatch[1],
5096
+ raw: trimmed,
5097
+ isTypeOnly: false,
5098
+ isRequire: true
5099
+ });
5100
+ }
4983
5101
  }
4984
- for (const id of identifiers) {
4985
- if (id.text !== "import") {
4986
- symbols.push(id.text);
5102
+ if (language === "python") {
5103
+ const pyMatch = trimmed.match(/^from\s+([^\s]+)\s+import/);
5104
+ if (pyMatch) {
5105
+ imports.push({ line: num, source: pyMatch[1], raw: trimmed, isTypeOnly: false });
5106
+ }
5107
+ const pyImport = trimmed.match(/^import\s+([^\s]+)/);
5108
+ if (pyImport) {
5109
+ imports.push({ line: num, source: pyImport[1], raw: trimmed, isTypeOnly: false });
4987
5110
  }
4988
5111
  }
4989
- module = symbols[0] ?? "";
4990
- }
4991
- return {
4992
- module,
4993
- symbols,
4994
- isFromImport,
4995
- line: importNode.startRow + 1,
4996
- text: importNode.text
4997
- };
4998
- }
4999
- function convertNode(node, parent) {
5000
- const children = [];
5001
- const astNode = {
5002
- type: node.type,
5003
- text: node.text,
5004
- startRow: node.startPosition.row,
5005
- startCol: node.startPosition.column,
5006
- endRow: node.endPosition.row,
5007
- endCol: node.endPosition.column,
5008
- children,
5009
- parent,
5010
- fieldName: null
5011
- };
5012
- for (let i = 0; i < node.childCount; i++) {
5013
- const child = node.child(i);
5014
- if (child) {
5015
- const converted = convertNode(child, astNode);
5016
- converted.fieldName = node.fieldNameForChild(i) ?? null;
5017
- children.push(converted);
5112
+ if (language === "go") {
5113
+ const goMatch = trimmed.match(/"([^"]+)"/);
5114
+ if (trimmed.startsWith("import") && goMatch) {
5115
+ imports.push({ line: num, source: goMatch[1], raw: trimmed, isTypeOnly: false });
5116
+ }
5018
5117
  }
5019
5118
  }
5020
- return astNode;
5119
+ return imports;
5021
5120
  }
5022
- var parserInstance, tsLang, tsxLang, initPromise, initDone, initOk, pyLang, pyInitDone, pyInitOk, goLang, goInitDone, goInitOk, rustLang, rustInitDone, rustInitOk, phpLang, phpInitDone, phpInitOk, csharpLang, csharpInitDone, csharpInitOk, swiftLang, swiftInitDone, swiftInitOk;
5023
- var init_tree_sitter = __esm({
5024
- "src/utils/tree-sitter.ts"() {
5121
+ var init_file_utils = __esm({
5122
+ "src/utils/file-utils.ts"() {
5025
5123
  "use strict";
5026
- parserInstance = null;
5027
- tsLang = null;
5028
- tsxLang = null;
5029
- initPromise = null;
5030
- initDone = false;
5031
- initOk = false;
5032
- pyLang = null;
5033
- pyInitDone = false;
5034
- pyInitOk = false;
5035
- goLang = null;
5036
- goInitDone = false;
5037
- goInitOk = false;
5038
- rustLang = null;
5039
- rustInitDone = false;
5040
- rustInitOk = false;
5041
- phpLang = null;
5042
- phpInitDone = false;
5043
- phpInitOk = false;
5044
- csharpLang = null;
5045
- csharpInitDone = false;
5046
- csharpInitOk = false;
5047
- swiftLang = null;
5048
- swiftInitDone = false;
5049
- swiftInitOk = false;
5050
5124
  }
5051
5125
  });
5052
5126
 
@@ -25862,6 +25936,7 @@ import { performance as performance3 } from "node:perf_hooks";
25862
25936
  async function runScan(context, callbacks) {
25863
25937
  const startTotal = performance3.now();
25864
25938
  clearFileCache();
25939
+ clearParseCache();
25865
25940
  if (context.files?.length) {
25866
25941
  await preloadFiles(context.files);
25867
25942
  }
@@ -26039,6 +26114,7 @@ var init_orchestrator = __esm({
26039
26114
  init_rule_overrides();
26040
26115
  init_store();
26041
26116
  init_file_cache();
26117
+ init_tree_sitter();
26042
26118
  init_registry();
26043
26119
  init_suppress();
26044
26120
  EXT_TO_LANG = {
@@ -29831,7 +29907,7 @@ function computeExitCode(options) {
29831
29907
  init_formatter();
29832
29908
 
29833
29909
  // src/version.ts
29834
- var APP_VERSION = "1.5.0";
29910
+ var APP_VERSION = "1.6.0";
29835
29911
 
29836
29912
  // src/output/sarif.ts
29837
29913
  function mapSeverity4(sev) {