truecourse 0.5.7 → 0.5.8-windows.2
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/README.md +27 -1
- package/cli.mjs +1970 -181
- package/package.json +1 -1
- package/public/assets/{index-DaPvUT-_.js → index-Bw6ucreX.js} +13 -13
- package/public/index.html +1 -1
- package/server.mjs +1776 -116
- package/skills/truecourse/truecourse-analyze/SKILL.md +8 -0
- package/skills/truecourse/truecourse-hooks/SKILL.md +3 -3
package/cli.mjs
CHANGED
|
@@ -3976,7 +3976,7 @@ var init_paths = __esm({
|
|
|
3976
3976
|
"packages/core/dist/config/paths.js"() {
|
|
3977
3977
|
"use strict";
|
|
3978
3978
|
TRUECOURSE_DIR = ".truecourse";
|
|
3979
|
-
GITIGNORE_CONTENTS = "analyses/\
|
|
3979
|
+
GITIGNORE_CONTENTS = "analyses/\nhistory.json\ndiff.json\nui-state.json\nlogs/\n.analyze.lock\n";
|
|
3980
3980
|
}
|
|
3981
3981
|
});
|
|
3982
3982
|
|
|
@@ -4077,15 +4077,17 @@ __export(helpers_exports, {
|
|
|
4077
4077
|
requireRegisteredRepo: () => requireRegisteredRepo,
|
|
4078
4078
|
severityColor: () => severityColor,
|
|
4079
4079
|
severityIcon: () => severityIcon,
|
|
4080
|
+
syncShippedSkills: () => syncShippedSkills,
|
|
4080
4081
|
writeConfig: () => writeConfig
|
|
4081
4082
|
});
|
|
4082
4083
|
import { exec as exec2 } from "node:child_process";
|
|
4083
|
-
import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
4084
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4084
4085
|
import fs3 from "node:fs";
|
|
4085
4086
|
import os2 from "node:os";
|
|
4086
4087
|
import path3 from "node:path";
|
|
4087
4088
|
import { dirname, resolve } from "node:path";
|
|
4088
4089
|
import { fileURLToPath } from "node:url";
|
|
4090
|
+
import { createHash } from "node:crypto";
|
|
4089
4091
|
function getConfigPath() {
|
|
4090
4092
|
return path3.join(os2.homedir(), ".truecourse", "config.json");
|
|
4091
4093
|
}
|
|
@@ -4352,6 +4354,31 @@ function computeMissingSkills(repoPath) {
|
|
|
4352
4354
|
function hasInstalledSkills(repoPath) {
|
|
4353
4355
|
return computeMissingSkills(repoPath).length === 0;
|
|
4354
4356
|
}
|
|
4357
|
+
function skillFilePath(parent, name) {
|
|
4358
|
+
return resolve(parent, name, "SKILL.md");
|
|
4359
|
+
}
|
|
4360
|
+
function sha256OfFile(filePath) {
|
|
4361
|
+
if (!existsSync(filePath)) return null;
|
|
4362
|
+
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
|
4363
|
+
}
|
|
4364
|
+
function skillsLockPath(repoPath) {
|
|
4365
|
+
return resolve(skillsParentDir(repoPath), SKILLS_LOCKFILE_NAME);
|
|
4366
|
+
}
|
|
4367
|
+
function readSkillsLock(repoPath) {
|
|
4368
|
+
const lockPath2 = skillsLockPath(repoPath);
|
|
4369
|
+
if (!existsSync(lockPath2)) return {};
|
|
4370
|
+
try {
|
|
4371
|
+
const parsed = JSON.parse(readFileSync(lockPath2, "utf-8"));
|
|
4372
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4373
|
+
} catch {
|
|
4374
|
+
return {};
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
function writeSkillsLock(repoPath, lock) {
|
|
4378
|
+
const lockPath2 = skillsLockPath(repoPath);
|
|
4379
|
+
mkdirSync(dirname(lockPath2), { recursive: true });
|
|
4380
|
+
writeFileSync(lockPath2, JSON.stringify(lock, null, 2) + "\n");
|
|
4381
|
+
}
|
|
4355
4382
|
function copySkills(repoPath, skillNames) {
|
|
4356
4383
|
const src = resolveSkillsSrcDir();
|
|
4357
4384
|
if (!src) {
|
|
@@ -4360,25 +4387,97 @@ function copySkills(repoPath, skillNames) {
|
|
|
4360
4387
|
}
|
|
4361
4388
|
const parent = skillsParentDir(repoPath);
|
|
4362
4389
|
mkdirSync(parent, { recursive: true });
|
|
4390
|
+
const lock = readSkillsLock(repoPath);
|
|
4363
4391
|
for (const name of skillNames) {
|
|
4364
4392
|
const skillSrc = resolve(src, name);
|
|
4365
4393
|
const skillDest = resolve(parent, name);
|
|
4366
4394
|
if (existsSync(skillDest)) continue;
|
|
4367
4395
|
cpSync(skillSrc, skillDest, { recursive: true });
|
|
4396
|
+
const sha = sha256OfFile(skillFilePath(parent, name));
|
|
4397
|
+
if (sha) lock[name] = sha;
|
|
4368
4398
|
}
|
|
4399
|
+
writeSkillsLock(repoPath, lock);
|
|
4369
4400
|
O2.success(
|
|
4370
4401
|
`Installed ${skillNames.length} Claude Code skill${skillNames.length === 1 ? "" : "s"}:`
|
|
4371
4402
|
);
|
|
4372
4403
|
for (const name of skillNames) O2.message(` - ${name}`);
|
|
4373
4404
|
}
|
|
4405
|
+
function syncShippedSkills(repoPath, srcDirOverride) {
|
|
4406
|
+
const src = srcDirOverride ?? resolveSkillsSrcDir();
|
|
4407
|
+
if (!src) return;
|
|
4408
|
+
const parent = skillsParentDir(repoPath);
|
|
4409
|
+
if (!existsSync(parent)) return;
|
|
4410
|
+
const shipped = listSkillDirs(src);
|
|
4411
|
+
const lock = readSkillsLock(repoPath);
|
|
4412
|
+
const updated = [];
|
|
4413
|
+
const migrated = [];
|
|
4414
|
+
const customized = [];
|
|
4415
|
+
let lockChanged = false;
|
|
4416
|
+
const overwriteWithShipped = (name, shippedSha) => {
|
|
4417
|
+
const skillSrc = resolve(src, name);
|
|
4418
|
+
const skillDest = resolve(parent, name);
|
|
4419
|
+
rmSync(skillDest, { recursive: true, force: true });
|
|
4420
|
+
cpSync(skillSrc, skillDest, { recursive: true });
|
|
4421
|
+
lock[name] = shippedSha;
|
|
4422
|
+
lockChanged = true;
|
|
4423
|
+
};
|
|
4424
|
+
for (const name of shipped) {
|
|
4425
|
+
const skillSrcFile = skillFilePath(src, name);
|
|
4426
|
+
const skillDestFile = skillFilePath(parent, name);
|
|
4427
|
+
if (!existsSync(skillDestFile)) continue;
|
|
4428
|
+
const shippedSha = sha256OfFile(skillSrcFile);
|
|
4429
|
+
const installedSha = sha256OfFile(skillDestFile);
|
|
4430
|
+
if (!shippedSha || !installedSha) continue;
|
|
4431
|
+
if (shippedSha === installedSha) {
|
|
4432
|
+
if (lock[name] !== shippedSha) {
|
|
4433
|
+
lock[name] = shippedSha;
|
|
4434
|
+
lockChanged = true;
|
|
4435
|
+
}
|
|
4436
|
+
continue;
|
|
4437
|
+
}
|
|
4438
|
+
const recordedSha = lock[name];
|
|
4439
|
+
if (!recordedSha) {
|
|
4440
|
+
overwriteWithShipped(name, shippedSha);
|
|
4441
|
+
migrated.push(name);
|
|
4442
|
+
continue;
|
|
4443
|
+
}
|
|
4444
|
+
if (installedSha === recordedSha) {
|
|
4445
|
+
overwriteWithShipped(name, shippedSha);
|
|
4446
|
+
updated.push(name);
|
|
4447
|
+
} else {
|
|
4448
|
+
customized.push(name);
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
if (lockChanged) writeSkillsLock(repoPath, lock);
|
|
4452
|
+
if (updated.length > 0) {
|
|
4453
|
+
O2.info(
|
|
4454
|
+
`Updated ${updated.length} Claude Code skill${updated.length === 1 ? "" : "s"} to the current version: ${updated.join(", ")}`
|
|
4455
|
+
);
|
|
4456
|
+
}
|
|
4457
|
+
if (migrated.length > 0) {
|
|
4458
|
+
const word = migrated.length === 1 ? "skill" : "skills";
|
|
4459
|
+
O2.info(
|
|
4460
|
+
`Migrated ${migrated.length} pre-existing Claude Code ${word} to the current shipped version: ${migrated.join(", ")}.
|
|
4461
|
+
If you had local edits, they're recoverable from git history. Future upgrades will preserve customizations automatically.`
|
|
4462
|
+
);
|
|
4463
|
+
}
|
|
4464
|
+
if (customized.length > 0) {
|
|
4465
|
+
const word = customized.length === 1 ? "skill has" : "skills have";
|
|
4466
|
+
O2.warn(
|
|
4467
|
+
`Claude Code ${word} local edits and a newer version is available \u2014 keeping yours: ${customized.join(", ")}.
|
|
4468
|
+
To take the new version, delete the skill folder under .claude/skills/ and re-run truecourse.`
|
|
4469
|
+
);
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4374
4472
|
async function promptInstallSkills(repoPath, { install } = {}) {
|
|
4473
|
+
if (install === false) return;
|
|
4474
|
+
syncShippedSkills(repoPath);
|
|
4375
4475
|
const missing = computeMissingSkills(repoPath);
|
|
4376
4476
|
if (missing.length === 0) return;
|
|
4377
4477
|
if (install === true) {
|
|
4378
4478
|
copySkills(repoPath, missing);
|
|
4379
4479
|
return;
|
|
4380
4480
|
}
|
|
4381
|
-
if (install === false) return;
|
|
4382
4481
|
if (!isInteractive()) return;
|
|
4383
4482
|
const src = resolveSkillsSrcDir();
|
|
4384
4483
|
const shipped = src ? listSkillDirs(src) : [];
|
|
@@ -4389,7 +4488,7 @@ async function promptInstallSkills(repoPath, { install } = {}) {
|
|
|
4389
4488
|
if (q(answer) || !answer) return;
|
|
4390
4489
|
copySkills(repoPath, missing);
|
|
4391
4490
|
}
|
|
4392
|
-
var DEFAULT_PORT, DEFAULT_CONFIG;
|
|
4491
|
+
var DEFAULT_PORT, DEFAULT_CONFIG, SKILLS_LOCKFILE_NAME;
|
|
4393
4492
|
var init_helpers = __esm({
|
|
4394
4493
|
"tools/cli/src/commands/helpers.ts"() {
|
|
4395
4494
|
"use strict";
|
|
@@ -4398,6 +4497,7 @@ var init_helpers = __esm({
|
|
|
4398
4497
|
init_registry();
|
|
4399
4498
|
DEFAULT_PORT = 3001;
|
|
4400
4499
|
DEFAULT_CONFIG = { runMode: "console" };
|
|
4500
|
+
SKILLS_LOCKFILE_NAME = ".truecourse-skills.json";
|
|
4401
4501
|
}
|
|
4402
4502
|
});
|
|
4403
4503
|
|
|
@@ -10542,7 +10642,12 @@ var init_language_config = __esm({
|
|
|
10542
10642
|
classQuery: `
|
|
10543
10643
|
(class_declaration) @class
|
|
10544
10644
|
(class) @class
|
|
10545
|
-
|
|
10645
|
+
`,
|
|
10646
|
+
// Skip pre-built/minified bundles. They have no analytical value (build
|
|
10647
|
+
// artifacts of source already in the repo or vendored libs) and a single
|
|
10648
|
+
// ~1MB minified line can blow past V8's max string length when the LLM
|
|
10649
|
+
// context router extracts thousands of arrow_function bodies from it.
|
|
10650
|
+
ignorePatterns: ["**/*.min.js", "**/*.min.cjs", "**/*.min.mjs"]
|
|
10546
10651
|
};
|
|
10547
10652
|
PYTHON_CONFIG = {
|
|
10548
10653
|
name: "python",
|
|
@@ -10585,9 +10690,55 @@ var init_language_config = __esm({
|
|
|
10585
10690
|
});
|
|
10586
10691
|
|
|
10587
10692
|
// packages/analyzer/dist/file-discovery.js
|
|
10588
|
-
import { existsSync as existsSync2, readFileSync, readdirSync as readdirSync2, statSync } from "fs";
|
|
10693
|
+
import { existsSync as existsSync2, openSync, readFileSync as readFileSync2, readSync, readdirSync as readdirSync2, statSync, closeSync } from "fs";
|
|
10589
10694
|
import { execFileSync } from "child_process";
|
|
10590
10695
|
import { join, relative, resolve as resolve2 } from "path";
|
|
10696
|
+
function looksLikeBuildArtifact(absPath) {
|
|
10697
|
+
const slash = absPath.lastIndexOf("/");
|
|
10698
|
+
const basename2 = slash >= 0 ? absPath.slice(slash + 1) : absPath;
|
|
10699
|
+
return /\.(bundle|production|development)\.(js|cjs|mjs|jsx|ts|tsx|mts|cts)$/i.test(basename2);
|
|
10700
|
+
}
|
|
10701
|
+
function looksMinified(absPath) {
|
|
10702
|
+
const dotIdx = absPath.lastIndexOf(".");
|
|
10703
|
+
if (dotIdx < 0)
|
|
10704
|
+
return false;
|
|
10705
|
+
const ext2 = absPath.slice(dotIdx);
|
|
10706
|
+
if (!SNIFFED_EXTS.has(ext2))
|
|
10707
|
+
return false;
|
|
10708
|
+
if (looksLikeBuildArtifact(absPath))
|
|
10709
|
+
return true;
|
|
10710
|
+
let size;
|
|
10711
|
+
try {
|
|
10712
|
+
size = statSync(absPath).size;
|
|
10713
|
+
} catch {
|
|
10714
|
+
return false;
|
|
10715
|
+
}
|
|
10716
|
+
if (size < MIN_SIZE_FOR_SNIFF)
|
|
10717
|
+
return false;
|
|
10718
|
+
let fd = null;
|
|
10719
|
+
try {
|
|
10720
|
+
fd = openSync(absPath, "r");
|
|
10721
|
+
const buf = Buffer.alloc(SNIFF_BYTES);
|
|
10722
|
+
const read = readSync(fd, buf, 0, SNIFF_BYTES, 0);
|
|
10723
|
+
let newlines = 0;
|
|
10724
|
+
for (let i = 0; i < read; i++) {
|
|
10725
|
+
if (buf[i] === 10)
|
|
10726
|
+
newlines++;
|
|
10727
|
+
if (newlines >= MIN_NEWLINES)
|
|
10728
|
+
return false;
|
|
10729
|
+
}
|
|
10730
|
+
return true;
|
|
10731
|
+
} catch {
|
|
10732
|
+
return false;
|
|
10733
|
+
} finally {
|
|
10734
|
+
if (fd !== null) {
|
|
10735
|
+
try {
|
|
10736
|
+
closeSync(fd);
|
|
10737
|
+
} catch {
|
|
10738
|
+
}
|
|
10739
|
+
}
|
|
10740
|
+
}
|
|
10741
|
+
}
|
|
10591
10742
|
function isInsideGitWorkTree(dir) {
|
|
10592
10743
|
try {
|
|
10593
10744
|
const out = execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], {
|
|
@@ -10602,7 +10753,7 @@ function buildPostGitFilter(dir) {
|
|
|
10602
10753
|
const ig = (0, import_ignore.default)();
|
|
10603
10754
|
const tcPath = join(dir, ".truecourseignore");
|
|
10604
10755
|
if (existsSync2(tcPath))
|
|
10605
|
-
ig.add(
|
|
10756
|
+
ig.add(readFileSync2(tcPath, "utf8"));
|
|
10606
10757
|
ig.add(".git");
|
|
10607
10758
|
for (const p2 of getAllTestPatterns())
|
|
10608
10759
|
ig.add(p2);
|
|
@@ -10635,8 +10786,11 @@ function discoverFilesViaGit(dir) {
|
|
|
10635
10786
|
}
|
|
10636
10787
|
if (!isFile)
|
|
10637
10788
|
continue;
|
|
10638
|
-
if (detectLanguage(abs))
|
|
10639
|
-
|
|
10789
|
+
if (!detectLanguage(abs))
|
|
10790
|
+
continue;
|
|
10791
|
+
if (looksMinified(abs))
|
|
10792
|
+
continue;
|
|
10793
|
+
out.push(abs);
|
|
10640
10794
|
}
|
|
10641
10795
|
return out;
|
|
10642
10796
|
}
|
|
@@ -10694,11 +10848,11 @@ function loadIgnorePatterns(baseDir) {
|
|
|
10694
10848
|
const gitignores = findAllGitignores(baseDir);
|
|
10695
10849
|
const rootDir = gitignores.length > 0 && gitignores[0] ? gitignores[0].dir : baseDir;
|
|
10696
10850
|
for (const { path: gitignorePath } of gitignores) {
|
|
10697
|
-
ig.add(
|
|
10851
|
+
ig.add(readFileSync2(gitignorePath, "utf8"));
|
|
10698
10852
|
}
|
|
10699
10853
|
const truecourseignorePath = join(baseDir, ".truecourseignore");
|
|
10700
10854
|
if (existsSync2(truecourseignorePath)) {
|
|
10701
|
-
const content =
|
|
10855
|
+
const content = readFileSync2(truecourseignorePath, "utf8");
|
|
10702
10856
|
const prefix = relative(rootDir, baseDir).replace(/\\/g, "/");
|
|
10703
10857
|
ig.add(reanchorTruecourseignore(content, prefix));
|
|
10704
10858
|
}
|
|
@@ -10725,8 +10879,11 @@ function discoverFilesViaWalker(dir) {
|
|
|
10725
10879
|
if (stat.isDirectory()) {
|
|
10726
10880
|
traverse(fullPath);
|
|
10727
10881
|
} else if (stat.isFile()) {
|
|
10728
|
-
if (detectLanguage(fullPath))
|
|
10729
|
-
|
|
10882
|
+
if (!detectLanguage(fullPath))
|
|
10883
|
+
continue;
|
|
10884
|
+
if (looksMinified(fullPath))
|
|
10885
|
+
continue;
|
|
10886
|
+
files.push(fullPath);
|
|
10730
10887
|
}
|
|
10731
10888
|
}
|
|
10732
10889
|
} catch {
|
|
@@ -10741,12 +10898,16 @@ function discoverFiles(dir) {
|
|
|
10741
10898
|
return viaGit;
|
|
10742
10899
|
return discoverFilesViaWalker(dir);
|
|
10743
10900
|
}
|
|
10744
|
-
var import_ignore;
|
|
10901
|
+
var import_ignore, SNIFFED_EXTS, MIN_SIZE_FOR_SNIFF, SNIFF_BYTES, MIN_NEWLINES;
|
|
10745
10902
|
var init_file_discovery = __esm({
|
|
10746
10903
|
"packages/analyzer/dist/file-discovery.js"() {
|
|
10747
10904
|
"use strict";
|
|
10748
10905
|
import_ignore = __toESM(require_ignore(), 1);
|
|
10749
10906
|
init_language_config();
|
|
10907
|
+
SNIFFED_EXTS = /* @__PURE__ */ new Set([".js", ".jsx", ".cjs", ".mjs", ".ts", ".tsx", ".mts", ".cts"]);
|
|
10908
|
+
MIN_SIZE_FOR_SNIFF = 15 * 1024;
|
|
10909
|
+
SNIFF_BYTES = 8 * 1024;
|
|
10910
|
+
MIN_NEWLINES = 4;
|
|
10750
10911
|
}
|
|
10751
10912
|
});
|
|
10752
10913
|
|
|
@@ -12516,6 +12677,15 @@ var init_typescript = __esm({
|
|
|
12516
12677
|
|
|
12517
12678
|
// packages/analyzer/dist/extractors/languages/javascript.js
|
|
12518
12679
|
import { Query as Query3 } from "web-tree-sitter";
|
|
12680
|
+
function isNestedInFunction2(node) {
|
|
12681
|
+
let current = node.parent;
|
|
12682
|
+
while (current) {
|
|
12683
|
+
if (FUNCTION_NODE_TYPES2.has(current.type))
|
|
12684
|
+
return true;
|
|
12685
|
+
current = current.parent;
|
|
12686
|
+
}
|
|
12687
|
+
return false;
|
|
12688
|
+
}
|
|
12519
12689
|
function extractFunctionName2(node) {
|
|
12520
12690
|
const nameNode = node.childForFieldName("name");
|
|
12521
12691
|
if (nameNode?.text)
|
|
@@ -12523,6 +12693,8 @@ function extractFunctionName2(node) {
|
|
|
12523
12693
|
const parent = node.parent;
|
|
12524
12694
|
if (!parent)
|
|
12525
12695
|
return "anonymous";
|
|
12696
|
+
if (isNestedInFunction2(node))
|
|
12697
|
+
return "anonymous";
|
|
12526
12698
|
if (parent.type === "variable_declarator") {
|
|
12527
12699
|
const varName = parent.childForFieldName("name");
|
|
12528
12700
|
if (varName?.text)
|
|
@@ -12541,25 +12713,15 @@ function extractFunctionName2(node) {
|
|
|
12541
12713
|
return prop.text;
|
|
12542
12714
|
}
|
|
12543
12715
|
}
|
|
12544
|
-
if (parent.type === "arguments") {
|
|
12545
|
-
const callNode = parent.parent;
|
|
12546
|
-
if (callNode?.type === "call_expression") {
|
|
12547
|
-
const callee = callNode.childForFieldName("function");
|
|
12548
|
-
if (callee?.type === "member_expression") {
|
|
12549
|
-
const method = callee.childForFieldName("property");
|
|
12550
|
-
if (method?.text)
|
|
12551
|
-
return `${method.text}_handler`;
|
|
12552
|
-
}
|
|
12553
|
-
}
|
|
12554
|
-
}
|
|
12555
12716
|
return "anonymous";
|
|
12556
12717
|
}
|
|
12557
12718
|
function isExported2(node) {
|
|
12558
12719
|
let current = node.parent;
|
|
12559
12720
|
while (current) {
|
|
12560
|
-
if (current.type === "export_statement")
|
|
12721
|
+
if (current.type === "export_statement")
|
|
12561
12722
|
return true;
|
|
12562
|
-
|
|
12723
|
+
if (FUNCTION_NODE_TYPES2.has(current.type))
|
|
12724
|
+
return false;
|
|
12563
12725
|
current = current.parent;
|
|
12564
12726
|
}
|
|
12565
12727
|
return false;
|
|
@@ -12904,12 +13066,22 @@ function extractJavaScriptExports(tree, _filePath) {
|
|
|
12904
13066
|
}
|
|
12905
13067
|
return exports;
|
|
12906
13068
|
}
|
|
13069
|
+
var FUNCTION_NODE_TYPES2;
|
|
12907
13070
|
var init_javascript = __esm({
|
|
12908
13071
|
"packages/analyzer/dist/extractors/languages/javascript.js"() {
|
|
12909
13072
|
"use strict";
|
|
12910
13073
|
init_language_config();
|
|
12911
13074
|
init_parser();
|
|
12912
13075
|
init_common();
|
|
13076
|
+
FUNCTION_NODE_TYPES2 = /* @__PURE__ */ new Set([
|
|
13077
|
+
"function_declaration",
|
|
13078
|
+
"function",
|
|
13079
|
+
"function_expression",
|
|
13080
|
+
"arrow_function",
|
|
13081
|
+
"generator_function_declaration",
|
|
13082
|
+
"generator_function",
|
|
13083
|
+
"method_definition"
|
|
13084
|
+
]);
|
|
12913
13085
|
}
|
|
12914
13086
|
});
|
|
12915
13087
|
|
|
@@ -12942,7 +13114,7 @@ function computePythonFunctionMetrics(node) {
|
|
|
12942
13114
|
walkNesting(bodyNode, 0);
|
|
12943
13115
|
return { lineCount, statementCount, maxNestingDepth: maxNestingDepth2 };
|
|
12944
13116
|
}
|
|
12945
|
-
function
|
|
13117
|
+
function isNestedInFunction3(node) {
|
|
12946
13118
|
let current = node.parent;
|
|
12947
13119
|
while (current) {
|
|
12948
13120
|
if (current.type === "function_definition")
|
|
@@ -13009,7 +13181,7 @@ function extractPythonFunctions(tree, filePath) {
|
|
|
13009
13181
|
const name = node.childForFieldName("name")?.text;
|
|
13010
13182
|
if (!name)
|
|
13011
13183
|
return;
|
|
13012
|
-
if (
|
|
13184
|
+
if (isNestedInFunction3(node))
|
|
13013
13185
|
return;
|
|
13014
13186
|
const paramsNode = node.childForFieldName("parameters");
|
|
13015
13187
|
const params = extractPythonParameters(paramsNode);
|
|
@@ -13645,7 +13817,7 @@ var init_registry2 = __esm({
|
|
|
13645
13817
|
|
|
13646
13818
|
// packages/analyzer/dist/dependency-graph.js
|
|
13647
13819
|
import { resolve as resolve4, dirname as dirname4, join as join3 } from "path";
|
|
13648
|
-
import { existsSync as existsSync4, readFileSync as
|
|
13820
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync4, realpathSync, statSync as statSync3 } from "fs";
|
|
13649
13821
|
function resolveRelativeFallback(importSource, containingFile, analyzedFiles, extensions, indexFiles) {
|
|
13650
13822
|
const fromDir = dirname4(containingFile);
|
|
13651
13823
|
const basePath = resolve4(fromDir, importSource);
|
|
@@ -13679,7 +13851,7 @@ function buildWorkspacePackageMap(rootPath) {
|
|
|
13679
13851
|
if (!existsSync4(pkgJsonPath))
|
|
13680
13852
|
continue;
|
|
13681
13853
|
try {
|
|
13682
|
-
const pkg = JSON.parse(
|
|
13854
|
+
const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
|
|
13683
13855
|
if (pkg.name)
|
|
13684
13856
|
packageMap.set(pkg.name, pkgDir);
|
|
13685
13857
|
} catch {
|
|
@@ -13710,7 +13882,7 @@ function resolveWorkspaceFallback(importSource, workspacePackages, analyzedFiles
|
|
|
13710
13882
|
const pkgJsonPath = join3(pkgDir, "package.json");
|
|
13711
13883
|
if (existsSync4(pkgJsonPath)) {
|
|
13712
13884
|
try {
|
|
13713
|
-
const pkg = JSON.parse(
|
|
13885
|
+
const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
|
|
13714
13886
|
const mainField = pkg.main || pkg.module;
|
|
13715
13887
|
if (mainField) {
|
|
13716
13888
|
const mainPath = resolve4(pkgDir, mainField);
|
|
@@ -16247,7 +16419,7 @@ var init_layer_detector = __esm({
|
|
|
16247
16419
|
});
|
|
16248
16420
|
|
|
16249
16421
|
// packages/analyzer/dist/service-detectors/javascript.js
|
|
16250
|
-
import { existsSync as existsSync5, readFileSync as
|
|
16422
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
16251
16423
|
import { join as join4 } from "path";
|
|
16252
16424
|
var jsServiceDetector;
|
|
16253
16425
|
var init_javascript2 = __esm({
|
|
@@ -16259,7 +16431,7 @@ var init_javascript2 = __esm({
|
|
|
16259
16431
|
if (!existsSync5(packageJsonPath))
|
|
16260
16432
|
return [];
|
|
16261
16433
|
try {
|
|
16262
|
-
const pkg = JSON.parse(
|
|
16434
|
+
const pkg = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
|
|
16263
16435
|
return Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
|
|
16264
16436
|
} catch {
|
|
16265
16437
|
return [];
|
|
@@ -16270,7 +16442,7 @@ var init_javascript2 = __esm({
|
|
|
16270
16442
|
if (!existsSync5(packageJsonPath))
|
|
16271
16443
|
return false;
|
|
16272
16444
|
try {
|
|
16273
|
-
const pkg = JSON.parse(
|
|
16445
|
+
const pkg = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
|
|
16274
16446
|
const libraryIndicators = ["main", "module", "exports", "types", "typings"];
|
|
16275
16447
|
return libraryIndicators.some((indicator) => pkg[indicator]);
|
|
16276
16448
|
} catch {
|
|
@@ -16282,7 +16454,7 @@ var init_javascript2 = __esm({
|
|
|
16282
16454
|
});
|
|
16283
16455
|
|
|
16284
16456
|
// packages/analyzer/dist/service-detectors/python.js
|
|
16285
|
-
import { existsSync as existsSync6, readFileSync as
|
|
16457
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
16286
16458
|
import { join as join5 } from "path";
|
|
16287
16459
|
var pythonServiceDetector;
|
|
16288
16460
|
var init_python4 = __esm({
|
|
@@ -16294,7 +16466,7 @@ var init_python4 = __esm({
|
|
|
16294
16466
|
const reqPath = join5(servicePath, "requirements.txt");
|
|
16295
16467
|
if (existsSync6(reqPath)) {
|
|
16296
16468
|
try {
|
|
16297
|
-
const content =
|
|
16469
|
+
const content = readFileSync5(reqPath, "utf-8");
|
|
16298
16470
|
for (const line of content.split("\n")) {
|
|
16299
16471
|
const trimmed2 = line.trim();
|
|
16300
16472
|
if (!trimmed2 || trimmed2.startsWith("#") || trimmed2.startsWith("-"))
|
|
@@ -16309,7 +16481,7 @@ var init_python4 = __esm({
|
|
|
16309
16481
|
const pyprojectPath = join5(servicePath, "pyproject.toml");
|
|
16310
16482
|
if (existsSync6(pyprojectPath)) {
|
|
16311
16483
|
try {
|
|
16312
|
-
const content =
|
|
16484
|
+
const content = readFileSync5(pyprojectPath, "utf-8");
|
|
16313
16485
|
const depsMatch = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/m);
|
|
16314
16486
|
if (depsMatch) {
|
|
16315
16487
|
const depMatches = depsMatch[1].matchAll(/"([^"]+)"|'([^']+)'/g);
|
|
@@ -16361,7 +16533,7 @@ var init_registry3 = __esm({
|
|
|
16361
16533
|
});
|
|
16362
16534
|
|
|
16363
16535
|
// packages/analyzer/dist/service-detector.js
|
|
16364
|
-
import { existsSync as existsSync7, readFileSync as
|
|
16536
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
16365
16537
|
import { join as join6, basename, dirname as dirname5 } from "path";
|
|
16366
16538
|
function detectServices(rootPath, allFiles) {
|
|
16367
16539
|
const monorepoServices = detectMonorepoServices(rootPath, allFiles);
|
|
@@ -16495,7 +16667,7 @@ function detectDockerComposeServices(rootPath, allFiles) {
|
|
|
16495
16667
|
return [];
|
|
16496
16668
|
}
|
|
16497
16669
|
try {
|
|
16498
|
-
const content =
|
|
16670
|
+
const content = readFileSync6(composePath2, "utf-8");
|
|
16499
16671
|
const services = [];
|
|
16500
16672
|
const lines = content.split("\n");
|
|
16501
16673
|
let inServices = false;
|
|
@@ -16533,7 +16705,7 @@ function detectServiceType(servicePath, files) {
|
|
|
16533
16705
|
const hasPackageJson = existsSync7(join6(servicePath, "package.json"));
|
|
16534
16706
|
if (hasPackageJson) {
|
|
16535
16707
|
try {
|
|
16536
|
-
const pkg = JSON.parse(
|
|
16708
|
+
const pkg = JSON.parse(readFileSync6(join6(servicePath, "package.json"), "utf-8"));
|
|
16537
16709
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
16538
16710
|
const isFrontend = patterns.frontend.frameworks.some((fw) => deps[fw]);
|
|
16539
16711
|
if (isFrontend) {
|
|
@@ -16562,7 +16734,7 @@ function detectFramework(servicePath) {
|
|
|
16562
16734
|
const packageJsonPath = join6(servicePath, "package.json");
|
|
16563
16735
|
if (existsSync7(packageJsonPath)) {
|
|
16564
16736
|
try {
|
|
16565
|
-
const pkg = JSON.parse(
|
|
16737
|
+
const pkg = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
|
|
16566
16738
|
const deps2 = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
16567
16739
|
for (const fw of patterns.metaFrameworks) {
|
|
16568
16740
|
if (deps2[fw]) {
|
|
@@ -16600,7 +16772,7 @@ function getServiceName(servicePath) {
|
|
|
16600
16772
|
const packageJsonPath = join6(servicePath, "package.json");
|
|
16601
16773
|
if (existsSync7(packageJsonPath)) {
|
|
16602
16774
|
try {
|
|
16603
|
-
const pkg = JSON.parse(
|
|
16775
|
+
const pkg = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
|
|
16604
16776
|
if (pkg.name) {
|
|
16605
16777
|
return pkg.name;
|
|
16606
16778
|
}
|
|
@@ -17162,7 +17334,7 @@ var init_registry4 = __esm({
|
|
|
17162
17334
|
});
|
|
17163
17335
|
|
|
17164
17336
|
// packages/analyzer/dist/database-detector.js
|
|
17165
|
-
import { existsSync as existsSync8, readFileSync as
|
|
17337
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync6 } from "fs";
|
|
17166
17338
|
import { join as join7, resolve as resolve5 } from "path";
|
|
17167
17339
|
function detectDatabases(rootPath, analyses, services) {
|
|
17168
17340
|
const detections = [];
|
|
@@ -17199,7 +17371,7 @@ function detectDatabases(rootPath, analyses, services) {
|
|
|
17199
17371
|
const schemaResults = /* @__PURE__ */ new Map();
|
|
17200
17372
|
const prismaFiles = findFiles(rootPath, "schema.prisma", ["node_modules", ".git", "dist"]);
|
|
17201
17373
|
for (const prismaFile of prismaFiles) {
|
|
17202
|
-
const content =
|
|
17374
|
+
const content = readFileSync7(prismaFile, "utf-8");
|
|
17203
17375
|
const result = parsePrismaSchema(content);
|
|
17204
17376
|
let dbType = "postgres";
|
|
17205
17377
|
const providerMatch = content.match(/provider\s*=\s*"(\w+)"/);
|
|
@@ -17237,7 +17409,7 @@ function detectDatabases(rootPath, analyses, services) {
|
|
|
17237
17409
|
if (!parser4.matchesImport(analysis))
|
|
17238
17410
|
continue;
|
|
17239
17411
|
try {
|
|
17240
|
-
const content =
|
|
17412
|
+
const content = readFileSync7(resolve5(analysis.filePath), "utf-8");
|
|
17241
17413
|
if (parser4.validateContent && !parser4.validateContent(content))
|
|
17242
17414
|
continue;
|
|
17243
17415
|
const result = parser4.parse(content);
|
|
@@ -17287,7 +17459,7 @@ function parseDockerCompose(rootPath) {
|
|
|
17287
17459
|
for (const file of composeFiles) {
|
|
17288
17460
|
const filePath = join7(rootPath, file);
|
|
17289
17461
|
if (existsSync8(filePath)) {
|
|
17290
|
-
composeContent =
|
|
17462
|
+
composeContent = readFileSync7(filePath, "utf-8");
|
|
17291
17463
|
break;
|
|
17292
17464
|
}
|
|
17293
17465
|
}
|
|
@@ -18548,7 +18720,7 @@ var init_entities = __esm({
|
|
|
18548
18720
|
|
|
18549
18721
|
// packages/analyzer/dist/lsp-client.js
|
|
18550
18722
|
import { spawn as spawn2 } from "child_process";
|
|
18551
|
-
import { readFileSync as
|
|
18723
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
18552
18724
|
import { resolve as resolve6 } from "path";
|
|
18553
18725
|
import { pathToFileURL, fileURLToPath as fileURLToPath3 } from "url";
|
|
18554
18726
|
function encodeMessage(msg) {
|
|
@@ -18663,7 +18835,7 @@ var init_lsp_client = __esm({
|
|
|
18663
18835
|
// -------------------------------------------------------------------------
|
|
18664
18836
|
openFile(filePath) {
|
|
18665
18837
|
const absPath = resolve6(this.rootPath, filePath);
|
|
18666
|
-
const content =
|
|
18838
|
+
const content = readFileSync8(absPath, "utf-8");
|
|
18667
18839
|
this.sendNotification("textDocument/didOpen", {
|
|
18668
18840
|
textDocument: {
|
|
18669
18841
|
uri: pathToFileURL(absPath).href,
|
|
@@ -18775,7 +18947,7 @@ var init_lsp_client = __esm({
|
|
|
18775
18947
|
const moduleMap = /* @__PURE__ */ new Map();
|
|
18776
18948
|
for (const fa of fileAnalyses) {
|
|
18777
18949
|
const absPath = resolve6(this.rootPath, fa.filePath);
|
|
18778
|
-
const fileContent =
|
|
18950
|
+
const fileContent = readFileSync8(absPath, "utf-8");
|
|
18779
18951
|
const lines = fileContent.split("\n");
|
|
18780
18952
|
const fileResolutions = /* @__PURE__ */ new Map();
|
|
18781
18953
|
for (const imp of fa.imports) {
|
|
@@ -32874,6 +33046,19 @@ function isPropertyAccess(node) {
|
|
|
32874
33046
|
}
|
|
32875
33047
|
if (parent.type === "keyword_argument" && parent.childForFieldName("name")?.id === node.id)
|
|
32876
33048
|
return true;
|
|
33049
|
+
if (parent.type === "jsx_attribute" && parent.namedChild(0)?.id === node.id)
|
|
33050
|
+
return true;
|
|
33051
|
+
if (parent.type === "jsx_opening_element" || parent.type === "jsx_closing_element" || parent.type === "jsx_self_closing_element") {
|
|
33052
|
+
const tagName = parent.childForFieldName("name");
|
|
33053
|
+
if (tagName?.id === node.id) {
|
|
33054
|
+
const text = node.text;
|
|
33055
|
+
if (text.length > 0) {
|
|
33056
|
+
const first2 = text[0];
|
|
33057
|
+
if (first2 === first2.toLowerCase() || text.includes("-"))
|
|
33058
|
+
return true;
|
|
33059
|
+
}
|
|
33060
|
+
}
|
|
33061
|
+
}
|
|
32877
33062
|
return false;
|
|
32878
33063
|
}
|
|
32879
33064
|
function isJsDeclarationPosition(node) {
|
|
@@ -32907,6 +33092,10 @@ function isJsDeclarationPosition(node) {
|
|
|
32907
33092
|
}
|
|
32908
33093
|
if (parent.type === "catch_clause")
|
|
32909
33094
|
return true;
|
|
33095
|
+
if (parent.type === "for_in_statement" && parent.childForFieldName("left")?.id === node.id)
|
|
33096
|
+
return true;
|
|
33097
|
+
if (parent.type === "arrow_function" && parent.childForFieldName("parameter")?.id === node.id)
|
|
33098
|
+
return true;
|
|
32910
33099
|
return false;
|
|
32911
33100
|
}
|
|
32912
33101
|
function isPyDeclarationPosition(node) {
|
|
@@ -33046,9 +33235,9 @@ function buildScopeTree(rootNode, language) {
|
|
|
33046
33235
|
return;
|
|
33047
33236
|
}
|
|
33048
33237
|
if ((parent.type === "function_declaration" || parent.type === "generator_function_declaration") && parent.childForFieldName("name")?.id === node.id) {
|
|
33049
|
-
const
|
|
33050
|
-
if (!
|
|
33051
|
-
createVariable(node.text, "function", node,
|
|
33238
|
+
const enclosing = scope.parent ? findFunctionScope(scope.parent) : scope;
|
|
33239
|
+
if (!enclosing.variables.has(node.text)) {
|
|
33240
|
+
createVariable(node.text, "function", node, enclosing, false);
|
|
33052
33241
|
}
|
|
33053
33242
|
return;
|
|
33054
33243
|
}
|
|
@@ -33068,6 +33257,13 @@ function buildScopeTree(rootNode, language) {
|
|
|
33068
33257
|
}
|
|
33069
33258
|
return;
|
|
33070
33259
|
}
|
|
33260
|
+
if (parent.type === "arrow_function" && parent.childForFieldName("parameter")?.id === node.id) {
|
|
33261
|
+
const funcScope = nodeToScope.get(parent.id);
|
|
33262
|
+
if (funcScope && !funcScope.variables.has(node.text)) {
|
|
33263
|
+
createVariable(node.text, "parameter", node, funcScope, false);
|
|
33264
|
+
}
|
|
33265
|
+
return;
|
|
33266
|
+
}
|
|
33071
33267
|
if (parent.type === "rest_pattern" || parent.type === "rest_element") {
|
|
33072
33268
|
const funcNode = findFunctionAncestor(node);
|
|
33073
33269
|
if (funcNode) {
|
|
@@ -33090,6 +33286,68 @@ function buildScopeTree(rootNode, language) {
|
|
|
33090
33286
|
return;
|
|
33091
33287
|
}
|
|
33092
33288
|
}
|
|
33289
|
+
if (parent.type === "array_pattern" || parent.type === "object_pattern") {
|
|
33290
|
+
if (isInFormalParameters(node)) {
|
|
33291
|
+
const funcNode = findFunctionAncestor(node);
|
|
33292
|
+
if (funcNode) {
|
|
33293
|
+
const funcScope = nodeToScope.get(funcNode.id);
|
|
33294
|
+
if (funcScope && !funcScope.variables.has(node.text)) {
|
|
33295
|
+
createVariable(node.text, "parameter", node, funcScope, false);
|
|
33296
|
+
}
|
|
33297
|
+
}
|
|
33298
|
+
return;
|
|
33299
|
+
}
|
|
33300
|
+
let ancestor = parent.parent;
|
|
33301
|
+
let declarator = null;
|
|
33302
|
+
let forIn = null;
|
|
33303
|
+
while (ancestor) {
|
|
33304
|
+
if (ancestor.type === "variable_declarator") {
|
|
33305
|
+
declarator = ancestor;
|
|
33306
|
+
break;
|
|
33307
|
+
}
|
|
33308
|
+
if (ancestor.type === "for_in_statement" && ancestor.childForFieldName("left")?.id === parent.id) {
|
|
33309
|
+
forIn = ancestor;
|
|
33310
|
+
break;
|
|
33311
|
+
}
|
|
33312
|
+
if (ancestor.type === "function_declaration" || ancestor.type === "function_expression" || ancestor.type === "arrow_function" || ancestor.type === "method_definition" || ancestor.type === "program")
|
|
33313
|
+
break;
|
|
33314
|
+
ancestor = ancestor.parent;
|
|
33315
|
+
}
|
|
33316
|
+
if (declarator) {
|
|
33317
|
+
const grandparent = declarator.parent;
|
|
33318
|
+
if (grandparent?.type === "variable_declaration") {
|
|
33319
|
+
const targetScope = findFunctionScope(scope);
|
|
33320
|
+
if (!targetScope.variables.has(node.text)) {
|
|
33321
|
+
createVariable(node.text, "var", node, targetScope, false);
|
|
33322
|
+
}
|
|
33323
|
+
} else if (grandparent?.type === "lexical_declaration") {
|
|
33324
|
+
const keyword = grandparent.child(0)?.text;
|
|
33325
|
+
const kind = keyword === "const" ? "const" : "let";
|
|
33326
|
+
if (!scope.variables.has(node.text)) {
|
|
33327
|
+
createVariable(node.text, kind, node, scope, false);
|
|
33328
|
+
}
|
|
33329
|
+
}
|
|
33330
|
+
return;
|
|
33331
|
+
}
|
|
33332
|
+
if (forIn) {
|
|
33333
|
+
const kindText = forIn.childForFieldName("kind")?.text;
|
|
33334
|
+
const kind = kindText === "var" ? "var" : kindText === "const" ? "const" : kindText === "let" ? "let" : "for-variable";
|
|
33335
|
+
const targetScope = kind === "var" ? findFunctionScope(scope) : scope;
|
|
33336
|
+
if (!targetScope.variables.has(node.text)) {
|
|
33337
|
+
createVariable(node.text, kind, node, targetScope, false);
|
|
33338
|
+
}
|
|
33339
|
+
return;
|
|
33340
|
+
}
|
|
33341
|
+
}
|
|
33342
|
+
if (parent.type === "for_in_statement" && parent.childForFieldName("left")?.id === node.id) {
|
|
33343
|
+
const kindText = parent.childForFieldName("kind")?.text;
|
|
33344
|
+
const kind = kindText === "var" ? "var" : kindText === "const" ? "const" : kindText === "let" ? "let" : "for-variable";
|
|
33345
|
+
const targetScope = kind === "var" ? findFunctionScope(scope) : scope;
|
|
33346
|
+
if (!targetScope.variables.has(node.text)) {
|
|
33347
|
+
createVariable(node.text, kind, node, targetScope, false);
|
|
33348
|
+
}
|
|
33349
|
+
return;
|
|
33350
|
+
}
|
|
33093
33351
|
if (parent.type === "shorthand_property_identifier_pattern") {
|
|
33094
33352
|
if (isInFormalParameters(node)) {
|
|
33095
33353
|
const funcNode = findFunctionAncestor(node);
|
|
@@ -33430,16 +33688,27 @@ function buildScopeTree(rootNode, language) {
|
|
|
33430
33688
|
if (isJs && node.type === "shorthand_property_identifier_pattern") {
|
|
33431
33689
|
let ancestor = node.parent;
|
|
33432
33690
|
let declarator = null;
|
|
33691
|
+
let isParam = false;
|
|
33692
|
+
let funcNode = null;
|
|
33433
33693
|
while (ancestor) {
|
|
33434
33694
|
if (ancestor.type === "variable_declarator") {
|
|
33435
33695
|
declarator = ancestor;
|
|
33436
33696
|
break;
|
|
33437
33697
|
}
|
|
33438
|
-
if (ancestor.type === "formal_parameters" || ancestor.type === "function_declaration" || ancestor.type === "arrow_function" || ancestor.type === "method_definition") {
|
|
33698
|
+
if (ancestor.type === "formal_parameters" || ancestor.type === "function_declaration" || ancestor.type === "function_expression" || ancestor.type === "arrow_function" || ancestor.type === "method_definition" || ancestor.type === "generator_function_declaration" || ancestor.type === "generator_function") {
|
|
33699
|
+
isParam = true;
|
|
33700
|
+
funcNode = ancestor.type === "formal_parameters" ? ancestor.parent : ancestor;
|
|
33439
33701
|
break;
|
|
33440
33702
|
}
|
|
33441
33703
|
ancestor = ancestor.parent;
|
|
33442
33704
|
}
|
|
33705
|
+
if (isParam && funcNode) {
|
|
33706
|
+
const funcScope = nodeToScope.get(funcNode.id);
|
|
33707
|
+
const name = node.text;
|
|
33708
|
+
if (funcScope && !funcScope.variables.has(name)) {
|
|
33709
|
+
createVariable(name, "parameter", node, funcScope, false);
|
|
33710
|
+
}
|
|
33711
|
+
}
|
|
33443
33712
|
if (declarator) {
|
|
33444
33713
|
const grandparent = declarator.parent;
|
|
33445
33714
|
const name = node.text;
|
|
@@ -33509,7 +33778,7 @@ var init_known_globals = __esm({
|
|
|
33509
33778
|
"packages/analyzer/dist/data-flow/known-globals.js"() {
|
|
33510
33779
|
"use strict";
|
|
33511
33780
|
JS_GLOBALS = /* @__PURE__ */ new Set([
|
|
33512
|
-
// Browser
|
|
33781
|
+
// Browser — global namespaces
|
|
33513
33782
|
"window",
|
|
33514
33783
|
"document",
|
|
33515
33784
|
"navigator",
|
|
@@ -33517,19 +33786,109 @@ var init_known_globals = __esm({
|
|
|
33517
33786
|
"history",
|
|
33518
33787
|
"localStorage",
|
|
33519
33788
|
"sessionStorage",
|
|
33789
|
+
"screen",
|
|
33790
|
+
"performance",
|
|
33791
|
+
"crypto",
|
|
33792
|
+
"caches",
|
|
33793
|
+
"indexedDB",
|
|
33794
|
+
"matchMedia",
|
|
33795
|
+
// Browser — networking / fetch
|
|
33520
33796
|
"fetch",
|
|
33521
33797
|
"XMLHttpRequest",
|
|
33522
33798
|
"WebSocket",
|
|
33799
|
+
"EventSource",
|
|
33523
33800
|
"URL",
|
|
33524
33801
|
"URLSearchParams",
|
|
33802
|
+
"Headers",
|
|
33803
|
+
"Request",
|
|
33804
|
+
"Response",
|
|
33805
|
+
"FormData",
|
|
33806
|
+
"Blob",
|
|
33807
|
+
"File",
|
|
33808
|
+
"FileReader",
|
|
33809
|
+
"FileList",
|
|
33810
|
+
"AbortController",
|
|
33811
|
+
"AbortSignal",
|
|
33812
|
+
"ReadableStream",
|
|
33813
|
+
"WritableStream",
|
|
33814
|
+
"TransformStream",
|
|
33815
|
+
// Browser — events
|
|
33816
|
+
"Event",
|
|
33817
|
+
"CustomEvent",
|
|
33818
|
+
"EventTarget",
|
|
33819
|
+
"MessageEvent",
|
|
33820
|
+
"ErrorEvent",
|
|
33821
|
+
"CloseEvent",
|
|
33822
|
+
"ProgressEvent",
|
|
33823
|
+
"PointerEvent",
|
|
33824
|
+
"KeyboardEvent",
|
|
33825
|
+
"MouseEvent",
|
|
33826
|
+
"TouchEvent",
|
|
33827
|
+
"DragEvent",
|
|
33828
|
+
"WheelEvent",
|
|
33829
|
+
"FocusEvent",
|
|
33830
|
+
"InputEvent",
|
|
33831
|
+
"StorageEvent",
|
|
33832
|
+
"PopStateEvent",
|
|
33833
|
+
"HashChangeEvent",
|
|
33834
|
+
"BeforeUnloadEvent",
|
|
33835
|
+
// Browser — DOM
|
|
33836
|
+
"Node",
|
|
33837
|
+
"Element",
|
|
33838
|
+
"HTMLElement",
|
|
33839
|
+
"HTMLInputElement",
|
|
33840
|
+
"HTMLButtonElement",
|
|
33841
|
+
"HTMLFormElement",
|
|
33842
|
+
"HTMLSelectElement",
|
|
33843
|
+
"HTMLTextAreaElement",
|
|
33844
|
+
"HTMLAnchorElement",
|
|
33845
|
+
"HTMLImageElement",
|
|
33846
|
+
"HTMLCanvasElement",
|
|
33847
|
+
"HTMLVideoElement",
|
|
33848
|
+
"HTMLAudioElement",
|
|
33849
|
+
"HTMLDivElement",
|
|
33850
|
+
"HTMLSpanElement",
|
|
33851
|
+
"HTMLScriptElement",
|
|
33852
|
+
"HTMLStyleElement",
|
|
33853
|
+
"HTMLTemplateElement",
|
|
33854
|
+
"HTMLIFrameElement",
|
|
33855
|
+
"HTMLDialogElement",
|
|
33856
|
+
"DocumentFragment",
|
|
33857
|
+
"ShadowRoot",
|
|
33858
|
+
"Text",
|
|
33859
|
+
"Comment",
|
|
33860
|
+
"Range",
|
|
33861
|
+
"Selection",
|
|
33862
|
+
"MutationObserver",
|
|
33863
|
+
"IntersectionObserver",
|
|
33864
|
+
"ResizeObserver",
|
|
33865
|
+
"PerformanceObserver",
|
|
33866
|
+
"NodeList",
|
|
33867
|
+
"HTMLCollection",
|
|
33868
|
+
"DOMTokenList",
|
|
33869
|
+
"CanvasRenderingContext2D",
|
|
33870
|
+
"OffscreenCanvas",
|
|
33871
|
+
// Browser — timers / scheduling
|
|
33525
33872
|
"setTimeout",
|
|
33526
33873
|
"setInterval",
|
|
33527
33874
|
"clearTimeout",
|
|
33528
33875
|
"clearInterval",
|
|
33529
33876
|
"requestAnimationFrame",
|
|
33877
|
+
"cancelAnimationFrame",
|
|
33878
|
+
"requestIdleCallback",
|
|
33879
|
+
"cancelIdleCallback",
|
|
33880
|
+
// Browser — dialogs
|
|
33530
33881
|
"alert",
|
|
33531
33882
|
"confirm",
|
|
33532
33883
|
"prompt",
|
|
33884
|
+
// Browser — workers / messaging
|
|
33885
|
+
"Worker",
|
|
33886
|
+
"SharedWorker",
|
|
33887
|
+
"ServiceWorker",
|
|
33888
|
+
"BroadcastChannel",
|
|
33889
|
+
"MessageChannel",
|
|
33890
|
+
"MessagePort",
|
|
33891
|
+
"postMessage",
|
|
33533
33892
|
// Node.js
|
|
33534
33893
|
"process",
|
|
33535
33894
|
"global",
|
|
@@ -33903,8 +34262,13 @@ function buildDataFlowContext(rootNode, language) {
|
|
|
33903
34262
|
continue;
|
|
33904
34263
|
if (v.useSites.length === 0)
|
|
33905
34264
|
continue;
|
|
34265
|
+
if (v.declarationNode.parent?.type === "named_expression")
|
|
34266
|
+
continue;
|
|
34267
|
+
const directUseSites = v.useSites.filter((u2) => !isInNestedFunctionScope(u2.scope, v.scope));
|
|
34268
|
+
if (directUseSites.length === 0)
|
|
34269
|
+
continue;
|
|
33906
34270
|
const declPos = v.declarationNode.startIndex;
|
|
33907
|
-
const earliestUse = Math.min(...
|
|
34271
|
+
const earliestUse = Math.min(...directUseSites.map((u2) => u2.node.startIndex));
|
|
33908
34272
|
if (earliestUse < declPos) {
|
|
33909
34273
|
result.push(v);
|
|
33910
34274
|
}
|
|
@@ -33912,6 +34276,17 @@ function buildDataFlowContext(rootNode, language) {
|
|
|
33912
34276
|
cachedUsedBeforeDefined = result;
|
|
33913
34277
|
return result;
|
|
33914
34278
|
}
|
|
34279
|
+
function isInNestedFunctionScope(useScope, declScope) {
|
|
34280
|
+
if (useScope === declScope)
|
|
34281
|
+
return false;
|
|
34282
|
+
let cursor = useScope;
|
|
34283
|
+
while (cursor && cursor !== declScope) {
|
|
34284
|
+
if (cursor.kind === "function")
|
|
34285
|
+
return true;
|
|
34286
|
+
cursor = cursor.parent;
|
|
34287
|
+
}
|
|
34288
|
+
return false;
|
|
34289
|
+
}
|
|
33915
34290
|
function unusedVariables() {
|
|
33916
34291
|
if (cachedUnused)
|
|
33917
34292
|
return cachedUnused;
|
|
@@ -34495,7 +34870,72 @@ var init_insecure_cookie = __esm({
|
|
|
34495
34870
|
});
|
|
34496
34871
|
|
|
34497
34872
|
// packages/analyzer/dist/rules/security/visitors/javascript/disabled-auto-escaping.js
|
|
34498
|
-
|
|
34873
|
+
function isStaticStringLiteral(node) {
|
|
34874
|
+
if (node.type === "string")
|
|
34875
|
+
return true;
|
|
34876
|
+
if (node.type === "template_string") {
|
|
34877
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
34878
|
+
const child = node.namedChild(i);
|
|
34879
|
+
if (child?.type === "template_substitution")
|
|
34880
|
+
return false;
|
|
34881
|
+
}
|
|
34882
|
+
return true;
|
|
34883
|
+
}
|
|
34884
|
+
return false;
|
|
34885
|
+
}
|
|
34886
|
+
function isEscapeCall(node) {
|
|
34887
|
+
if (node.type !== "call_expression")
|
|
34888
|
+
return false;
|
|
34889
|
+
const fn = node.childForFieldName("function");
|
|
34890
|
+
if (!fn)
|
|
34891
|
+
return false;
|
|
34892
|
+
if (fn.type === "identifier")
|
|
34893
|
+
return ESCAPE_HELPER_NAMES.has(fn.text);
|
|
34894
|
+
if (fn.type === "member_expression") {
|
|
34895
|
+
const prop = fn.childForFieldName("property");
|
|
34896
|
+
if (prop && ESCAPE_HELPER_NAMES.has(prop.text))
|
|
34897
|
+
return true;
|
|
34898
|
+
const obj = fn.childForFieldName("object");
|
|
34899
|
+
if (obj?.type === "identifier" && /purify/i.test(obj.text))
|
|
34900
|
+
return true;
|
|
34901
|
+
}
|
|
34902
|
+
return false;
|
|
34903
|
+
}
|
|
34904
|
+
function isFullyEscapedRhs(node) {
|
|
34905
|
+
if (node.type === "string")
|
|
34906
|
+
return true;
|
|
34907
|
+
if (node.type === "number")
|
|
34908
|
+
return true;
|
|
34909
|
+
if (node.type === "true" || node.type === "false" || node.type === "null" || node.type === "undefined")
|
|
34910
|
+
return true;
|
|
34911
|
+
if (isEscapeCall(node))
|
|
34912
|
+
return true;
|
|
34913
|
+
if (node.type === "template_string") {
|
|
34914
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
34915
|
+
const child = node.namedChild(i);
|
|
34916
|
+
if (!child)
|
|
34917
|
+
continue;
|
|
34918
|
+
if (child.type === "template_substitution") {
|
|
34919
|
+
const inner = child.namedChild(0);
|
|
34920
|
+
if (!inner || !isFullyEscapedRhs(inner))
|
|
34921
|
+
return false;
|
|
34922
|
+
}
|
|
34923
|
+
}
|
|
34924
|
+
return true;
|
|
34925
|
+
}
|
|
34926
|
+
if (node.type === "binary_expression") {
|
|
34927
|
+
const op = node.children.find((c2) => c2.text === "+");
|
|
34928
|
+
if (!op)
|
|
34929
|
+
return false;
|
|
34930
|
+
const lhs = node.childForFieldName("left");
|
|
34931
|
+
const rhs = node.childForFieldName("right");
|
|
34932
|
+
if (!lhs || !rhs)
|
|
34933
|
+
return false;
|
|
34934
|
+
return isFullyEscapedRhs(lhs) && isFullyEscapedRhs(rhs);
|
|
34935
|
+
}
|
|
34936
|
+
return false;
|
|
34937
|
+
}
|
|
34938
|
+
var disabledAutoEscapingVisitor, ESCAPE_HELPER_NAMES;
|
|
34499
34939
|
var init_disabled_auto_escaping = __esm({
|
|
34500
34940
|
"packages/analyzer/dist/rules/security/visitors/javascript/disabled-auto-escaping.js"() {
|
|
34501
34941
|
"use strict";
|
|
@@ -34516,6 +34956,11 @@ var init_disabled_auto_escaping = __esm({
|
|
|
34516
34956
|
if (left?.type === "member_expression") {
|
|
34517
34957
|
const prop = left.childForFieldName("property");
|
|
34518
34958
|
if (prop?.text === "innerHTML") {
|
|
34959
|
+
const right = node.childForFieldName("right");
|
|
34960
|
+
if (right && isStaticStringLiteral(right))
|
|
34961
|
+
return null;
|
|
34962
|
+
if (right && isFullyEscapedRhs(right))
|
|
34963
|
+
return null;
|
|
34519
34964
|
return makeViolation(this.ruleKey, node, filePath, "high", "Disabled auto-escaping", "Direct innerHTML assignment can lead to XSS vulnerabilities.", sourceCode, "Use textContent instead of innerHTML, or sanitize input with DOMPurify.");
|
|
34520
34965
|
}
|
|
34521
34966
|
}
|
|
@@ -34523,6 +34968,19 @@ var init_disabled_auto_escaping = __esm({
|
|
|
34523
34968
|
return null;
|
|
34524
34969
|
}
|
|
34525
34970
|
};
|
|
34971
|
+
ESCAPE_HELPER_NAMES = /* @__PURE__ */ new Set([
|
|
34972
|
+
"esc",
|
|
34973
|
+
"escape",
|
|
34974
|
+
"escapeHtml",
|
|
34975
|
+
"escapeHTML",
|
|
34976
|
+
"escapeHtmlEntities",
|
|
34977
|
+
"htmlEscape",
|
|
34978
|
+
"htmlEntities",
|
|
34979
|
+
"sanitize",
|
|
34980
|
+
"sanitizeHtml",
|
|
34981
|
+
"sanitizeHTML",
|
|
34982
|
+
"purify"
|
|
34983
|
+
]);
|
|
34526
34984
|
}
|
|
34527
34985
|
});
|
|
34528
34986
|
|
|
@@ -38137,6 +38595,18 @@ var init_javascript3 = __esm({
|
|
|
38137
38595
|
});
|
|
38138
38596
|
|
|
38139
38597
|
// packages/analyzer/dist/rules/security/visitors/python/sql-injection.js
|
|
38598
|
+
function hasStringOperand(binNode) {
|
|
38599
|
+
for (let i = 0; i < binNode.namedChildCount; i++) {
|
|
38600
|
+
const child = binNode.namedChild(i);
|
|
38601
|
+
if (!child)
|
|
38602
|
+
continue;
|
|
38603
|
+
if (child.type === "string")
|
|
38604
|
+
return true;
|
|
38605
|
+
if (child.type === "binary_operator" && hasStringOperand(child))
|
|
38606
|
+
return true;
|
|
38607
|
+
}
|
|
38608
|
+
return false;
|
|
38609
|
+
}
|
|
38140
38610
|
var PYTHON_QUERY_METHODS, pythonSqlInjectionVisitor;
|
|
38141
38611
|
var init_sql_injection2 = __esm({
|
|
38142
38612
|
"packages/analyzer/dist/rules/security/visitors/python/sql-injection.js"() {
|
|
@@ -38190,7 +38660,9 @@ var init_sql_injection2 = __esm({
|
|
|
38190
38660
|
if (firstArg.type === "binary_operator") {
|
|
38191
38661
|
const op = firstArg.children.find((c2) => c2.text === "+");
|
|
38192
38662
|
if (op) {
|
|
38193
|
-
|
|
38663
|
+
if (hasStringOperand(firstArg)) {
|
|
38664
|
+
return makeViolation(this.ruleKey, node, filePath, "high", "Potential SQL injection", `String concatenation passed to ${methodName}(). Use parameterized queries instead.`, sourceCode, "Use parameterized queries (e.g., %s or :param) instead of string concatenation in SQL.");
|
|
38665
|
+
}
|
|
38194
38666
|
}
|
|
38195
38667
|
}
|
|
38196
38668
|
return null;
|
|
@@ -40142,6 +40614,40 @@ var init_paramiko_call = __esm({
|
|
|
40142
40614
|
});
|
|
40143
40615
|
|
|
40144
40616
|
// packages/analyzer/dist/rules/security/visitors/python/suspicious-url-open.js
|
|
40617
|
+
function isConfigSourcedVar(node, varName) {
|
|
40618
|
+
let func = node.parent;
|
|
40619
|
+
while (func) {
|
|
40620
|
+
if (func.type === "function_definition")
|
|
40621
|
+
break;
|
|
40622
|
+
func = func.parent;
|
|
40623
|
+
}
|
|
40624
|
+
let scope = func ? func.childForFieldName("body") : node.tree.rootNode;
|
|
40625
|
+
if (!scope)
|
|
40626
|
+
return false;
|
|
40627
|
+
let found = false;
|
|
40628
|
+
function walk(n) {
|
|
40629
|
+
if (found)
|
|
40630
|
+
return;
|
|
40631
|
+
if (n.type === "assignment") {
|
|
40632
|
+
const lhs = n.childForFieldName("left");
|
|
40633
|
+
const rhs = n.childForFieldName("right");
|
|
40634
|
+
if (lhs?.type === "identifier" && lhs.text === varName && rhs) {
|
|
40635
|
+
const text = rhs.text;
|
|
40636
|
+
if (/\bos\.getenv\b|\bos\.environ\b|\bconfig\.|\bsettings\.|\bSettings\(\)|\bConfig\(\)/.test(text)) {
|
|
40637
|
+
found = true;
|
|
40638
|
+
return;
|
|
40639
|
+
}
|
|
40640
|
+
}
|
|
40641
|
+
}
|
|
40642
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
40643
|
+
const ch = n.child(i);
|
|
40644
|
+
if (ch)
|
|
40645
|
+
walk(ch);
|
|
40646
|
+
}
|
|
40647
|
+
}
|
|
40648
|
+
walk(scope);
|
|
40649
|
+
return found;
|
|
40650
|
+
}
|
|
40145
40651
|
var pythonSuspiciousUrlOpenVisitor;
|
|
40146
40652
|
var init_suspicious_url_open = __esm({
|
|
40147
40653
|
"packages/analyzer/dist/rules/security/visitors/python/suspicious-url-open.js"() {
|
|
@@ -40175,10 +40681,11 @@ var init_suspicious_url_open = __esm({
|
|
|
40175
40681
|
const firstArg = args.namedChildren[0];
|
|
40176
40682
|
if (!firstArg)
|
|
40177
40683
|
return null;
|
|
40178
|
-
if (firstArg.type
|
|
40179
|
-
return
|
|
40180
|
-
|
|
40181
|
-
|
|
40684
|
+
if (firstArg.type === "string" && !firstArg.text.startsWith("f"))
|
|
40685
|
+
return null;
|
|
40686
|
+
if (firstArg.type === "identifier" && isConfigSourcedVar(node, firstArg.text))
|
|
40687
|
+
return null;
|
|
40688
|
+
return makeViolation(this.ruleKey, node, filePath, "high", "urlopen with user-controlled URL", `${objectName ? objectName + "." : ""}urlopen() called with a non-literal URL. User-controlled URLs enable SSRF.`, sourceCode, "Validate and allowlist URLs before passing them to urlopen().");
|
|
40182
40689
|
}
|
|
40183
40690
|
};
|
|
40184
40691
|
}
|
|
@@ -42636,8 +43143,15 @@ var init_exclusions = __esm({
|
|
|
42636
43143
|
// All asterisks (masked values)
|
|
42637
43144
|
/^x+$/i,
|
|
42638
43145
|
// All x's (placeholder)
|
|
42639
|
-
/^\.{3,}
|
|
43146
|
+
/^\.{3,}$/,
|
|
42640
43147
|
// Ellipsis placeholders
|
|
43148
|
+
// Filenames with a recognized document / media / archive / data
|
|
43149
|
+
// extension. Composed object keys like `<hash>__<date>__<batch>.pdf`
|
|
43150
|
+
// look like high-entropy tokens to pattern detectors, but the
|
|
43151
|
+
// trailing extension makes them clearly filenames. The optional
|
|
43152
|
+
// trailing slash matches S3 path prefixes that act as directories
|
|
43153
|
+
// (`processed/.../<file>.pdf/source_pdfs/...`).
|
|
43154
|
+
/\.(pdf|csv|tsv|json|jsonl|ndjson|parquet|xml|html?|md|rst|txt|log|zip|tar|gz|tgz|bz2|7z|rar|png|jpe?g|gif|webp|svg|ico|bmp|tiff?|mp[34]|wav|flac|ogg|webm|avi|mov|mkv|docx?|xlsx?|pptx?|odt|ods|odp|rtf|epub|yaml|yml|toml|ini|cfg|conf|sql)\/?$/i
|
|
42641
43155
|
];
|
|
42642
43156
|
}
|
|
42643
43157
|
});
|
|
@@ -44442,7 +44956,7 @@ var init_secret_scanner = __esm({
|
|
|
44442
44956
|
function stripCommentMarkers(text) {
|
|
44443
44957
|
return text.replace(/^\/\/\s?/, "").replace(/^\/\*\s?/, "").replace(/\s?\*\/$/, "").replace(/^\s*\*\s?/gm, "").replace(/^#\s?/gm, "").trim();
|
|
44444
44958
|
}
|
|
44445
|
-
var hardcodedSecretVisitor, IPV4_REGEX, EXCLUDED_IPS, hardcodedIpVisitor, CLEARTEXT_PROTOCOLS, LOCALHOST_PREFIXES, clearTextProtocolVisitor, BIP39_COMMON_WORDS, hardcodedBlockchainMnemonicVisitor,
|
|
44959
|
+
var hardcodedSecretVisitor, IPV4_REGEX, EXCLUDED_IPS, hardcodedIpVisitor, CLEARTEXT_PROTOCOLS, LOCALHOST_PREFIXES, clearTextProtocolVisitor, BIP39_COMMON_WORDS, hardcodedBlockchainMnemonicVisitor, DB_URL_PATTERN, DB_PASSWORD_KV_PATTERNS, NON_LITERAL_PASSWORD_SEGMENT, hardcodedDatabasePasswordVisitor, ldapUnauthenticatedVisitor, passwordStoredPlaintextVisitor, HASH_FUNCTIONS_NEEDING_SALT, unpredictableSaltMissingVisitor, sensitiveFileVisitor, hardcodedSecretInCommentVisitor, SECURITY_UNIVERSAL_VISITORS;
|
|
44446
44960
|
var init_universal = __esm({
|
|
44447
44961
|
"packages/analyzer/dist/rules/security/visitors/universal.js"() {
|
|
44448
44962
|
"use strict";
|
|
@@ -44460,6 +44974,15 @@ var init_universal = __esm({
|
|
|
44460
44974
|
if (parent?.type === "pair" && parent.childForFieldName("key")?.id === node.id) {
|
|
44461
44975
|
return null;
|
|
44462
44976
|
}
|
|
44977
|
+
if (parent?.type === "expression_statement") {
|
|
44978
|
+
const grandParent = parent.parent;
|
|
44979
|
+
if (grandParent && (grandParent.type === "module" || grandParent.type === "block")) {
|
|
44980
|
+
const firstChild = grandParent.namedChild(0);
|
|
44981
|
+
if (firstChild && firstChild.id === parent.id) {
|
|
44982
|
+
return null;
|
|
44983
|
+
}
|
|
44984
|
+
}
|
|
44985
|
+
}
|
|
44463
44986
|
const match2 = scanForSecrets(stripped);
|
|
44464
44987
|
if (match2) {
|
|
44465
44988
|
return makeViolation(this.ruleKey, node, filePath, "critical", `Hardcoded secret detected (${match2.patternId})`, `This string matches a known secret pattern: ${match2.description}. Use environment variables instead.`, sourceCode, "Move this secret to an environment variable.");
|
|
@@ -44474,7 +44997,11 @@ var init_universal = __esm({
|
|
|
44474
44997
|
const secretNames = ["password", "passwd", "secret", "apikey", "api_key", "token", "auth_token", "access_token", "private_key"];
|
|
44475
44998
|
const isNonSecretName = /(?:uri|url|endpoint|type|scope|name|header|grant|method)/.test(name);
|
|
44476
44999
|
const isNonSecretValue = /https?:\/\//.test(stripped) || /^(true|false|null|undefined|localhost|None|True|False|Bearer)$/i.test(stripped) || /[[\]<>{}()#.=\s]/.test(stripped);
|
|
44477
|
-
|
|
45000
|
+
const nameClean = name.replace(/^['"`]+|['"`]+$/g, "");
|
|
45001
|
+
const valueClean = stripped.toLowerCase();
|
|
45002
|
+
const isPlainAlphaSnake = /^[a-z_]+$/.test(valueClean);
|
|
45003
|
+
const isVocabularyTag = isPlainAlphaSnake && nameClean.length >= 4 && (valueClean === nameClean || valueClean.includes(nameClean));
|
|
45004
|
+
if (secretNames.some((s) => name.includes(s)) && stripped.length >= 8 && !isNonSecretName && !isNonSecretValue && !isVocabularyTag) {
|
|
44478
45005
|
return makeViolation(this.ruleKey, node, filePath, "critical", "Hardcoded secret detected", `Variable "${nameNode.text}" contains what appears to be a hardcoded secret. Use environment variables instead.`, sourceCode, "Move this secret to an environment variable.");
|
|
44479
45006
|
}
|
|
44480
45007
|
}
|
|
@@ -45488,21 +46015,28 @@ var init_universal = __esm({
|
|
|
45488
46015
|
return null;
|
|
45489
46016
|
}
|
|
45490
46017
|
};
|
|
45491
|
-
|
|
45492
|
-
|
|
46018
|
+
DB_URL_PATTERN = /(?:mysql|postgresql|postgres|mongodb|sqlite|mssql|oracle):\/\/[^:]+:([^@]+)@/i;
|
|
46019
|
+
DB_PASSWORD_KV_PATTERNS = [
|
|
45493
46020
|
/password\s*=\s*['"][^'"]{4,}['"]/i,
|
|
45494
46021
|
/pwd\s*=\s*['"][^'"]{4,}['"]/i
|
|
45495
46022
|
];
|
|
46023
|
+
NON_LITERAL_PASSWORD_SEGMENT = /^\{[^{}]*\}$|^\$\{[^}]*\}$|^%s$|^%\([^)]+\)s$|^<[^>]+>$|^\$\([^)]*\)$|^\$[A-Z_][A-Z0-9_]*$|process\.env/i;
|
|
45496
46024
|
hardcodedDatabasePasswordVisitor = {
|
|
45497
46025
|
ruleKey: "security/deterministic/hardcoded-database-password",
|
|
45498
46026
|
nodeTypes: ["string", "template_string"],
|
|
45499
46027
|
visit(node, filePath, sourceCode) {
|
|
45500
46028
|
const text = node.text;
|
|
45501
46029
|
const stripped = text.replace(/^[fFbBrRuU]*['"`]{1,3}|['"`]{1,3}$/g, "");
|
|
45502
|
-
|
|
46030
|
+
if (/\$\{|%s|<password>|\$\(|process\.env/i.test(stripped))
|
|
46031
|
+
return null;
|
|
46032
|
+
const urlMatch = DB_URL_PATTERN.exec(stripped);
|
|
46033
|
+
if (urlMatch) {
|
|
46034
|
+
if (NON_LITERAL_PASSWORD_SEGMENT.test(urlMatch[1]))
|
|
46035
|
+
return null;
|
|
46036
|
+
return makeViolation(this.ruleKey, node, filePath, "critical", "Hardcoded database password", "Database connection string contains a hardcoded password.", sourceCode, "Load database credentials from environment variables.");
|
|
46037
|
+
}
|
|
46038
|
+
for (const pattern of DB_PASSWORD_KV_PATTERNS) {
|
|
45503
46039
|
if (pattern.test(stripped)) {
|
|
45504
|
-
if (/\$\{|%s|<password>|\$\(|process\.env/i.test(stripped))
|
|
45505
|
-
return null;
|
|
45506
46040
|
return makeViolation(this.ruleKey, node, filePath, "critical", "Hardcoded database password", "Database connection string contains a hardcoded password.", sourceCode, "Load database credentials from environment variables.");
|
|
45507
46041
|
}
|
|
45508
46042
|
}
|
|
@@ -47478,6 +48012,78 @@ function isKeyFromControlledIteration(assignmentNode, keyName) {
|
|
|
47478
48012
|
}
|
|
47479
48013
|
return false;
|
|
47480
48014
|
}
|
|
48015
|
+
function isKeyFromForOfLoopVarMember(assignmentNode, keyName) {
|
|
48016
|
+
let scope = assignmentNode.parent;
|
|
48017
|
+
while (scope) {
|
|
48018
|
+
if (scope.type === "function_declaration" || scope.type === "function_expression" || scope.type === "arrow_function" || scope.type === "method_definition" || scope.type === "program")
|
|
48019
|
+
break;
|
|
48020
|
+
scope = scope.parent;
|
|
48021
|
+
}
|
|
48022
|
+
if (!scope)
|
|
48023
|
+
return false;
|
|
48024
|
+
const forVarNames = /* @__PURE__ */ new Set();
|
|
48025
|
+
function collectForVars(n) {
|
|
48026
|
+
if (n.type === "for_in_statement") {
|
|
48027
|
+
const left = n.childForFieldName("left");
|
|
48028
|
+
if (left?.type === "identifier")
|
|
48029
|
+
forVarNames.add(left.text);
|
|
48030
|
+
}
|
|
48031
|
+
if (n !== scope && (n.type === "function_declaration" || n.type === "function_expression" || n.type === "arrow_function" || n.type === "method_definition"))
|
|
48032
|
+
return;
|
|
48033
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
48034
|
+
const ch = n.child(i);
|
|
48035
|
+
if (ch)
|
|
48036
|
+
collectForVars(ch);
|
|
48037
|
+
}
|
|
48038
|
+
}
|
|
48039
|
+
collectForVars(scope);
|
|
48040
|
+
if (forVarNames.size === 0)
|
|
48041
|
+
return false;
|
|
48042
|
+
let found = false;
|
|
48043
|
+
function findAssignment(n) {
|
|
48044
|
+
if (found)
|
|
48045
|
+
return;
|
|
48046
|
+
if (n.type === "variable_declarator") {
|
|
48047
|
+
const name = n.childForFieldName("name");
|
|
48048
|
+
const value = n.childForFieldName("value");
|
|
48049
|
+
if (name?.type === "identifier" && name.text === keyName && value?.type === "member_expression") {
|
|
48050
|
+
const obj = value.childForFieldName("object");
|
|
48051
|
+
if (obj?.type === "identifier" && forVarNames.has(obj.text)) {
|
|
48052
|
+
found = true;
|
|
48053
|
+
return;
|
|
48054
|
+
}
|
|
48055
|
+
}
|
|
48056
|
+
}
|
|
48057
|
+
if (n !== scope && (n.type === "function_declaration" || n.type === "function_expression" || n.type === "arrow_function" || n.type === "method_definition"))
|
|
48058
|
+
return;
|
|
48059
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
48060
|
+
const ch = n.child(i);
|
|
48061
|
+
if (ch)
|
|
48062
|
+
findAssignment(ch);
|
|
48063
|
+
}
|
|
48064
|
+
}
|
|
48065
|
+
findAssignment(scope);
|
|
48066
|
+
return found;
|
|
48067
|
+
}
|
|
48068
|
+
function isAssignmentGuardedByKeyInObject(assignmentNode, keyName, objText) {
|
|
48069
|
+
let cursor = assignmentNode.parent;
|
|
48070
|
+
while (cursor) {
|
|
48071
|
+
if (cursor.type === "if_statement") {
|
|
48072
|
+
const cond = cursor.childForFieldName("condition");
|
|
48073
|
+
if (cond) {
|
|
48074
|
+
const condText = cond.text;
|
|
48075
|
+
const pattern = new RegExp(`\\b${escapeRegExp(keyName)}\\s+in\\s+${escapeRegExp(objText)}\\b`);
|
|
48076
|
+
if (pattern.test(condText))
|
|
48077
|
+
return true;
|
|
48078
|
+
}
|
|
48079
|
+
}
|
|
48080
|
+
cursor = cursor.parent;
|
|
48081
|
+
}
|
|
48082
|
+
return false;
|
|
48083
|
+
}
|
|
48084
|
+
function escapeRegExp(s) {
|
|
48085
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
48086
|
+
}
|
|
47481
48087
|
function isObjectEntriesOrKeys(node) {
|
|
47482
48088
|
if (node.type === "call_expression") {
|
|
47483
48089
|
const fn = node.childForFieldName("function");
|
|
@@ -47491,6 +48097,77 @@ function isObjectEntriesOrKeys(node) {
|
|
|
47491
48097
|
}
|
|
47492
48098
|
return false;
|
|
47493
48099
|
}
|
|
48100
|
+
function isKeyDestructuredParam(assignmentNode, keyName) {
|
|
48101
|
+
let scope = assignmentNode.parent;
|
|
48102
|
+
while (scope) {
|
|
48103
|
+
if (scope.type === "function_declaration" || scope.type === "function_expression" || scope.type === "arrow_function" || scope.type === "method_definition") {
|
|
48104
|
+
const params = scope.childForFieldName("parameters") ?? scope.childForFieldName("parameter");
|
|
48105
|
+
if (params && containsDestructuredName(params, keyName))
|
|
48106
|
+
return true;
|
|
48107
|
+
}
|
|
48108
|
+
if (scope.type === "program")
|
|
48109
|
+
break;
|
|
48110
|
+
scope = scope.parent;
|
|
48111
|
+
}
|
|
48112
|
+
return false;
|
|
48113
|
+
}
|
|
48114
|
+
function containsDestructuredName(params, keyName) {
|
|
48115
|
+
let found = false;
|
|
48116
|
+
function walk(n) {
|
|
48117
|
+
if (found)
|
|
48118
|
+
return;
|
|
48119
|
+
if (n.type === "identifier" && n.text === keyName) {
|
|
48120
|
+
const parent = n.parent;
|
|
48121
|
+
if (parent?.type === "array_pattern" || parent?.type === "object_pattern") {
|
|
48122
|
+
found = true;
|
|
48123
|
+
return;
|
|
48124
|
+
}
|
|
48125
|
+
if (parent?.type === "shorthand_property_identifier_pattern" && n.text === keyName) {
|
|
48126
|
+
found = true;
|
|
48127
|
+
return;
|
|
48128
|
+
}
|
|
48129
|
+
}
|
|
48130
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
48131
|
+
const child = n.child(i);
|
|
48132
|
+
if (child)
|
|
48133
|
+
walk(child);
|
|
48134
|
+
}
|
|
48135
|
+
}
|
|
48136
|
+
walk(params);
|
|
48137
|
+
return found;
|
|
48138
|
+
}
|
|
48139
|
+
function isKeyAssignedFromLocalCall(assignmentNode, keyName) {
|
|
48140
|
+
let scope = assignmentNode.parent;
|
|
48141
|
+
while (scope) {
|
|
48142
|
+
if (scope.type === "function_declaration" || scope.type === "function_expression" || scope.type === "arrow_function" || scope.type === "method_definition" || scope.type === "program")
|
|
48143
|
+
break;
|
|
48144
|
+
scope = scope.parent;
|
|
48145
|
+
}
|
|
48146
|
+
if (!scope)
|
|
48147
|
+
return false;
|
|
48148
|
+
let found = false;
|
|
48149
|
+
function walk(n) {
|
|
48150
|
+
if (found)
|
|
48151
|
+
return;
|
|
48152
|
+
if (n.type === "variable_declarator") {
|
|
48153
|
+
const name = n.childForFieldName("name");
|
|
48154
|
+
const value = n.childForFieldName("value");
|
|
48155
|
+
if (name?.type === "identifier" && name.text === keyName && value?.type === "call_expression") {
|
|
48156
|
+
found = true;
|
|
48157
|
+
return;
|
|
48158
|
+
}
|
|
48159
|
+
}
|
|
48160
|
+
if (n !== scope && (n.type === "function_declaration" || n.type === "function_expression" || n.type === "arrow_function" || n.type === "method_definition"))
|
|
48161
|
+
return;
|
|
48162
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
48163
|
+
const child = n.child(i);
|
|
48164
|
+
if (child)
|
|
48165
|
+
walk(child);
|
|
48166
|
+
}
|
|
48167
|
+
}
|
|
48168
|
+
walk(scope);
|
|
48169
|
+
return found;
|
|
48170
|
+
}
|
|
47494
48171
|
var prototypePollutionVisitor;
|
|
47495
48172
|
var init_prototype_pollution = __esm({
|
|
47496
48173
|
"packages/analyzer/dist/rules/bugs/visitors/javascript/prototype-pollution.js"() {
|
|
@@ -47521,6 +48198,14 @@ var init_prototype_pollution = __esm({
|
|
|
47521
48198
|
const keyName = index.text;
|
|
47522
48199
|
if (isKeyFromControlledIteration(node, keyName))
|
|
47523
48200
|
return null;
|
|
48201
|
+
if (isKeyAssignedFromLocalCall(node, keyName))
|
|
48202
|
+
return null;
|
|
48203
|
+
if (isKeyDestructuredParam(node, keyName))
|
|
48204
|
+
return null;
|
|
48205
|
+
if (isKeyFromForOfLoopVarMember(node, keyName))
|
|
48206
|
+
return null;
|
|
48207
|
+
if (isAssignmentGuardedByKeyInObject(node, keyName, obj.text))
|
|
48208
|
+
return null;
|
|
47524
48209
|
return makeViolation(this.ruleKey, node, filePath, "high", "Prototype pollution", `\`${left.text}\` uses a dynamic key for property assignment \u2014 if \`${index.text}\` is \`"__proto__"\` or \`"constructor"\`, this enables prototype pollution.`, sourceCode, `Validate that \`${index.text}\` is not "__proto__", "constructor", or "prototype" before assignment, or use Map instead.`);
|
|
47525
48210
|
}
|
|
47526
48211
|
};
|
|
@@ -51444,8 +52129,10 @@ var init_useeffect_missing_deps = __esm({
|
|
|
51444
52129
|
}
|
|
51445
52130
|
const suspiciousIds = [...usedIds].filter((id) => /^[a-z]/.test(id) && !id.startsWith("set") && id.length > 1 && !EXCLUDED_IDENTIFIERS.has(id) && !refAccessedIds.has(id) && !stableFunctions.has(id) && !componentBodyFunctions.has(id));
|
|
51446
52131
|
const nodeStartLine = node.startPosition.row;
|
|
51447
|
-
const
|
|
51448
|
-
|
|
52132
|
+
const nodeEndLine = node.endPosition.row;
|
|
52133
|
+
const sourceLines = sourceCode.split("\n");
|
|
52134
|
+
const windowLines = sourceLines.slice(Math.max(0, nodeStartLine - 2), nodeEndLine + 2);
|
|
52135
|
+
if (windowLines.some((line) => line.includes("eslint-disable")))
|
|
51449
52136
|
return null;
|
|
51450
52137
|
if (suspiciousIds.length > 0) {
|
|
51451
52138
|
return makeViolation(this.ruleKey, depsArg, filePath, "high", "useEffect with empty deps may have stale closure", `\`useEffect\` has an empty dependency array but references \`${suspiciousIds.slice(0, 3).join("`, `")}\` \u2014 these will be stale closures if they change.`, sourceCode, `Add the referenced variables to the dependency array: \`[${suspiciousIds.slice(0, 3).join(", ")}]\`.`);
|
|
@@ -52909,7 +53596,7 @@ var init_empty_catch2 = __esm({
|
|
|
52909
53596
|
if (!body)
|
|
52910
53597
|
return null;
|
|
52911
53598
|
const statements = body.namedChildren.filter((c2) => c2.type !== "comment");
|
|
52912
|
-
if (statements.length === 0
|
|
53599
|
+
if (statements.length === 0) {
|
|
52913
53600
|
return makeViolation(this.ruleKey, node, filePath, "medium", "Empty except block", "This except block swallows errors silently. Add error handling or at least log the error.", sourceCode, "Add error logging or re-raise the exception in this except block.");
|
|
52914
53601
|
}
|
|
52915
53602
|
return null;
|
|
@@ -52951,6 +53638,15 @@ var init_bare_except = __esm({
|
|
|
52951
53638
|
});
|
|
52952
53639
|
|
|
52953
53640
|
// packages/analyzer/dist/rules/bugs/visitors/python/_helpers.js
|
|
53641
|
+
function isFStringWithInterpolation(node) {
|
|
53642
|
+
if (node.type !== "string")
|
|
53643
|
+
return false;
|
|
53644
|
+
for (const child of node.namedChildren) {
|
|
53645
|
+
if (child && child.type === "interpolation")
|
|
53646
|
+
return true;
|
|
53647
|
+
}
|
|
53648
|
+
return false;
|
|
53649
|
+
}
|
|
52954
53650
|
var MUTABLE_DEFAULTS, PY_TERMINAL_TYPES, SAFE_DEFAULT_CALLS, VALID_OPEN_MODES, DUNDER_PARAM_COUNTS, CANCELLATION_EXCEPTIONS, BROAD_EXCEPTIONS, MUTATING_METHODS, SPECIAL_METHOD_RETURN_CONSTRAINTS, PYTHON_BUILTIN_NON_EXCEPTIONS, BIDI_CHARS, VALID_FORMAT_CHARS, FORMAT_SPEC_RE;
|
|
52955
53651
|
var init_helpers3 = __esm({
|
|
52956
53652
|
"packages/analyzer/dist/rules/bugs/visitors/python/_helpers.js"() {
|
|
@@ -53753,17 +54449,25 @@ var init_loop_variable_overrides_iterator = __esm({
|
|
|
53753
54449
|
if (loopVar.type !== "identifier")
|
|
53754
54450
|
return null;
|
|
53755
54451
|
const varName = loopVar.text;
|
|
53756
|
-
function
|
|
53757
|
-
if (n.type === "identifier" && n.text === name)
|
|
54452
|
+
function hasFreeUse(n, name) {
|
|
54453
|
+
if (n.type === "identifier" && n.text === name) {
|
|
54454
|
+
const parent = n.parent;
|
|
54455
|
+
if (parent?.type === "attribute" && parent.childForFieldName("attribute")?.id === n.id) {
|
|
54456
|
+
return false;
|
|
54457
|
+
}
|
|
54458
|
+
if (parent?.type === "keyword_argument" && parent.childForFieldName("name")?.id === n.id) {
|
|
54459
|
+
return false;
|
|
54460
|
+
}
|
|
53758
54461
|
return true;
|
|
54462
|
+
}
|
|
53759
54463
|
for (let i = 0; i < n.childCount; i++) {
|
|
53760
54464
|
const child = n.child(i);
|
|
53761
|
-
if (child &&
|
|
54465
|
+
if (child && hasFreeUse(child, name))
|
|
53762
54466
|
return true;
|
|
53763
54467
|
}
|
|
53764
54468
|
return false;
|
|
53765
54469
|
}
|
|
53766
|
-
if (
|
|
54470
|
+
if (hasFreeUse(iterExpr, varName)) {
|
|
53767
54471
|
return makeViolation(this.ruleKey, loopVar, filePath, "high", "Loop variable overrides iterator", `Loop variable \`${varName}\` has the same name as the iterable \`${iterExpr.text}\` \u2014 after the first iteration \`${varName}\` no longer references the original iterable.`, sourceCode, `Rename the loop variable to something different from \`${iterExpr.text}\`.`);
|
|
53768
54472
|
}
|
|
53769
54473
|
return null;
|
|
@@ -55353,6 +56057,7 @@ var init_static_key_dict_comprehension = __esm({
|
|
|
55353
56057
|
"packages/analyzer/dist/rules/bugs/visitors/python/static-key-dict-comprehension.js"() {
|
|
55354
56058
|
"use strict";
|
|
55355
56059
|
init_types();
|
|
56060
|
+
init_helpers3();
|
|
55356
56061
|
pythonStaticKeyDictComprehensionVisitor = {
|
|
55357
56062
|
ruleKey: "bugs/deterministic/static-key-dict-comprehension",
|
|
55358
56063
|
languages: ["python"],
|
|
@@ -55365,7 +56070,7 @@ var init_static_key_dict_comprehension = __esm({
|
|
|
55365
56070
|
if (!keyNode)
|
|
55366
56071
|
return null;
|
|
55367
56072
|
const LITERAL_TYPES5 = /* @__PURE__ */ new Set(["string", "integer", "float", "true", "false", "none"]);
|
|
55368
|
-
if (LITERAL_TYPES5.has(keyNode.type)) {
|
|
56073
|
+
if (LITERAL_TYPES5.has(keyNode.type) && !isFStringWithInterpolation(keyNode)) {
|
|
55369
56074
|
return makeViolation(this.ruleKey, keyNode, filePath, "high", "Static key in dict comprehension", `Dict comprehension with constant key \`${keyNode.text}\` \u2014 every iteration overwrites the same key, leaving only the last value.`, sourceCode, "Use a variable as the key, or use a list comprehension if you only need the values.");
|
|
55370
56075
|
}
|
|
55371
56076
|
return null;
|
|
@@ -56570,6 +57275,7 @@ var init_duplicate_dict_key = __esm({
|
|
|
56570
57275
|
"packages/analyzer/dist/rules/bugs/visitors/python/duplicate-dict-key.js"() {
|
|
56571
57276
|
"use strict";
|
|
56572
57277
|
init_types();
|
|
57278
|
+
init_helpers3();
|
|
56573
57279
|
pythonDuplicateDictKeyVisitor = {
|
|
56574
57280
|
ruleKey: "bugs/deterministic/duplicate-dict-key",
|
|
56575
57281
|
languages: ["python"],
|
|
@@ -56585,7 +57291,8 @@ var init_duplicate_dict_key = __esm({
|
|
|
56585
57291
|
const leftNode = forInClause?.childForFieldName("left");
|
|
56586
57292
|
const loopVarNames = leftNode ? collectLoopVarNames(leftNode) : /* @__PURE__ */ new Set();
|
|
56587
57293
|
const LITERAL_TYPES5 = /* @__PURE__ */ new Set(["string", "integer", "float", "true", "false", "none"]);
|
|
56588
|
-
const
|
|
57294
|
+
const isStaticLiteral = LITERAL_TYPES5.has(keyNode.type) && !isFStringWithInterpolation(keyNode);
|
|
57295
|
+
const isConstantKey = isStaticLiteral || keyNode.type === "identifier" && loopVarNames.size > 0 && !loopVarNames.has(keyNode.text);
|
|
56589
57296
|
if (isConstantKey) {
|
|
56590
57297
|
return makeViolation(this.ruleKey, keyNode, filePath, "high", "Constant key in dict comprehension", `Dict comprehension with constant key \`${keyNode.text}\` \u2014 each iteration overwrites the same key, leaving only the last value.`, sourceCode, "Use a key expression that depends on the loop variable, or use a list comprehension instead.");
|
|
56591
57298
|
}
|
|
@@ -56992,9 +57699,8 @@ var init_warnings_no_stacklevel = __esm({
|
|
|
56992
57699
|
const funcText = func.text;
|
|
56993
57700
|
if (funcText !== "warnings.warn" && funcText !== "warn")
|
|
56994
57701
|
return null;
|
|
56995
|
-
if (funcText === "warn")
|
|
57702
|
+
if (funcText === "warn")
|
|
56996
57703
|
return null;
|
|
56997
|
-
}
|
|
56998
57704
|
const args = node.childForFieldName("arguments");
|
|
56999
57705
|
if (!args)
|
|
57000
57706
|
return null;
|
|
@@ -58213,6 +58919,27 @@ function isMutableInit2(node) {
|
|
|
58213
58919
|
function isConstantName(name) {
|
|
58214
58920
|
return name === name.toUpperCase();
|
|
58215
58921
|
}
|
|
58922
|
+
function moduleHasLockGuard(moduleNode) {
|
|
58923
|
+
for (let i = 0; i < moduleNode.namedChildCount; i++) {
|
|
58924
|
+
const stmt = moduleNode.namedChild(i);
|
|
58925
|
+
if (!stmt)
|
|
58926
|
+
continue;
|
|
58927
|
+
const inner = stmt.type === "expression_statement" ? stmt.namedChild(0) : stmt;
|
|
58928
|
+
if (inner?.type !== "assignment")
|
|
58929
|
+
continue;
|
|
58930
|
+
const rhs = inner.childForFieldName("right");
|
|
58931
|
+
if (rhs?.type !== "call")
|
|
58932
|
+
continue;
|
|
58933
|
+
const fn = rhs.childForFieldName("function");
|
|
58934
|
+
if (!fn)
|
|
58935
|
+
continue;
|
|
58936
|
+
const text = fn.text;
|
|
58937
|
+
if (text === "threading.Lock" || text === "threading.RLock" || text === "threading.Semaphore" || text === "threading.BoundedSemaphore" || text === "asyncio.Lock" || text === "multiprocessing.Lock" || text === "multiprocessing.RLock" || text === "Lock" || text === "RLock") {
|
|
58938
|
+
return true;
|
|
58939
|
+
}
|
|
58940
|
+
}
|
|
58941
|
+
return false;
|
|
58942
|
+
}
|
|
58216
58943
|
var pythonSharedMutableModuleStateVisitor;
|
|
58217
58944
|
var init_shared_mutable_module_state2 = __esm({
|
|
58218
58945
|
"packages/analyzer/dist/rules/bugs/visitors/python/shared-mutable-module-state.js"() {
|
|
@@ -58236,6 +58963,11 @@ var init_shared_mutable_module_state2 = __esm({
|
|
|
58236
58963
|
return null;
|
|
58237
58964
|
if (varName === "__all__")
|
|
58238
58965
|
return null;
|
|
58966
|
+
let moduleNode = node.parent;
|
|
58967
|
+
while (moduleNode && moduleNode.type !== "module")
|
|
58968
|
+
moduleNode = moduleNode.parent;
|
|
58969
|
+
if (moduleNode && moduleHasLockGuard(moduleNode))
|
|
58970
|
+
return null;
|
|
58239
58971
|
return makeViolation(this.ruleKey, node, filePath, "high", "Shared mutable state in module scope", `\`${varName}\` is a mutable ${right.type} at module level \u2014 in server environments this state is shared across all requests, causing race conditions and data leaks.`, sourceCode, "Move mutable state inside request handlers or use immutable data structures.");
|
|
58240
58972
|
}
|
|
58241
58973
|
};
|
|
@@ -59653,9 +60385,31 @@ function extractStringContent(node) {
|
|
|
59653
60385
|
}
|
|
59654
60386
|
return null;
|
|
59655
60387
|
}
|
|
60388
|
+
function pythonRegexToJs(pattern) {
|
|
60389
|
+
let flags = "";
|
|
60390
|
+
const collectFlag = (f) => {
|
|
60391
|
+
if ("imsu".includes(f) && !flags.includes(f))
|
|
60392
|
+
flags += f;
|
|
60393
|
+
};
|
|
60394
|
+
const prefix = pattern.match(/^\(\?([aiLmsux]+)\)/);
|
|
60395
|
+
if (prefix) {
|
|
60396
|
+
for (const f of prefix[1])
|
|
60397
|
+
collectFlag(f);
|
|
60398
|
+
pattern = pattern.slice(prefix[0].length);
|
|
60399
|
+
}
|
|
60400
|
+
pattern = pattern.replace(/\(\?([aiLmsux]+):/g, (_, flagSet) => {
|
|
60401
|
+
for (const f of flagSet)
|
|
60402
|
+
collectFlag(f);
|
|
60403
|
+
return "(?:";
|
|
60404
|
+
});
|
|
60405
|
+
pattern = pattern.replace(/\(\?P</g, "(?<");
|
|
60406
|
+
pattern = pattern.replace(/\(\?P=([^)]+)\)/g, "\\k<$1>");
|
|
60407
|
+
return { pattern, flags };
|
|
60408
|
+
}
|
|
59656
60409
|
function isValidRegex(pattern) {
|
|
59657
60410
|
try {
|
|
59658
|
-
|
|
60411
|
+
const normalized = pythonRegexToJs(pattern);
|
|
60412
|
+
new RegExp(normalized.pattern, normalized.flags);
|
|
59659
60413
|
return true;
|
|
59660
60414
|
} catch {
|
|
59661
60415
|
return false;
|
|
@@ -61398,6 +62152,25 @@ var init_confusing_implicit_concat = __esm({
|
|
|
61398
62152
|
}
|
|
61399
62153
|
if (hasFormatString)
|
|
61400
62154
|
continue;
|
|
62155
|
+
const isMultiLine = child.startPosition.row !== child.endPosition.row;
|
|
62156
|
+
if (isMultiLine) {
|
|
62157
|
+
const parts = child.namedChildren;
|
|
62158
|
+
let allButLastEndWithSpace = parts.length > 1;
|
|
62159
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
62160
|
+
const t2 = parts[i].text;
|
|
62161
|
+
const stripped = t2.replace(/['"]+$/, "");
|
|
62162
|
+
const lastChar = stripped.charAt(stripped.length - 1);
|
|
62163
|
+
if (lastChar !== " " && lastChar !== "\\" && lastChar !== " ") {
|
|
62164
|
+
allButLastEndWithSpace = false;
|
|
62165
|
+
break;
|
|
62166
|
+
}
|
|
62167
|
+
}
|
|
62168
|
+
if (allButLastEndWithSpace)
|
|
62169
|
+
continue;
|
|
62170
|
+
const totalLen = parts.reduce((sum, p2) => sum + p2.text.length, 0);
|
|
62171
|
+
if (totalLen > 60)
|
|
62172
|
+
continue;
|
|
62173
|
+
}
|
|
61401
62174
|
return makeViolation(this.ruleKey, child, filePath, "medium", "Confusing implicit string concatenation", `Implicit string concatenation \`${child.text.slice(0, 50)}${child.text.length > 50 ? "..." : ""}\` inside a ${node.type.replace(/_/g, " ")} \u2014 adjacent string literals are joined at compile time. If two separate strings were intended, add a comma between them.`, sourceCode, "If you meant two separate strings, add a comma. If intentional concatenation, use explicit `+` or join into a single string.");
|
|
61402
62175
|
}
|
|
61403
62176
|
}
|
|
@@ -61881,6 +62654,7 @@ var init_static_key_dict_comprehension_ruff = __esm({
|
|
|
61881
62654
|
"packages/analyzer/dist/rules/bugs/visitors/python/static-key-dict-comprehension-ruff.js"() {
|
|
61882
62655
|
"use strict";
|
|
61883
62656
|
init_types();
|
|
62657
|
+
init_helpers3();
|
|
61884
62658
|
LITERAL_TYPES2 = /* @__PURE__ */ new Set(["string", "integer", "float", "true", "false", "none"]);
|
|
61885
62659
|
pythonStaticKeyDictComprehensionRuffVisitor = {
|
|
61886
62660
|
ruleKey: "bugs/deterministic/static-key-dict-comprehension-ruff",
|
|
@@ -61893,7 +62667,7 @@ var init_static_key_dict_comprehension_ruff = __esm({
|
|
|
61893
62667
|
const keyNode = pairNode.childForFieldName("key");
|
|
61894
62668
|
if (!keyNode)
|
|
61895
62669
|
return null;
|
|
61896
|
-
if (LITERAL_TYPES2.has(keyNode.type)) {
|
|
62670
|
+
if (LITERAL_TYPES2.has(keyNode.type) && !isFStringWithInterpolation(keyNode)) {
|
|
61897
62671
|
return makeViolation(this.ruleKey, keyNode, filePath, "high", "Static key in dict comprehension", `Dict comprehension with constant key \`${keyNode.text}\` \u2014 every iteration overwrites the same key, resulting in a single-entry dict with only the last value.`, sourceCode, "Use a variable expression as the key. If you only need values, use a list comprehension instead.");
|
|
61898
62672
|
}
|
|
61899
62673
|
return null;
|
|
@@ -63496,7 +64270,16 @@ function findFunctionDefs(root) {
|
|
|
63496
64270
|
hasVarArgs = true;
|
|
63497
64271
|
continue;
|
|
63498
64272
|
}
|
|
63499
|
-
if (p2.type === "
|
|
64273
|
+
if (p2.type === "typed_parameter") {
|
|
64274
|
+
const inner = p2.namedChild(0);
|
|
64275
|
+
if (inner && (inner.type === "list_splat_pattern" || inner.type === "dictionary_splat_pattern")) {
|
|
64276
|
+
hasVarArgs = true;
|
|
64277
|
+
continue;
|
|
64278
|
+
}
|
|
64279
|
+
required++;
|
|
64280
|
+
continue;
|
|
64281
|
+
}
|
|
64282
|
+
if (p2.type === "identifier") {
|
|
63500
64283
|
required++;
|
|
63501
64284
|
} else if (p2.type === "default_parameter" || p2.type === "typed_default_parameter") {
|
|
63502
64285
|
optional++;
|
|
@@ -65482,14 +66265,25 @@ var init_expression_complexity = __esm({
|
|
|
65482
66265
|
return null;
|
|
65483
66266
|
let operatorCount = 0;
|
|
65484
66267
|
const BINARY_TYPES = /* @__PURE__ */ new Set(["binary_expression", "logical_expression"]);
|
|
66268
|
+
const FUNCTION_BOUNDARY_TYPES = /* @__PURE__ */ new Set([
|
|
66269
|
+
"function_declaration",
|
|
66270
|
+
"function_expression",
|
|
66271
|
+
"arrow_function",
|
|
66272
|
+
"method_definition",
|
|
66273
|
+
"generator_function_declaration",
|
|
66274
|
+
"generator_function"
|
|
66275
|
+
]);
|
|
65485
66276
|
function countOps(n) {
|
|
65486
66277
|
if (BINARY_TYPES.has(n.type)) {
|
|
65487
66278
|
operatorCount++;
|
|
65488
66279
|
}
|
|
65489
66280
|
for (let i = 0; i < n.childCount; i++) {
|
|
65490
66281
|
const child = n.child(i);
|
|
65491
|
-
if (child)
|
|
65492
|
-
|
|
66282
|
+
if (!child)
|
|
66283
|
+
continue;
|
|
66284
|
+
if (FUNCTION_BOUNDARY_TYPES.has(child.type))
|
|
66285
|
+
continue;
|
|
66286
|
+
countOps(child);
|
|
65493
66287
|
}
|
|
65494
66288
|
}
|
|
65495
66289
|
countOps(expr);
|
|
@@ -75716,26 +76510,6 @@ var init_and_or_ternary = __esm({
|
|
|
75716
76510
|
}
|
|
75717
76511
|
});
|
|
75718
76512
|
|
|
75719
|
-
// packages/analyzer/dist/rules/code-quality/visitors/python/any-type-hint.js
|
|
75720
|
-
var pythonAnyTypeHintVisitor;
|
|
75721
|
-
var init_any_type_hint = __esm({
|
|
75722
|
-
"packages/analyzer/dist/rules/code-quality/visitors/python/any-type-hint.js"() {
|
|
75723
|
-
"use strict";
|
|
75724
|
-
init_types();
|
|
75725
|
-
pythonAnyTypeHintVisitor = {
|
|
75726
|
-
ruleKey: "code-quality/deterministic/any-type-hint",
|
|
75727
|
-
languages: ["python"],
|
|
75728
|
-
nodeTypes: ["type"],
|
|
75729
|
-
visit(node, filePath, sourceCode) {
|
|
75730
|
-
const text = node.text.trim();
|
|
75731
|
-
if (text !== "Any")
|
|
75732
|
-
return null;
|
|
75733
|
-
return makeViolation(this.ruleKey, node, filePath, "medium", "Any used as type hint", "Using `Any` as a type hint defeats type checking \u2014 use a more specific type.", sourceCode, "Replace `Any` with a specific type annotation, or use `object` if truly any type is acceptable.");
|
|
75734
|
-
}
|
|
75735
|
-
};
|
|
75736
|
-
}
|
|
75737
|
-
});
|
|
75738
|
-
|
|
75739
76513
|
// packages/analyzer/dist/rules/code-quality/visitors/python/assert-in-production.js
|
|
75740
76514
|
var pythonAssertInProductionVisitor;
|
|
75741
76515
|
var init_assert_in_production = __esm({
|
|
@@ -78272,6 +79046,24 @@ var init_implicit_string_concatenation = __esm({
|
|
|
78272
79046
|
const grandparent = parent.parent;
|
|
78273
79047
|
if (!isCollection(parent) && !isCollection(grandparent ?? { type: "" }))
|
|
78274
79048
|
return null;
|
|
79049
|
+
if (node.startPosition.row !== node.endPosition.row) {
|
|
79050
|
+
const parts = node.namedChildren;
|
|
79051
|
+
let allButLastEndWithSpace = parts.length > 1;
|
|
79052
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
79053
|
+
const t2 = parts[i].text;
|
|
79054
|
+
const inner = t2.replace(/['"]+$/, "");
|
|
79055
|
+
const lastChar = inner.charAt(inner.length - 1);
|
|
79056
|
+
if (lastChar !== " " && lastChar !== "\\" && lastChar !== " ") {
|
|
79057
|
+
allButLastEndWithSpace = false;
|
|
79058
|
+
break;
|
|
79059
|
+
}
|
|
79060
|
+
}
|
|
79061
|
+
if (allButLastEndWithSpace)
|
|
79062
|
+
return null;
|
|
79063
|
+
const totalLen = parts.reduce((sum, p2) => sum + p2.text.length, 0);
|
|
79064
|
+
if (totalLen > 60)
|
|
79065
|
+
return null;
|
|
79066
|
+
}
|
|
78275
79067
|
return makeViolation(this.ruleKey, node, filePath, "high", "Implicit string concatenation in collection", "Adjacent string literals in a collection are implicitly concatenated \u2014 this may be a missing comma.", sourceCode, "Add a comma between the strings, or use explicit `+` concatenation if intentional.");
|
|
78276
79068
|
}
|
|
78277
79069
|
};
|
|
@@ -82015,6 +82807,14 @@ var init_try_except_continue = __esm({
|
|
|
82015
82807
|
return null;
|
|
82016
82808
|
if (stmts[0].type !== "continue_statement")
|
|
82017
82809
|
return null;
|
|
82810
|
+
const exprChildren = node.namedChildren.filter((c2) => c2.type !== "block" && c2.type !== "comment");
|
|
82811
|
+
if (exprChildren.length > 0) {
|
|
82812
|
+
const exceptionType = exprChildren[0];
|
|
82813
|
+
const typeText = exceptionType.type === "as_pattern" ? exceptionType.namedChildren[0]?.text : exceptionType.text;
|
|
82814
|
+
if (typeText && typeText !== "Exception" && typeText !== "BaseException") {
|
|
82815
|
+
return null;
|
|
82816
|
+
}
|
|
82817
|
+
}
|
|
82018
82818
|
return makeViolation(this.ruleKey, node, filePath, "medium", "Silent exception with continue", "`except` block with only `continue` silently ignores errors in loops.", sourceCode, "Add logging or error handling before `continue`, or use `contextlib.suppress()` for intentional suppression.");
|
|
82019
82819
|
}
|
|
82020
82820
|
};
|
|
@@ -84386,34 +85186,56 @@ var init_ambiguous_unicode_character = __esm({
|
|
|
84386
85186
|
"use strict";
|
|
84387
85187
|
init_types();
|
|
84388
85188
|
CONFUSABLE_MAP = {
|
|
84389
|
-
"\xB4": "'",
|
|
84390
|
-
// acute accent → apostrophe
|
|
84391
|
-
"\u2018": "'",
|
|
84392
|
-
// left single quote → apostrophe
|
|
84393
|
-
"\u2019": "'",
|
|
84394
|
-
// right single quote → apostrophe
|
|
84395
|
-
"\u201C": '"',
|
|
84396
|
-
// left double quote → double quote
|
|
84397
|
-
"\u201D": '"',
|
|
84398
|
-
// right double quote → double quote
|
|
84399
|
-
"\u2013": "-",
|
|
84400
|
-
// en dash → hyphen
|
|
84401
|
-
"\u2014": "--",
|
|
84402
|
-
// em dash → double hyphen
|
|
84403
|
-
"\xAD": "-",
|
|
84404
|
-
// soft hyphen
|
|
84405
|
-
"\u2212": "-",
|
|
84406
|
-
// minus sign
|
|
84407
|
-
"\xD7": "x",
|
|
84408
|
-
// multiplication sign
|
|
84409
85189
|
"\u03BF": "o",
|
|
84410
85190
|
// Greek small letter omicron
|
|
84411
85191
|
"\u0430": "a",
|
|
84412
85192
|
// Cyrillic small a
|
|
84413
85193
|
"\u0435": "e",
|
|
84414
85194
|
// Cyrillic small e
|
|
84415
|
-
"\u043E": "o"
|
|
85195
|
+
"\u043E": "o",
|
|
84416
85196
|
// Cyrillic small o
|
|
85197
|
+
"\u0440": "p",
|
|
85198
|
+
// Cyrillic small er
|
|
85199
|
+
"\u0441": "c",
|
|
85200
|
+
// Cyrillic small es
|
|
85201
|
+
"\u0445": "x",
|
|
85202
|
+
// Cyrillic small ha
|
|
85203
|
+
"\u0455": "s",
|
|
85204
|
+
// Cyrillic small dze
|
|
85205
|
+
"\u0456": "i",
|
|
85206
|
+
// Cyrillic small Byelorussian-Ukrainian I
|
|
85207
|
+
"\u0501": "d",
|
|
85208
|
+
// Cyrillic small komi de
|
|
85209
|
+
"\u051B": "q",
|
|
85210
|
+
// Cyrillic small qa
|
|
85211
|
+
"\u051D": "w",
|
|
85212
|
+
// Cyrillic small we
|
|
85213
|
+
"\u0399": "I",
|
|
85214
|
+
// Greek capital iota
|
|
85215
|
+
"\u0396": "Z",
|
|
85216
|
+
// Greek capital zeta
|
|
85217
|
+
"\u039F": "O",
|
|
85218
|
+
// Greek capital omicron
|
|
85219
|
+
"\u0410": "A",
|
|
85220
|
+
// Cyrillic capital A
|
|
85221
|
+
"\u0415": "E",
|
|
85222
|
+
// Cyrillic capital E
|
|
85223
|
+
"\u041A": "K",
|
|
85224
|
+
// Cyrillic capital Ka
|
|
85225
|
+
"\u041C": "M",
|
|
85226
|
+
// Cyrillic capital Em
|
|
85227
|
+
"\u041D": "H",
|
|
85228
|
+
// Cyrillic capital En (renders as H)
|
|
85229
|
+
"\u041E": "O",
|
|
85230
|
+
// Cyrillic capital O
|
|
85231
|
+
"\u0420": "P",
|
|
85232
|
+
// Cyrillic capital er
|
|
85233
|
+
"\u0421": "C",
|
|
85234
|
+
// Cyrillic capital es
|
|
85235
|
+
"\u0422": "T",
|
|
85236
|
+
// Cyrillic capital te
|
|
85237
|
+
"\u0425": "X"
|
|
85238
|
+
// Cyrillic capital ha
|
|
84417
85239
|
};
|
|
84418
85240
|
pythonAmbiguousUnicodeCharacterVisitor = {
|
|
84419
85241
|
ruleKey: "code-quality/deterministic/ambiguous-unicode-character",
|
|
@@ -84661,6 +85483,8 @@ var init_regex_char_class_preferred = __esm({
|
|
|
84661
85483
|
if (!isReCall4)
|
|
84662
85484
|
return null;
|
|
84663
85485
|
const pattern = node.text.slice(1, -1);
|
|
85486
|
+
if (/\(\?[aiLmux]*s[aiLmux]*[):]/.test(pattern))
|
|
85487
|
+
return null;
|
|
84664
85488
|
if (/\..+?\?/.test(pattern) || /\.\*\?/.test(pattern)) {
|
|
84665
85489
|
return makeViolation(this.ruleKey, node, filePath, "low", "Reluctant quantifier where character class preferred", "Using `.+?` or `.*?` \u2014 a character class like `[^x]*` is more explicit and efficient than a reluctant quantifier.", sourceCode, "Replace the reluctant quantifier with an explicit character class.");
|
|
84666
85490
|
}
|
|
@@ -85388,6 +86212,62 @@ var init_lambda_reserved_env_var = __esm({
|
|
|
85388
86212
|
});
|
|
85389
86213
|
|
|
85390
86214
|
// packages/analyzer/dist/rules/code-quality/visitors/python/boto3-client-error.js
|
|
86215
|
+
function findAwsClientVarNames(root) {
|
|
86216
|
+
const names = /* @__PURE__ */ new Set();
|
|
86217
|
+
function walk(n) {
|
|
86218
|
+
if (n.type === "assignment") {
|
|
86219
|
+
const lhs = n.childForFieldName("left");
|
|
86220
|
+
const rhs = n.childForFieldName("right");
|
|
86221
|
+
if (lhs?.type === "identifier" && rhs?.type === "call") {
|
|
86222
|
+
const fn = rhs.childForFieldName("function");
|
|
86223
|
+
if (fn?.type === "attribute") {
|
|
86224
|
+
const obj = fn.childForFieldName("object");
|
|
86225
|
+
const attr = fn.childForFieldName("attribute");
|
|
86226
|
+
if ((obj?.text === "boto3" || obj?.text === "aiobotocore") && (attr?.text === "client" || attr?.text === "resource")) {
|
|
86227
|
+
names.add(lhs.text);
|
|
86228
|
+
}
|
|
86229
|
+
}
|
|
86230
|
+
}
|
|
86231
|
+
}
|
|
86232
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
86233
|
+
const child = n.child(i);
|
|
86234
|
+
if (child)
|
|
86235
|
+
walk(child);
|
|
86236
|
+
}
|
|
86237
|
+
}
|
|
86238
|
+
walk(root);
|
|
86239
|
+
return names;
|
|
86240
|
+
}
|
|
86241
|
+
function bodyHasAwsCall(body, awsVarNames) {
|
|
86242
|
+
let found = false;
|
|
86243
|
+
function walk(n) {
|
|
86244
|
+
if (found)
|
|
86245
|
+
return;
|
|
86246
|
+
if (n.type === "call") {
|
|
86247
|
+
const fn = n.childForFieldName("function");
|
|
86248
|
+
if (fn?.type === "attribute") {
|
|
86249
|
+
let receiver = fn.childForFieldName("object");
|
|
86250
|
+
while (receiver?.type === "attribute") {
|
|
86251
|
+
receiver = receiver.childForFieldName("object");
|
|
86252
|
+
}
|
|
86253
|
+
if (receiver?.type === "identifier") {
|
|
86254
|
+
const id = receiver.text;
|
|
86255
|
+
if (id === "boto3" || id === "botocore" || id === "aiobotocore" || awsVarNames.has(id)) {
|
|
86256
|
+
found = true;
|
|
86257
|
+
return;
|
|
86258
|
+
}
|
|
86259
|
+
}
|
|
86260
|
+
}
|
|
86261
|
+
}
|
|
86262
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
86263
|
+
const child = n.child(i);
|
|
86264
|
+
if (child)
|
|
86265
|
+
walk(child);
|
|
86266
|
+
}
|
|
86267
|
+
}
|
|
86268
|
+
walk(body);
|
|
86269
|
+
return found;
|
|
86270
|
+
}
|
|
85391
86271
|
var pythonBoto3ClientErrorVisitor;
|
|
85392
86272
|
var init_boto3_client_error = __esm({
|
|
85393
86273
|
"packages/analyzer/dist/rules/code-quality/visitors/python/boto3-client-error.js"() {
|
|
@@ -85425,6 +86305,9 @@ var init_boto3_client_error = __esm({
|
|
|
85425
86305
|
});
|
|
85426
86306
|
if (!hasBareOrGeneric)
|
|
85427
86307
|
return null;
|
|
86308
|
+
const awsVarNames = findAwsClientVarNames(node.tree.rootNode);
|
|
86309
|
+
if (!bodyHasAwsCall(body, awsVarNames))
|
|
86310
|
+
return null;
|
|
85428
86311
|
return makeViolation(this.ruleKey, node, filePath, "medium", "Uncaught botocore ClientError", "boto3/botocore calls inside try block but `ClientError` is not explicitly caught \u2014 generic `except` hides AWS API errors.", sourceCode, "Add explicit `except botocore.exceptions.ClientError as e:` to handle AWS API errors.");
|
|
85429
86312
|
}
|
|
85430
86313
|
};
|
|
@@ -86289,7 +87172,6 @@ var init_python7 = __esm({
|
|
|
86289
87172
|
init_commented_out_code2();
|
|
86290
87173
|
init_abstract_class_without_abstract_method();
|
|
86291
87174
|
init_and_or_ternary();
|
|
86292
|
-
init_any_type_hint();
|
|
86293
87175
|
init_assert_in_production();
|
|
86294
87176
|
init_async_long_sleep();
|
|
86295
87177
|
init_async_unused_async();
|
|
@@ -86544,7 +87426,6 @@ var init_python7 = __esm({
|
|
|
86544
87426
|
pythonCommentedOutCodeVisitor,
|
|
86545
87427
|
pythonAbstractClassWithoutAbstractMethodVisitor,
|
|
86546
87428
|
pythonAndOrTernaryVisitor,
|
|
86547
|
-
pythonAnyTypeHintVisitor,
|
|
86548
87429
|
pythonAssertInProductionVisitor,
|
|
86549
87430
|
pythonAsyncLongSleepVisitor,
|
|
86550
87431
|
pythonAsyncUnusedAsyncVisitor,
|
|
@@ -88925,6 +89806,22 @@ var init_incorrect_dict_iterator = __esm({
|
|
|
88925
89806
|
});
|
|
88926
89807
|
|
|
88927
89808
|
// packages/analyzer/dist/rules/performance/visitors/python/try-except-in-loop.js
|
|
89809
|
+
function isTypedSkipOnlyHandler(exceptClause) {
|
|
89810
|
+
const block = exceptClause.namedChildren.find((c2) => c2.type === "block");
|
|
89811
|
+
if (!block)
|
|
89812
|
+
return false;
|
|
89813
|
+
const stmts = block.namedChildren.filter((c2) => c2.type !== "comment");
|
|
89814
|
+
if (stmts.length !== 1)
|
|
89815
|
+
return false;
|
|
89816
|
+
if (stmts[0].type !== "continue_statement" && stmts[0].type !== "pass_statement")
|
|
89817
|
+
return false;
|
|
89818
|
+
const exprChildren = exceptClause.namedChildren.filter((c2) => c2.type !== "block" && c2.type !== "comment");
|
|
89819
|
+
if (exprChildren.length === 0)
|
|
89820
|
+
return false;
|
|
89821
|
+
const exceptionType = exprChildren[0];
|
|
89822
|
+
const typeText = exceptionType.type === "as_pattern" ? exceptionType.namedChildren[0]?.text : exceptionType.text;
|
|
89823
|
+
return typeText !== void 0 && typeText !== "Exception" && typeText !== "BaseException";
|
|
89824
|
+
}
|
|
88928
89825
|
var tryExceptInLoopVisitor;
|
|
88929
89826
|
var init_try_except_in_loop = __esm({
|
|
88930
89827
|
"packages/analyzer/dist/rules/performance/visitors/python/try-except-in-loop.js"() {
|
|
@@ -88938,6 +89835,10 @@ var init_try_except_in_loop = __esm({
|
|
|
88938
89835
|
visit(node, filePath, sourceCode) {
|
|
88939
89836
|
if (!isInsidePythonLoop(node))
|
|
88940
89837
|
return null;
|
|
89838
|
+
const exceptClauses = node.namedChildren.filter((c2) => c2.type === "except_clause");
|
|
89839
|
+
if (exceptClauses.length > 0 && exceptClauses.every(isTypedSkipOnlyHandler)) {
|
|
89840
|
+
return null;
|
|
89841
|
+
}
|
|
88941
89842
|
return makeViolation(this.ruleKey, node, filePath, "low", "try/except inside loop", "try/except inside a loop adds overhead per iteration. Move the try/except outside the loop if possible.", sourceCode, "Wrap the entire loop in a try/except, or use a conditional check instead of exception handling.");
|
|
88942
89843
|
}
|
|
88943
89844
|
};
|
|
@@ -89311,6 +90212,9 @@ var init_catch_without_error_type = __esm({
|
|
|
89311
90212
|
const hasTypeAnnotation = node.childForFieldName("type") !== null;
|
|
89312
90213
|
if (hasTypeAnnotation)
|
|
89313
90214
|
return null;
|
|
90215
|
+
const stmts = body.namedChildren.filter((c2) => c2.type !== "comment");
|
|
90216
|
+
if (stmts.length <= 1)
|
|
90217
|
+
return null;
|
|
89314
90218
|
return makeViolation(this.ruleKey, node, filePath, "medium", "Catch without error type discrimination", "Catch block does not check or narrow the error type. Different error types may need different handling.", sourceCode, "Use instanceof checks or type guards in the catch block to handle specific error types.");
|
|
89315
90219
|
}
|
|
89316
90220
|
};
|
|
@@ -89378,6 +90282,28 @@ function isInsidePromiseConstructor(node) {
|
|
|
89378
90282
|
}
|
|
89379
90283
|
return false;
|
|
89380
90284
|
}
|
|
90285
|
+
function looksLikeAwsLambda(sourceCode) {
|
|
90286
|
+
if (/\baws-lambda\b/.test(sourceCode))
|
|
90287
|
+
return true;
|
|
90288
|
+
if (/\bAPIGatewayProxy(Event|Result|Handler)/.test(sourceCode))
|
|
90289
|
+
return true;
|
|
90290
|
+
if (/\bLambda(Event|Result|Context|Handler)\b/.test(sourceCode))
|
|
90291
|
+
return true;
|
|
90292
|
+
if (/export\s+const\s+handler\s*[:=]\s*async\s*\(/.test(sourceCode))
|
|
90293
|
+
return true;
|
|
90294
|
+
return false;
|
|
90295
|
+
}
|
|
90296
|
+
function looksLikeAwsCdkScript(sourceCode) {
|
|
90297
|
+
if (/\baws-cdk-lib\b/.test(sourceCode))
|
|
90298
|
+
return true;
|
|
90299
|
+
if (/@aws-cdk\//.test(sourceCode))
|
|
90300
|
+
return true;
|
|
90301
|
+
if (/\bnew\s+cdk\.(App|Stack)\b/.test(sourceCode))
|
|
90302
|
+
return true;
|
|
90303
|
+
if (/\bapp\.synth\(\)/.test(sourceCode))
|
|
90304
|
+
return true;
|
|
90305
|
+
return false;
|
|
90306
|
+
}
|
|
89381
90307
|
var init_helpers7 = __esm({
|
|
89382
90308
|
"packages/analyzer/dist/rules/reliability/visitors/javascript/_helpers.js"() {
|
|
89383
90309
|
"use strict";
|
|
@@ -89664,7 +90590,11 @@ var init_unchecked_array_access = __esm({
|
|
|
89664
90590
|
init_helpers7();
|
|
89665
90591
|
uncheckedArrayAccessVisitor = {
|
|
89666
90592
|
ruleKey: "reliability/deterministic/unchecked-array-access",
|
|
89667
|
-
|
|
90593
|
+
// TS/TSX only. The rule is meaningful when the project opts into
|
|
90594
|
+
// `noUncheckedIndexedAccess`, which only exists in TypeScript. Plain
|
|
90595
|
+
// JS / JSX has no static type system to opt into - every index access
|
|
90596
|
+
// is implicitly `T | undefined` and flagging it is pure noise.
|
|
90597
|
+
languages: ["typescript", "tsx"],
|
|
89668
90598
|
nodeTypes: ["subscript_expression"],
|
|
89669
90599
|
visit(node, filePath, sourceCode) {
|
|
89670
90600
|
const object = node.childForFieldName("object");
|
|
@@ -89960,6 +90890,7 @@ var init_uncaught_exception_no_handler = __esm({
|
|
|
89960
90890
|
"packages/analyzer/dist/rules/reliability/visitors/javascript/uncaught-exception-no-handler.js"() {
|
|
89961
90891
|
"use strict";
|
|
89962
90892
|
init_types();
|
|
90893
|
+
init_helpers7();
|
|
89963
90894
|
uncaughtExceptionNoHandlerVisitor = {
|
|
89964
90895
|
ruleKey: "reliability/deterministic/uncaught-exception-no-handler",
|
|
89965
90896
|
languages: ["typescript", "tsx", "javascript"],
|
|
@@ -89972,6 +90903,10 @@ var init_uncaught_exception_no_handler = __esm({
|
|
|
89972
90903
|
if (!lowerPath.includes("index.") && !lowerPath.includes("main.") && !lowerPath.includes("server.") && !lowerPath.includes("app.") && !lowerPath.endsWith("/worker.ts") && !lowerPath.endsWith("/worker.js") && !lowerPath.includes("bin/")) {
|
|
89973
90904
|
return null;
|
|
89974
90905
|
}
|
|
90906
|
+
if (looksLikeAwsLambda(sourceCode))
|
|
90907
|
+
return null;
|
|
90908
|
+
if (looksLikeAwsCdkScript(sourceCode))
|
|
90909
|
+
return null;
|
|
89975
90910
|
const text = sourceCode.replace(/\/\/.*$/gm, "");
|
|
89976
90911
|
if (text.includes("'uncaughtException'") || text.includes('"uncaughtException"') || text.includes("`uncaughtException`")) {
|
|
89977
90912
|
return null;
|
|
@@ -90024,6 +90959,7 @@ var init_unhandled_rejection_no_handler = __esm({
|
|
|
90024
90959
|
"packages/analyzer/dist/rules/reliability/visitors/javascript/unhandled-rejection-no-handler.js"() {
|
|
90025
90960
|
"use strict";
|
|
90026
90961
|
init_types();
|
|
90962
|
+
init_helpers7();
|
|
90027
90963
|
unhandledRejectionNoHandlerVisitor = {
|
|
90028
90964
|
ruleKey: "reliability/deterministic/unhandled-rejection-no-handler",
|
|
90029
90965
|
languages: ["typescript", "tsx", "javascript"],
|
|
@@ -90036,6 +90972,10 @@ var init_unhandled_rejection_no_handler = __esm({
|
|
|
90036
90972
|
if (!lowerPath.includes("index.") && !lowerPath.includes("main.") && !lowerPath.includes("server.") && !lowerPath.includes("app.") && !lowerPath.endsWith("/worker.ts") && !lowerPath.endsWith("/worker.js") && !lowerPath.includes("bin/")) {
|
|
90037
90973
|
return null;
|
|
90038
90974
|
}
|
|
90975
|
+
if (looksLikeAwsLambda(sourceCode))
|
|
90976
|
+
return null;
|
|
90977
|
+
if (looksLikeAwsCdkScript(sourceCode))
|
|
90978
|
+
return null;
|
|
90039
90979
|
const text = sourceCode.replace(/\/\/.*$/gm, "");
|
|
90040
90980
|
if (text.includes("'unhandledRejection'") || text.includes('"unhandledRejection"') || text.includes("`unhandledRejection`")) {
|
|
90041
90981
|
return null;
|
|
@@ -91352,6 +92292,21 @@ var init_duplicate_import4 = __esm({
|
|
|
91352
92292
|
});
|
|
91353
92293
|
|
|
91354
92294
|
// packages/analyzer/dist/rules/architecture/visitors/python/declarations-in-global-scope.js
|
|
92295
|
+
function isTypingConstruct(node) {
|
|
92296
|
+
if (node.type !== "subscript")
|
|
92297
|
+
return false;
|
|
92298
|
+
const value = node.childForFieldName("value");
|
|
92299
|
+
if (!value)
|
|
92300
|
+
return false;
|
|
92301
|
+
if (value.type === "identifier")
|
|
92302
|
+
return TYPING_NAMES.has(value.text);
|
|
92303
|
+
if (value.type === "attribute") {
|
|
92304
|
+
const attr = value.childForFieldName("attribute");
|
|
92305
|
+
if (attr && TYPING_NAMES.has(attr.text))
|
|
92306
|
+
return true;
|
|
92307
|
+
}
|
|
92308
|
+
return false;
|
|
92309
|
+
}
|
|
91355
92310
|
function rootIsCall(node) {
|
|
91356
92311
|
if (node.type === "call")
|
|
91357
92312
|
return true;
|
|
@@ -91367,11 +92322,46 @@ function rootIsCall(node) {
|
|
|
91367
92322
|
}
|
|
91368
92323
|
return false;
|
|
91369
92324
|
}
|
|
91370
|
-
var pythonDeclarationsInGlobalScopeVisitor;
|
|
92325
|
+
var TYPING_NAMES, pythonDeclarationsInGlobalScopeVisitor;
|
|
91371
92326
|
var init_declarations_in_global_scope2 = __esm({
|
|
91372
92327
|
"packages/analyzer/dist/rules/architecture/visitors/python/declarations-in-global-scope.js"() {
|
|
91373
92328
|
"use strict";
|
|
91374
92329
|
init_types();
|
|
92330
|
+
TYPING_NAMES = /* @__PURE__ */ new Set([
|
|
92331
|
+
"Callable",
|
|
92332
|
+
"Optional",
|
|
92333
|
+
"Union",
|
|
92334
|
+
"Literal",
|
|
92335
|
+
"Annotated",
|
|
92336
|
+
"Final",
|
|
92337
|
+
"List",
|
|
92338
|
+
"Tuple",
|
|
92339
|
+
"Dict",
|
|
92340
|
+
"Set",
|
|
92341
|
+
"FrozenSet",
|
|
92342
|
+
"Type",
|
|
92343
|
+
"ClassVar",
|
|
92344
|
+
"Sequence",
|
|
92345
|
+
"Mapping",
|
|
92346
|
+
"MutableMapping",
|
|
92347
|
+
"Iterable",
|
|
92348
|
+
"Iterator",
|
|
92349
|
+
"Generator",
|
|
92350
|
+
"Coroutine",
|
|
92351
|
+
"Awaitable",
|
|
92352
|
+
"AsyncIterable",
|
|
92353
|
+
"AsyncIterator",
|
|
92354
|
+
"Protocol",
|
|
92355
|
+
"TypedDict",
|
|
92356
|
+
"NamedTuple",
|
|
92357
|
+
// Built-in generics (PEP 585)
|
|
92358
|
+
"list",
|
|
92359
|
+
"tuple",
|
|
92360
|
+
"dict",
|
|
92361
|
+
"set",
|
|
92362
|
+
"frozenset",
|
|
92363
|
+
"type"
|
|
92364
|
+
]);
|
|
91375
92365
|
pythonDeclarationsInGlobalScopeVisitor = {
|
|
91376
92366
|
ruleKey: "architecture/deterministic/declarations-in-global-scope",
|
|
91377
92367
|
languages: ["python"],
|
|
@@ -91433,6 +92423,8 @@ var init_declarations_in_global_scope2 = __esm({
|
|
|
91433
92423
|
return null;
|
|
91434
92424
|
if (right?.type === "attribute" && rootIsCall(right))
|
|
91435
92425
|
return null;
|
|
92426
|
+
if (right?.type === "subscript" && isTypingConstruct(right))
|
|
92427
|
+
return null;
|
|
91436
92428
|
return makeViolation(this.ruleKey, node, filePath, "medium", "Mutable variable in global scope", `Module-level mutable variable '${name}' creates shared state that is hard to test.`, sourceCode, "Move into a function, class, or use UPPER_CASE for intended constants.");
|
|
91437
92429
|
}
|
|
91438
92430
|
};
|
|
@@ -91897,13 +92889,47 @@ var init_missing_transaction = __esm({
|
|
|
91897
92889
|
});
|
|
91898
92890
|
|
|
91899
92891
|
// packages/analyzer/dist/rules/database/visitors/javascript/unvalidated-external-data.js
|
|
91900
|
-
|
|
92892
|
+
function hasOrmLikeReceiver(node) {
|
|
92893
|
+
const fn = node.childForFieldName("function");
|
|
92894
|
+
if (fn?.type !== "member_expression")
|
|
92895
|
+
return false;
|
|
92896
|
+
let receiver = fn.childForFieldName("object");
|
|
92897
|
+
while (receiver?.type === "member_expression") {
|
|
92898
|
+
receiver = receiver.childForFieldName("object");
|
|
92899
|
+
}
|
|
92900
|
+
if (receiver?.type !== "identifier")
|
|
92901
|
+
return false;
|
|
92902
|
+
const name = receiver.text.toLowerCase();
|
|
92903
|
+
return ORM_RECEIVER_NAMES2.has(name);
|
|
92904
|
+
}
|
|
92905
|
+
var AMBIGUOUS_ORM_METHODS, ORM_RECEIVER_NAMES2, unvalidatedExternalDataVisitor;
|
|
91901
92906
|
var init_unvalidated_external_data = __esm({
|
|
91902
92907
|
"packages/analyzer/dist/rules/database/visitors/javascript/unvalidated-external-data.js"() {
|
|
91903
92908
|
"use strict";
|
|
91904
92909
|
init_types();
|
|
91905
92910
|
init_helpers9();
|
|
91906
92911
|
init_javascript_helpers();
|
|
92912
|
+
AMBIGUOUS_ORM_METHODS = /* @__PURE__ */ new Set(["add", "delete"]);
|
|
92913
|
+
ORM_RECEIVER_NAMES2 = /* @__PURE__ */ new Set([
|
|
92914
|
+
"session",
|
|
92915
|
+
"db",
|
|
92916
|
+
"conn",
|
|
92917
|
+
"connection",
|
|
92918
|
+
"cursor",
|
|
92919
|
+
"engine",
|
|
92920
|
+
"database",
|
|
92921
|
+
"manager",
|
|
92922
|
+
"repo",
|
|
92923
|
+
"repository",
|
|
92924
|
+
"orm",
|
|
92925
|
+
"em",
|
|
92926
|
+
"tx",
|
|
92927
|
+
"trx",
|
|
92928
|
+
"knex",
|
|
92929
|
+
"prisma",
|
|
92930
|
+
"sequelize",
|
|
92931
|
+
"mongoose"
|
|
92932
|
+
]);
|
|
91907
92933
|
unvalidatedExternalDataVisitor = {
|
|
91908
92934
|
ruleKey: "database/deterministic/unvalidated-external-data",
|
|
91909
92935
|
languages: ["typescript", "tsx", "javascript"],
|
|
@@ -91913,6 +92939,8 @@ var init_unvalidated_external_data = __esm({
|
|
|
91913
92939
|
const methodName = getMethodName(node);
|
|
91914
92940
|
if (!ORM_WRITE_METHODS2.has(methodName) && !SQL_WRITE_METHODS.has(methodName))
|
|
91915
92941
|
return null;
|
|
92942
|
+
if (AMBIGUOUS_ORM_METHODS.has(methodName) && !hasOrmLikeReceiver(node))
|
|
92943
|
+
return null;
|
|
91916
92944
|
const args = node.childForFieldName("arguments");
|
|
91917
92945
|
if (!args)
|
|
91918
92946
|
return null;
|
|
@@ -92298,6 +93326,9 @@ var init_missing_migration2 = __esm({
|
|
|
92298
93326
|
if (!/alter\s+table|create\s+table|drop\s+table|create\s+index|drop\s+index/.test(sqlText)) {
|
|
92299
93327
|
return null;
|
|
92300
93328
|
}
|
|
93329
|
+
if (/if\s+(not\s+)?exists/.test(sqlText)) {
|
|
93330
|
+
return null;
|
|
93331
|
+
}
|
|
92301
93332
|
return makeViolation(this.ruleKey, node, filePath, "high", "Schema change outside migration file", `DDL statement found outside a migration file. Schema changes should be tracked in migrations.`, sourceCode, "Move this schema change into a versioned migration file.");
|
|
92302
93333
|
}
|
|
92303
93334
|
};
|
|
@@ -92305,6 +93336,122 @@ var init_missing_migration2 = __esm({
|
|
|
92305
93336
|
});
|
|
92306
93337
|
|
|
92307
93338
|
// packages/analyzer/dist/rules/database/visitors/python/connection-not-released.js
|
|
93339
|
+
function escapeRegExp2(s) {
|
|
93340
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
93341
|
+
}
|
|
93342
|
+
function isFactoryReturn(node) {
|
|
93343
|
+
let current = node.parent;
|
|
93344
|
+
while (current) {
|
|
93345
|
+
if (current.type === "return_statement")
|
|
93346
|
+
return true;
|
|
93347
|
+
if (current.type === "expression_statement")
|
|
93348
|
+
return false;
|
|
93349
|
+
if (current.type === "function_definition")
|
|
93350
|
+
return false;
|
|
93351
|
+
current = current.parent;
|
|
93352
|
+
}
|
|
93353
|
+
return false;
|
|
93354
|
+
}
|
|
93355
|
+
function isModuleLevelCache(node) {
|
|
93356
|
+
let assign = node.parent;
|
|
93357
|
+
while (assign) {
|
|
93358
|
+
if (assign.type === "assignment")
|
|
93359
|
+
break;
|
|
93360
|
+
if (assign.type === "expression_statement")
|
|
93361
|
+
return false;
|
|
93362
|
+
if (assign.type === "function_definition")
|
|
93363
|
+
return false;
|
|
93364
|
+
if (assign.type === "class_definition")
|
|
93365
|
+
return false;
|
|
93366
|
+
assign = assign.parent;
|
|
93367
|
+
}
|
|
93368
|
+
if (!assign)
|
|
93369
|
+
return false;
|
|
93370
|
+
const stmt = assign.parent;
|
|
93371
|
+
if (!(stmt?.type === "expression_statement" && stmt.parent?.type === "module"))
|
|
93372
|
+
return false;
|
|
93373
|
+
const lhs = assign.childForFieldName("left");
|
|
93374
|
+
const nameNode = lhs?.type === "identifier" ? lhs : null;
|
|
93375
|
+
if (!nameNode)
|
|
93376
|
+
return false;
|
|
93377
|
+
const name = nameNode.text;
|
|
93378
|
+
return /^[A-Z_][A-Z0-9_]*$/.test(name) || name.startsWith("_");
|
|
93379
|
+
}
|
|
93380
|
+
function isInstanceAttributeAssignmentInPrivateClass(node) {
|
|
93381
|
+
let assign = node.parent;
|
|
93382
|
+
while (assign) {
|
|
93383
|
+
if (assign.type === "assignment")
|
|
93384
|
+
break;
|
|
93385
|
+
if (assign.type === "expression_statement")
|
|
93386
|
+
return false;
|
|
93387
|
+
if (assign.type === "function_definition")
|
|
93388
|
+
return false;
|
|
93389
|
+
assign = assign.parent;
|
|
93390
|
+
}
|
|
93391
|
+
if (!assign)
|
|
93392
|
+
return false;
|
|
93393
|
+
const lhs = assign.childForFieldName("left");
|
|
93394
|
+
if (lhs?.type !== "attribute")
|
|
93395
|
+
return false;
|
|
93396
|
+
const obj = lhs.childForFieldName("object");
|
|
93397
|
+
if (!(obj?.type === "identifier" && (obj.text === "self" || obj.text === "cls")))
|
|
93398
|
+
return false;
|
|
93399
|
+
let cls = assign.parent;
|
|
93400
|
+
while (cls) {
|
|
93401
|
+
if (cls.type === "class_definition") {
|
|
93402
|
+
const name = cls.childForFieldName("name");
|
|
93403
|
+
return name?.type === "identifier" && name.text.startsWith("_");
|
|
93404
|
+
}
|
|
93405
|
+
cls = cls.parent;
|
|
93406
|
+
}
|
|
93407
|
+
return false;
|
|
93408
|
+
}
|
|
93409
|
+
function isClosedInFinally(node) {
|
|
93410
|
+
let assignmentParent = node.parent;
|
|
93411
|
+
while (assignmentParent) {
|
|
93412
|
+
if (assignmentParent.type === "assignment")
|
|
93413
|
+
break;
|
|
93414
|
+
if (assignmentParent.type === "expression_statement")
|
|
93415
|
+
return false;
|
|
93416
|
+
if (assignmentParent.type === "function_definition")
|
|
93417
|
+
return false;
|
|
93418
|
+
assignmentParent = assignmentParent.parent;
|
|
93419
|
+
}
|
|
93420
|
+
if (!assignmentParent)
|
|
93421
|
+
return false;
|
|
93422
|
+
const lhs = assignmentParent.childForFieldName("left");
|
|
93423
|
+
if (!lhs || lhs.type !== "identifier")
|
|
93424
|
+
return false;
|
|
93425
|
+
const varName = lhs.text;
|
|
93426
|
+
let func = assignmentParent.parent;
|
|
93427
|
+
while (func) {
|
|
93428
|
+
if (func.type === "function_definition")
|
|
93429
|
+
break;
|
|
93430
|
+
func = func.parent;
|
|
93431
|
+
}
|
|
93432
|
+
if (!func)
|
|
93433
|
+
return false;
|
|
93434
|
+
const body = func.childForFieldName("body");
|
|
93435
|
+
if (!body)
|
|
93436
|
+
return false;
|
|
93437
|
+
const closePattern2 = new RegExp(`\\b${escapeRegExp2(varName)}\\.(?:close|dispose|release)\\s*\\(`);
|
|
93438
|
+
let found = false;
|
|
93439
|
+
function walk(n) {
|
|
93440
|
+
if (found)
|
|
93441
|
+
return;
|
|
93442
|
+
if (n.type === "finally_clause" && closePattern2.test(n.text)) {
|
|
93443
|
+
found = true;
|
|
93444
|
+
return;
|
|
93445
|
+
}
|
|
93446
|
+
for (let i = 0; i < n.childCount; i++) {
|
|
93447
|
+
const ch = n.child(i);
|
|
93448
|
+
if (ch)
|
|
93449
|
+
walk(ch);
|
|
93450
|
+
}
|
|
93451
|
+
}
|
|
93452
|
+
walk(body);
|
|
93453
|
+
return found;
|
|
93454
|
+
}
|
|
92308
93455
|
var pythonConnectionNotReleasedVisitor;
|
|
92309
93456
|
var init_connection_not_released2 = __esm({
|
|
92310
93457
|
"packages/analyzer/dist/rules/database/visitors/python/connection-not-released.js"() {
|
|
@@ -92352,6 +93499,14 @@ var init_connection_not_released2 = __esm({
|
|
|
92352
93499
|
break;
|
|
92353
93500
|
current = current.parent;
|
|
92354
93501
|
}
|
|
93502
|
+
if (isFactoryReturn(node))
|
|
93503
|
+
return null;
|
|
93504
|
+
if (isClosedInFinally(node))
|
|
93505
|
+
return null;
|
|
93506
|
+
if (isModuleLevelCache(node))
|
|
93507
|
+
return null;
|
|
93508
|
+
if (isInstanceAttributeAssignmentInPrivateClass(node))
|
|
93509
|
+
return null;
|
|
92355
93510
|
return makeViolation(this.ruleKey, node, filePath, "high", "Database connection not released", `${methodName}() acquires a connection but it may not be released on error. Use a context manager (with statement) or try/finally to guarantee the connection is released.`, sourceCode, "Use `with connection:` or a try/finally block to ensure the connection is always released.");
|
|
92356
93511
|
}
|
|
92357
93512
|
};
|
|
@@ -92435,6 +93590,23 @@ var init_orm_lazy_load_in_loop2 = __esm({
|
|
|
92435
93590
|
});
|
|
92436
93591
|
|
|
92437
93592
|
// packages/analyzer/dist/rules/database/visitors/python/missing-transaction.js
|
|
93593
|
+
function hasCursorLikeParam(params) {
|
|
93594
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
93595
|
+
const p2 = params.namedChild(i);
|
|
93596
|
+
if (!p2)
|
|
93597
|
+
continue;
|
|
93598
|
+
let nameNode = null;
|
|
93599
|
+
if (p2.type === "identifier") {
|
|
93600
|
+
nameNode = p2;
|
|
93601
|
+
} else if (p2.type === "typed_parameter" || p2.type === "default_parameter" || p2.type === "typed_default_parameter") {
|
|
93602
|
+
nameNode = p2.childForFieldName("name") ?? p2.namedChild(0);
|
|
93603
|
+
}
|
|
93604
|
+
if (nameNode && nameNode.type === "identifier" && CURSOR_PARAM_NAMES.has(nameNode.text)) {
|
|
93605
|
+
return true;
|
|
93606
|
+
}
|
|
93607
|
+
}
|
|
93608
|
+
return false;
|
|
93609
|
+
}
|
|
92438
93610
|
function isOrmWriteCall(n) {
|
|
92439
93611
|
const name = getPythonMethodName(n);
|
|
92440
93612
|
if (name === "execute" || name === "executemany") {
|
|
@@ -92442,7 +93614,7 @@ function isOrmWriteCall(n) {
|
|
|
92442
93614
|
const firstArg = args?.namedChildren[0];
|
|
92443
93615
|
if (firstArg?.type === "string") {
|
|
92444
93616
|
const sql = firstArg.text.toLowerCase();
|
|
92445
|
-
if (
|
|
93617
|
+
if (/\b(insert|update|delete|alter|create|drop)\b/.test(sql))
|
|
92446
93618
|
return true;
|
|
92447
93619
|
}
|
|
92448
93620
|
return false;
|
|
@@ -92455,26 +93627,26 @@ function isOrmWriteCall(n) {
|
|
|
92455
93627
|
if (fn?.type === "attribute") {
|
|
92456
93628
|
const receiver = fn.childForFieldName("object");
|
|
92457
93629
|
if (receiver?.type === "identifier") {
|
|
92458
|
-
return
|
|
93630
|
+
return ORM_RECEIVER_NAMES3.has(receiver.text);
|
|
92459
93631
|
}
|
|
92460
93632
|
if (receiver?.type === "attribute") {
|
|
92461
93633
|
const attrText = receiver.childForFieldName("attribute")?.text;
|
|
92462
|
-
if (attrText &&
|
|
93634
|
+
if (attrText && ORM_RECEIVER_NAMES3.has(attrText))
|
|
92463
93635
|
return true;
|
|
92464
93636
|
const rootObj = receiver.childForFieldName("object");
|
|
92465
|
-
if (rootObj?.type === "identifier" &&
|
|
93637
|
+
if (rootObj?.type === "identifier" && ORM_RECEIVER_NAMES3.has(rootObj.text))
|
|
92466
93638
|
return true;
|
|
92467
93639
|
}
|
|
92468
93640
|
}
|
|
92469
93641
|
return false;
|
|
92470
93642
|
}
|
|
92471
|
-
var
|
|
93643
|
+
var ORM_RECEIVER_NAMES3, UNAMBIGUOUS_DB_WRITES, CURSOR_PARAM_NAMES, pythonMissingTransactionVisitor;
|
|
92472
93644
|
var init_missing_transaction2 = __esm({
|
|
92473
93645
|
"packages/analyzer/dist/rules/database/visitors/python/missing-transaction.js"() {
|
|
92474
93646
|
"use strict";
|
|
92475
93647
|
init_types();
|
|
92476
93648
|
init_helpers10();
|
|
92477
|
-
|
|
93649
|
+
ORM_RECEIVER_NAMES3 = /* @__PURE__ */ new Set([
|
|
92478
93650
|
"session",
|
|
92479
93651
|
"db",
|
|
92480
93652
|
"conn",
|
|
@@ -92494,6 +93666,17 @@ var init_missing_transaction2 = __esm({
|
|
|
92494
93666
|
"bulk_update",
|
|
92495
93667
|
"executemany"
|
|
92496
93668
|
]);
|
|
93669
|
+
CURSOR_PARAM_NAMES = /* @__PURE__ */ new Set([
|
|
93670
|
+
"cur",
|
|
93671
|
+
"cursor",
|
|
93672
|
+
"conn",
|
|
93673
|
+
"connection",
|
|
93674
|
+
"session",
|
|
93675
|
+
"db",
|
|
93676
|
+
"tx",
|
|
93677
|
+
"trans",
|
|
93678
|
+
"transaction"
|
|
93679
|
+
]);
|
|
92497
93680
|
pythonMissingTransactionVisitor = {
|
|
92498
93681
|
ruleKey: "database/deterministic/missing-transaction",
|
|
92499
93682
|
languages: ["python"],
|
|
@@ -92505,8 +93688,18 @@ var init_missing_transaction2 = __esm({
|
|
|
92505
93688
|
if (!body)
|
|
92506
93689
|
return null;
|
|
92507
93690
|
const bodyText = body.text.toLowerCase();
|
|
92508
|
-
if (/transaction|atomic|begin\b/.test(bodyText))
|
|
93691
|
+
if (/transaction|atomic|begin\b|autocommit\s*=\s*false/.test(bodyText))
|
|
92509
93692
|
return null;
|
|
93693
|
+
if (/\bwith\s+[^:]*\bas\s+(conn|connection|cursor|cur|session|engine|tx|trans)\b[^:]*:/.test(bodyText))
|
|
93694
|
+
return null;
|
|
93695
|
+
const funcDef = body.parent;
|
|
93696
|
+
if (funcDef?.type === "function_definition") {
|
|
93697
|
+
const nameNode = funcDef.childForFieldName("name");
|
|
93698
|
+
const params = funcDef.childForFieldName("parameters");
|
|
93699
|
+
if (nameNode?.text.startsWith("_") && params && hasCursorLikeParam(params)) {
|
|
93700
|
+
return null;
|
|
93701
|
+
}
|
|
93702
|
+
}
|
|
92510
93703
|
let writeCount = 0;
|
|
92511
93704
|
let seenSelf = false;
|
|
92512
93705
|
let isSecondOccurrence = false;
|
|
@@ -99283,6 +100476,503 @@ var init_rules_service = __esm({
|
|
|
99283
100476
|
}
|
|
99284
100477
|
});
|
|
99285
100478
|
|
|
100479
|
+
// node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js
|
|
100480
|
+
var require_windows = __commonJS({
|
|
100481
|
+
"node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module) {
|
|
100482
|
+
module.exports = isexe;
|
|
100483
|
+
isexe.sync = sync;
|
|
100484
|
+
var fs17 = __require("fs");
|
|
100485
|
+
function checkPathExt(path22, options) {
|
|
100486
|
+
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
100487
|
+
if (!pathext) {
|
|
100488
|
+
return true;
|
|
100489
|
+
}
|
|
100490
|
+
pathext = pathext.split(";");
|
|
100491
|
+
if (pathext.indexOf("") !== -1) {
|
|
100492
|
+
return true;
|
|
100493
|
+
}
|
|
100494
|
+
for (var i = 0; i < pathext.length; i++) {
|
|
100495
|
+
var p2 = pathext[i].toLowerCase();
|
|
100496
|
+
if (p2 && path22.substr(-p2.length).toLowerCase() === p2) {
|
|
100497
|
+
return true;
|
|
100498
|
+
}
|
|
100499
|
+
}
|
|
100500
|
+
return false;
|
|
100501
|
+
}
|
|
100502
|
+
function checkStat(stat, path22, options) {
|
|
100503
|
+
if (!stat.isSymbolicLink() && !stat.isFile()) {
|
|
100504
|
+
return false;
|
|
100505
|
+
}
|
|
100506
|
+
return checkPathExt(path22, options);
|
|
100507
|
+
}
|
|
100508
|
+
function isexe(path22, options, cb) {
|
|
100509
|
+
fs17.stat(path22, function(er, stat) {
|
|
100510
|
+
cb(er, er ? false : checkStat(stat, path22, options));
|
|
100511
|
+
});
|
|
100512
|
+
}
|
|
100513
|
+
function sync(path22, options) {
|
|
100514
|
+
return checkStat(fs17.statSync(path22), path22, options);
|
|
100515
|
+
}
|
|
100516
|
+
}
|
|
100517
|
+
});
|
|
100518
|
+
|
|
100519
|
+
// node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js
|
|
100520
|
+
var require_mode = __commonJS({
|
|
100521
|
+
"node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module) {
|
|
100522
|
+
module.exports = isexe;
|
|
100523
|
+
isexe.sync = sync;
|
|
100524
|
+
var fs17 = __require("fs");
|
|
100525
|
+
function isexe(path22, options, cb) {
|
|
100526
|
+
fs17.stat(path22, function(er, stat) {
|
|
100527
|
+
cb(er, er ? false : checkStat(stat, options));
|
|
100528
|
+
});
|
|
100529
|
+
}
|
|
100530
|
+
function sync(path22, options) {
|
|
100531
|
+
return checkStat(fs17.statSync(path22), options);
|
|
100532
|
+
}
|
|
100533
|
+
function checkStat(stat, options) {
|
|
100534
|
+
return stat.isFile() && checkMode(stat, options);
|
|
100535
|
+
}
|
|
100536
|
+
function checkMode(stat, options) {
|
|
100537
|
+
var mod = stat.mode;
|
|
100538
|
+
var uid = stat.uid;
|
|
100539
|
+
var gid = stat.gid;
|
|
100540
|
+
var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
|
|
100541
|
+
var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
|
|
100542
|
+
var u2 = parseInt("100", 8);
|
|
100543
|
+
var g = parseInt("010", 8);
|
|
100544
|
+
var o = parseInt("001", 8);
|
|
100545
|
+
var ug = u2 | g;
|
|
100546
|
+
var ret = mod & o || mod & g && gid === myGid || mod & u2 && uid === myUid || mod & ug && myUid === 0;
|
|
100547
|
+
return ret;
|
|
100548
|
+
}
|
|
100549
|
+
}
|
|
100550
|
+
});
|
|
100551
|
+
|
|
100552
|
+
// node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
|
|
100553
|
+
var require_isexe = __commonJS({
|
|
100554
|
+
"node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
|
|
100555
|
+
var fs17 = __require("fs");
|
|
100556
|
+
var core2;
|
|
100557
|
+
if (process.platform === "win32" || global.TESTING_WINDOWS) {
|
|
100558
|
+
core2 = require_windows();
|
|
100559
|
+
} else {
|
|
100560
|
+
core2 = require_mode();
|
|
100561
|
+
}
|
|
100562
|
+
module.exports = isexe;
|
|
100563
|
+
isexe.sync = sync;
|
|
100564
|
+
function isexe(path22, options, cb) {
|
|
100565
|
+
if (typeof options === "function") {
|
|
100566
|
+
cb = options;
|
|
100567
|
+
options = {};
|
|
100568
|
+
}
|
|
100569
|
+
if (!cb) {
|
|
100570
|
+
if (typeof Promise !== "function") {
|
|
100571
|
+
throw new TypeError("callback not provided");
|
|
100572
|
+
}
|
|
100573
|
+
return new Promise(function(resolve8, reject) {
|
|
100574
|
+
isexe(path22, options || {}, function(er, is) {
|
|
100575
|
+
if (er) {
|
|
100576
|
+
reject(er);
|
|
100577
|
+
} else {
|
|
100578
|
+
resolve8(is);
|
|
100579
|
+
}
|
|
100580
|
+
});
|
|
100581
|
+
});
|
|
100582
|
+
}
|
|
100583
|
+
core2(path22, options || {}, function(er, is) {
|
|
100584
|
+
if (er) {
|
|
100585
|
+
if (er.code === "EACCES" || options && options.ignoreErrors) {
|
|
100586
|
+
er = null;
|
|
100587
|
+
is = false;
|
|
100588
|
+
}
|
|
100589
|
+
}
|
|
100590
|
+
cb(er, is);
|
|
100591
|
+
});
|
|
100592
|
+
}
|
|
100593
|
+
function sync(path22, options) {
|
|
100594
|
+
try {
|
|
100595
|
+
return core2.sync(path22, options || {});
|
|
100596
|
+
} catch (er) {
|
|
100597
|
+
if (options && options.ignoreErrors || er.code === "EACCES") {
|
|
100598
|
+
return false;
|
|
100599
|
+
} else {
|
|
100600
|
+
throw er;
|
|
100601
|
+
}
|
|
100602
|
+
}
|
|
100603
|
+
}
|
|
100604
|
+
}
|
|
100605
|
+
});
|
|
100606
|
+
|
|
100607
|
+
// node_modules/.pnpm/which@2.0.2/node_modules/which/which.js
|
|
100608
|
+
var require_which = __commonJS({
|
|
100609
|
+
"node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module) {
|
|
100610
|
+
var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
|
|
100611
|
+
var path22 = __require("path");
|
|
100612
|
+
var COLON = isWindows ? ";" : ":";
|
|
100613
|
+
var isexe = require_isexe();
|
|
100614
|
+
var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
|
|
100615
|
+
var getPathInfo = (cmd, opt) => {
|
|
100616
|
+
const colon = opt.colon || COLON;
|
|
100617
|
+
const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [
|
|
100618
|
+
// windows always checks the cwd first
|
|
100619
|
+
...isWindows ? [process.cwd()] : [],
|
|
100620
|
+
...(opt.path || process.env.PATH || /* istanbul ignore next: very unusual */
|
|
100621
|
+
"").split(colon)
|
|
100622
|
+
];
|
|
100623
|
+
const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
|
|
100624
|
+
const pathExt = isWindows ? pathExtExe.split(colon) : [""];
|
|
100625
|
+
if (isWindows) {
|
|
100626
|
+
if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
|
|
100627
|
+
pathExt.unshift("");
|
|
100628
|
+
}
|
|
100629
|
+
return {
|
|
100630
|
+
pathEnv,
|
|
100631
|
+
pathExt,
|
|
100632
|
+
pathExtExe
|
|
100633
|
+
};
|
|
100634
|
+
};
|
|
100635
|
+
var which = (cmd, opt, cb) => {
|
|
100636
|
+
if (typeof opt === "function") {
|
|
100637
|
+
cb = opt;
|
|
100638
|
+
opt = {};
|
|
100639
|
+
}
|
|
100640
|
+
if (!opt)
|
|
100641
|
+
opt = {};
|
|
100642
|
+
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
|
|
100643
|
+
const found = [];
|
|
100644
|
+
const step = (i) => new Promise((resolve8, reject) => {
|
|
100645
|
+
if (i === pathEnv.length)
|
|
100646
|
+
return opt.all && found.length ? resolve8(found) : reject(getNotFoundError(cmd));
|
|
100647
|
+
const ppRaw = pathEnv[i];
|
|
100648
|
+
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
100649
|
+
const pCmd = path22.join(pathPart, cmd);
|
|
100650
|
+
const p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
100651
|
+
resolve8(subStep(p2, i, 0));
|
|
100652
|
+
});
|
|
100653
|
+
const subStep = (p2, i, ii) => new Promise((resolve8, reject) => {
|
|
100654
|
+
if (ii === pathExt.length)
|
|
100655
|
+
return resolve8(step(i + 1));
|
|
100656
|
+
const ext2 = pathExt[ii];
|
|
100657
|
+
isexe(p2 + ext2, { pathExt: pathExtExe }, (er, is) => {
|
|
100658
|
+
if (!er && is) {
|
|
100659
|
+
if (opt.all)
|
|
100660
|
+
found.push(p2 + ext2);
|
|
100661
|
+
else
|
|
100662
|
+
return resolve8(p2 + ext2);
|
|
100663
|
+
}
|
|
100664
|
+
return resolve8(subStep(p2, i, ii + 1));
|
|
100665
|
+
});
|
|
100666
|
+
});
|
|
100667
|
+
return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
|
|
100668
|
+
};
|
|
100669
|
+
var whichSync = (cmd, opt) => {
|
|
100670
|
+
opt = opt || {};
|
|
100671
|
+
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
|
|
100672
|
+
const found = [];
|
|
100673
|
+
for (let i = 0; i < pathEnv.length; i++) {
|
|
100674
|
+
const ppRaw = pathEnv[i];
|
|
100675
|
+
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
100676
|
+
const pCmd = path22.join(pathPart, cmd);
|
|
100677
|
+
const p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
100678
|
+
for (let j2 = 0; j2 < pathExt.length; j2++) {
|
|
100679
|
+
const cur = p2 + pathExt[j2];
|
|
100680
|
+
try {
|
|
100681
|
+
const is = isexe.sync(cur, { pathExt: pathExtExe });
|
|
100682
|
+
if (is) {
|
|
100683
|
+
if (opt.all)
|
|
100684
|
+
found.push(cur);
|
|
100685
|
+
else
|
|
100686
|
+
return cur;
|
|
100687
|
+
}
|
|
100688
|
+
} catch (ex) {
|
|
100689
|
+
}
|
|
100690
|
+
}
|
|
100691
|
+
}
|
|
100692
|
+
if (opt.all && found.length)
|
|
100693
|
+
return found;
|
|
100694
|
+
if (opt.nothrow)
|
|
100695
|
+
return null;
|
|
100696
|
+
throw getNotFoundError(cmd);
|
|
100697
|
+
};
|
|
100698
|
+
module.exports = which;
|
|
100699
|
+
which.sync = whichSync;
|
|
100700
|
+
}
|
|
100701
|
+
});
|
|
100702
|
+
|
|
100703
|
+
// node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js
|
|
100704
|
+
var require_path_key = __commonJS({
|
|
100705
|
+
"node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js"(exports, module) {
|
|
100706
|
+
"use strict";
|
|
100707
|
+
var pathKey = (options = {}) => {
|
|
100708
|
+
const environment = options.env || process.env;
|
|
100709
|
+
const platform = options.platform || process.platform;
|
|
100710
|
+
if (platform !== "win32") {
|
|
100711
|
+
return "PATH";
|
|
100712
|
+
}
|
|
100713
|
+
return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
|
|
100714
|
+
};
|
|
100715
|
+
module.exports = pathKey;
|
|
100716
|
+
module.exports.default = pathKey;
|
|
100717
|
+
}
|
|
100718
|
+
});
|
|
100719
|
+
|
|
100720
|
+
// node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
|
|
100721
|
+
var require_resolveCommand = __commonJS({
|
|
100722
|
+
"node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
|
|
100723
|
+
"use strict";
|
|
100724
|
+
var path22 = __require("path");
|
|
100725
|
+
var which = require_which();
|
|
100726
|
+
var getPathKey = require_path_key();
|
|
100727
|
+
function resolveCommandAttempt(parsed, withoutPathExt) {
|
|
100728
|
+
const env = parsed.options.env || process.env;
|
|
100729
|
+
const cwd = process.cwd();
|
|
100730
|
+
const hasCustomCwd = parsed.options.cwd != null;
|
|
100731
|
+
const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
|
|
100732
|
+
if (shouldSwitchCwd) {
|
|
100733
|
+
try {
|
|
100734
|
+
process.chdir(parsed.options.cwd);
|
|
100735
|
+
} catch (err) {
|
|
100736
|
+
}
|
|
100737
|
+
}
|
|
100738
|
+
let resolved;
|
|
100739
|
+
try {
|
|
100740
|
+
resolved = which.sync(parsed.command, {
|
|
100741
|
+
path: env[getPathKey({ env })],
|
|
100742
|
+
pathExt: withoutPathExt ? path22.delimiter : void 0
|
|
100743
|
+
});
|
|
100744
|
+
} catch (e) {
|
|
100745
|
+
} finally {
|
|
100746
|
+
if (shouldSwitchCwd) {
|
|
100747
|
+
process.chdir(cwd);
|
|
100748
|
+
}
|
|
100749
|
+
}
|
|
100750
|
+
if (resolved) {
|
|
100751
|
+
resolved = path22.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
|
|
100752
|
+
}
|
|
100753
|
+
return resolved;
|
|
100754
|
+
}
|
|
100755
|
+
function resolveCommand(parsed) {
|
|
100756
|
+
return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
|
|
100757
|
+
}
|
|
100758
|
+
module.exports = resolveCommand;
|
|
100759
|
+
}
|
|
100760
|
+
});
|
|
100761
|
+
|
|
100762
|
+
// node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js
|
|
100763
|
+
var require_escape = __commonJS({
|
|
100764
|
+
"node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js"(exports, module) {
|
|
100765
|
+
"use strict";
|
|
100766
|
+
var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
|
|
100767
|
+
function escapeCommand(arg) {
|
|
100768
|
+
arg = arg.replace(metaCharsRegExp, "^$1");
|
|
100769
|
+
return arg;
|
|
100770
|
+
}
|
|
100771
|
+
function escapeArgument(arg, doubleEscapeMetaChars) {
|
|
100772
|
+
arg = `${arg}`;
|
|
100773
|
+
arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
|
|
100774
|
+
arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
|
|
100775
|
+
arg = `"${arg}"`;
|
|
100776
|
+
arg = arg.replace(metaCharsRegExp, "^$1");
|
|
100777
|
+
if (doubleEscapeMetaChars) {
|
|
100778
|
+
arg = arg.replace(metaCharsRegExp, "^$1");
|
|
100779
|
+
}
|
|
100780
|
+
return arg;
|
|
100781
|
+
}
|
|
100782
|
+
module.exports.command = escapeCommand;
|
|
100783
|
+
module.exports.argument = escapeArgument;
|
|
100784
|
+
}
|
|
100785
|
+
});
|
|
100786
|
+
|
|
100787
|
+
// node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js
|
|
100788
|
+
var require_shebang_regex = __commonJS({
|
|
100789
|
+
"node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js"(exports, module) {
|
|
100790
|
+
"use strict";
|
|
100791
|
+
module.exports = /^#!(.*)/;
|
|
100792
|
+
}
|
|
100793
|
+
});
|
|
100794
|
+
|
|
100795
|
+
// node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js
|
|
100796
|
+
var require_shebang_command = __commonJS({
|
|
100797
|
+
"node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js"(exports, module) {
|
|
100798
|
+
"use strict";
|
|
100799
|
+
var shebangRegex = require_shebang_regex();
|
|
100800
|
+
module.exports = (string = "") => {
|
|
100801
|
+
const match2 = string.match(shebangRegex);
|
|
100802
|
+
if (!match2) {
|
|
100803
|
+
return null;
|
|
100804
|
+
}
|
|
100805
|
+
const [path22, argument] = match2[0].replace(/#! ?/, "").split(" ");
|
|
100806
|
+
const binary2 = path22.split("/").pop();
|
|
100807
|
+
if (binary2 === "env") {
|
|
100808
|
+
return argument;
|
|
100809
|
+
}
|
|
100810
|
+
return argument ? `${binary2} ${argument}` : binary2;
|
|
100811
|
+
};
|
|
100812
|
+
}
|
|
100813
|
+
});
|
|
100814
|
+
|
|
100815
|
+
// node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
|
|
100816
|
+
var require_readShebang = __commonJS({
|
|
100817
|
+
"node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
|
|
100818
|
+
"use strict";
|
|
100819
|
+
var fs17 = __require("fs");
|
|
100820
|
+
var shebangCommand = require_shebang_command();
|
|
100821
|
+
function readShebang(command) {
|
|
100822
|
+
const size = 150;
|
|
100823
|
+
const buffer = Buffer.alloc(size);
|
|
100824
|
+
let fd;
|
|
100825
|
+
try {
|
|
100826
|
+
fd = fs17.openSync(command, "r");
|
|
100827
|
+
fs17.readSync(fd, buffer, 0, size, 0);
|
|
100828
|
+
fs17.closeSync(fd);
|
|
100829
|
+
} catch (e) {
|
|
100830
|
+
}
|
|
100831
|
+
return shebangCommand(buffer.toString());
|
|
100832
|
+
}
|
|
100833
|
+
module.exports = readShebang;
|
|
100834
|
+
}
|
|
100835
|
+
});
|
|
100836
|
+
|
|
100837
|
+
// node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
|
|
100838
|
+
var require_parse = __commonJS({
|
|
100839
|
+
"node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js"(exports, module) {
|
|
100840
|
+
"use strict";
|
|
100841
|
+
var path22 = __require("path");
|
|
100842
|
+
var resolveCommand = require_resolveCommand();
|
|
100843
|
+
var escape2 = require_escape();
|
|
100844
|
+
var readShebang = require_readShebang();
|
|
100845
|
+
var isWin = process.platform === "win32";
|
|
100846
|
+
var isExecutableRegExp = /\.(?:com|exe)$/i;
|
|
100847
|
+
var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
|
|
100848
|
+
function detectShebang(parsed) {
|
|
100849
|
+
parsed.file = resolveCommand(parsed);
|
|
100850
|
+
const shebang = parsed.file && readShebang(parsed.file);
|
|
100851
|
+
if (shebang) {
|
|
100852
|
+
parsed.args.unshift(parsed.file);
|
|
100853
|
+
parsed.command = shebang;
|
|
100854
|
+
return resolveCommand(parsed);
|
|
100855
|
+
}
|
|
100856
|
+
return parsed.file;
|
|
100857
|
+
}
|
|
100858
|
+
function parseNonShell(parsed) {
|
|
100859
|
+
if (!isWin) {
|
|
100860
|
+
return parsed;
|
|
100861
|
+
}
|
|
100862
|
+
const commandFile = detectShebang(parsed);
|
|
100863
|
+
const needsShell = !isExecutableRegExp.test(commandFile);
|
|
100864
|
+
if (parsed.options.forceShell || needsShell) {
|
|
100865
|
+
const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
|
|
100866
|
+
parsed.command = path22.normalize(parsed.command);
|
|
100867
|
+
parsed.command = escape2.command(parsed.command);
|
|
100868
|
+
parsed.args = parsed.args.map((arg) => escape2.argument(arg, needsDoubleEscapeMetaChars));
|
|
100869
|
+
const shellCommand = [parsed.command].concat(parsed.args).join(" ");
|
|
100870
|
+
parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`];
|
|
100871
|
+
parsed.command = process.env.comspec || "cmd.exe";
|
|
100872
|
+
parsed.options.windowsVerbatimArguments = true;
|
|
100873
|
+
}
|
|
100874
|
+
return parsed;
|
|
100875
|
+
}
|
|
100876
|
+
function parse2(command, args, options) {
|
|
100877
|
+
if (args && !Array.isArray(args)) {
|
|
100878
|
+
options = args;
|
|
100879
|
+
args = null;
|
|
100880
|
+
}
|
|
100881
|
+
args = args ? args.slice(0) : [];
|
|
100882
|
+
options = Object.assign({}, options);
|
|
100883
|
+
const parsed = {
|
|
100884
|
+
command,
|
|
100885
|
+
args,
|
|
100886
|
+
options,
|
|
100887
|
+
file: void 0,
|
|
100888
|
+
original: {
|
|
100889
|
+
command,
|
|
100890
|
+
args
|
|
100891
|
+
}
|
|
100892
|
+
};
|
|
100893
|
+
return options.shell ? parsed : parseNonShell(parsed);
|
|
100894
|
+
}
|
|
100895
|
+
module.exports = parse2;
|
|
100896
|
+
}
|
|
100897
|
+
});
|
|
100898
|
+
|
|
100899
|
+
// node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js
|
|
100900
|
+
var require_enoent = __commonJS({
|
|
100901
|
+
"node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js"(exports, module) {
|
|
100902
|
+
"use strict";
|
|
100903
|
+
var isWin = process.platform === "win32";
|
|
100904
|
+
function notFoundError(original, syscall) {
|
|
100905
|
+
return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
|
|
100906
|
+
code: "ENOENT",
|
|
100907
|
+
errno: "ENOENT",
|
|
100908
|
+
syscall: `${syscall} ${original.command}`,
|
|
100909
|
+
path: original.command,
|
|
100910
|
+
spawnargs: original.args
|
|
100911
|
+
});
|
|
100912
|
+
}
|
|
100913
|
+
function hookChildProcess(cp, parsed) {
|
|
100914
|
+
if (!isWin) {
|
|
100915
|
+
return;
|
|
100916
|
+
}
|
|
100917
|
+
const originalEmit = cp.emit;
|
|
100918
|
+
cp.emit = function(name, arg1) {
|
|
100919
|
+
if (name === "exit") {
|
|
100920
|
+
const err = verifyENOENT(arg1, parsed);
|
|
100921
|
+
if (err) {
|
|
100922
|
+
return originalEmit.call(cp, "error", err);
|
|
100923
|
+
}
|
|
100924
|
+
}
|
|
100925
|
+
return originalEmit.apply(cp, arguments);
|
|
100926
|
+
};
|
|
100927
|
+
}
|
|
100928
|
+
function verifyENOENT(status, parsed) {
|
|
100929
|
+
if (isWin && status === 1 && !parsed.file) {
|
|
100930
|
+
return notFoundError(parsed.original, "spawn");
|
|
100931
|
+
}
|
|
100932
|
+
return null;
|
|
100933
|
+
}
|
|
100934
|
+
function verifyENOENTSync(status, parsed) {
|
|
100935
|
+
if (isWin && status === 1 && !parsed.file) {
|
|
100936
|
+
return notFoundError(parsed.original, "spawnSync");
|
|
100937
|
+
}
|
|
100938
|
+
return null;
|
|
100939
|
+
}
|
|
100940
|
+
module.exports = {
|
|
100941
|
+
hookChildProcess,
|
|
100942
|
+
verifyENOENT,
|
|
100943
|
+
verifyENOENTSync,
|
|
100944
|
+
notFoundError
|
|
100945
|
+
};
|
|
100946
|
+
}
|
|
100947
|
+
});
|
|
100948
|
+
|
|
100949
|
+
// node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js
|
|
100950
|
+
var require_cross_spawn = __commonJS({
|
|
100951
|
+
"node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js"(exports, module) {
|
|
100952
|
+
"use strict";
|
|
100953
|
+
var cp = __require("child_process");
|
|
100954
|
+
var parse2 = require_parse();
|
|
100955
|
+
var enoent = require_enoent();
|
|
100956
|
+
function spawn6(command, args, options) {
|
|
100957
|
+
const parsed = parse2(command, args, options);
|
|
100958
|
+
const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
|
|
100959
|
+
enoent.hookChildProcess(spawned, parsed);
|
|
100960
|
+
return spawned;
|
|
100961
|
+
}
|
|
100962
|
+
function spawnSync2(command, args, options) {
|
|
100963
|
+
const parsed = parse2(command, args, options);
|
|
100964
|
+
const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
|
|
100965
|
+
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
|
|
100966
|
+
return result;
|
|
100967
|
+
}
|
|
100968
|
+
module.exports = spawn6;
|
|
100969
|
+
module.exports.spawn = spawn6;
|
|
100970
|
+
module.exports.sync = spawnSync2;
|
|
100971
|
+
module.exports._parse = parse2;
|
|
100972
|
+
module.exports._enoent = enoent;
|
|
100973
|
+
}
|
|
100974
|
+
});
|
|
100975
|
+
|
|
99286
100976
|
// node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
|
|
99287
100977
|
var Node, Queue;
|
|
99288
100978
|
var init_yocto_queue = __esm({
|
|
@@ -102102,15 +103792,15 @@ var init_schemas2 = __esm({
|
|
|
102102
103792
|
});
|
|
102103
103793
|
|
|
102104
103794
|
// packages/core/dist/services/llm/cli-provider.js
|
|
102105
|
-
import {
|
|
102106
|
-
import { mkdirSync as mkdirSync2, writeFileSync } from "node:fs";
|
|
103795
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
102107
103796
|
import { join as join8 } from "node:path";
|
|
102108
103797
|
import { tmpdir } from "node:os";
|
|
102109
103798
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
102110
|
-
var BaseCLIProvider, ClaudeCodeProvider;
|
|
103799
|
+
var import_cross_spawn, BaseCLIProvider, ClaudeCodeProvider;
|
|
102111
103800
|
var init_cli_provider = __esm({
|
|
102112
103801
|
"packages/core/dist/services/llm/cli-provider.js"() {
|
|
102113
103802
|
"use strict";
|
|
103803
|
+
import_cross_spawn = __toESM(require_cross_spawn(), 1);
|
|
102114
103804
|
init_logger();
|
|
102115
103805
|
init_p_limit();
|
|
102116
103806
|
init_analysis_registry();
|
|
@@ -102178,10 +103868,10 @@ var init_cli_provider = __esm({
|
|
|
102178
103868
|
return;
|
|
102179
103869
|
const n = String(++this.callCounter).padStart(2, "0");
|
|
102180
103870
|
const prefix = join8(this.debugDir, `${n}-${label}`);
|
|
102181
|
-
|
|
102182
|
-
|
|
103871
|
+
writeFileSync2(`${prefix}-input.txt`, prompt, "utf-8");
|
|
103872
|
+
writeFileSync2(`${prefix}-output.json`, rawOutput, "utf-8");
|
|
102183
103873
|
if (jsonSchema)
|
|
102184
|
-
|
|
103874
|
+
writeFileSync2(`${prefix}-schema.json`, jsonSchema, "utf-8");
|
|
102185
103875
|
}
|
|
102186
103876
|
/** Strip nesting guard env vars so subprocess doesn't detect parent Claude Code. */
|
|
102187
103877
|
getCleanEnv() {
|
|
@@ -102213,7 +103903,7 @@ var init_cli_provider = __esm({
|
|
|
102213
103903
|
...opts?.extraArgs ?? []
|
|
102214
103904
|
];
|
|
102215
103905
|
return new Promise((resolve8, reject) => {
|
|
102216
|
-
const child =
|
|
103906
|
+
const child = (0, import_cross_spawn.default)(this.binaryName, args, {
|
|
102217
103907
|
env: this.getCleanEnv(),
|
|
102218
103908
|
stdio: ["pipe", "pipe", "pipe"],
|
|
102219
103909
|
...this._repoPath ? { cwd: this._repoPath } : {}
|
|
@@ -103120,7 +104810,38 @@ function splitIntoBatches(tier, rules, content, fileCount, functionCount, filePa
|
|
|
103120
104810
|
}
|
|
103121
104811
|
return batches;
|
|
103122
104812
|
}
|
|
104813
|
+
function translateContextRangeError(err, fileContents) {
|
|
104814
|
+
if (!(err instanceof RangeError) || !err.message.includes("Invalid string length")) {
|
|
104815
|
+
throw err;
|
|
104816
|
+
}
|
|
104817
|
+
const sized = [...fileContents.entries()].map(([filePath, fc]) => ({
|
|
104818
|
+
filePath,
|
|
104819
|
+
sizeKb: Math.round(fc.content.length / 1024),
|
|
104820
|
+
lineCount: fc.lineCount,
|
|
104821
|
+
// long single lines are a strong minified-bundle signal
|
|
104822
|
+
maxLineLength: fc.lineCount > 0 ? Math.round(fc.content.length / fc.lineCount) : 0
|
|
104823
|
+
})).sort((a, b) => b.sizeKb - a.sizeKb).slice(0, 5);
|
|
104824
|
+
const list = sized.map((f) => {
|
|
104825
|
+
const minHint = f.maxLineLength > 5e3 ? " [likely minified]" : "";
|
|
104826
|
+
return ` - ${f.filePath} (${f.sizeKb} KB, ${f.lineCount} lines)${minHint}`;
|
|
104827
|
+
}).join("\n");
|
|
104828
|
+
const suggestions = sized.slice(0, 3).map((f) => ` ${f.filePath}`).join("\n");
|
|
104829
|
+
throw new Error(`LLM context exceeded V8's max string length (~512 MB) while preparing rule batches. This is almost always caused by minified bundles, vendored libraries, or generated files.
|
|
104830
|
+
|
|
104831
|
+
Largest files in scope:
|
|
104832
|
+
${list}
|
|
104833
|
+
|
|
104834
|
+
Add the offending paths to \`.truecourseignore\` at the repo root, e.g.:
|
|
104835
|
+
${suggestions}`);
|
|
104836
|
+
}
|
|
103123
104837
|
function estimateContext(rules, fileAnalyses, fileContents, options) {
|
|
104838
|
+
try {
|
|
104839
|
+
return estimateContextInner(rules, fileAnalyses, fileContents, options);
|
|
104840
|
+
} catch (err) {
|
|
104841
|
+
translateContextRangeError(err, fileContents);
|
|
104842
|
+
}
|
|
104843
|
+
}
|
|
104844
|
+
function estimateContextInner(rules, fileAnalyses, fileContents, options) {
|
|
103124
104845
|
const grouped = groupRulesByContext(rules);
|
|
103125
104846
|
const tiers = [];
|
|
103126
104847
|
const useFilePaths = options?.useFilePaths ?? false;
|
|
@@ -103179,6 +104900,13 @@ function estimateContext(rules, fileAnalyses, fileContents, options) {
|
|
|
103179
104900
|
};
|
|
103180
104901
|
}
|
|
103181
104902
|
function routeContext(rules, fileAnalyses, fileContents) {
|
|
104903
|
+
try {
|
|
104904
|
+
return routeContextInner(rules, fileAnalyses, fileContents);
|
|
104905
|
+
} catch (err) {
|
|
104906
|
+
translateContextRangeError(err, fileContents);
|
|
104907
|
+
}
|
|
104908
|
+
}
|
|
104909
|
+
function routeContextInner(rules, fileAnalyses, fileContents) {
|
|
103182
104910
|
const grouped = groupRulesByContext(rules);
|
|
103183
104911
|
const batches = [];
|
|
103184
104912
|
const faByPath = /* @__PURE__ */ new Map();
|
|
@@ -103794,24 +105522,39 @@ async function runViolationPipeline(input) {
|
|
|
103794
105522
|
tracker?.start(stepKey);
|
|
103795
105523
|
if (domain === "architecture") {
|
|
103796
105524
|
tracker?.detail(stepKey, "Service checks...");
|
|
105525
|
+
await new Promise((r) => setImmediate(r));
|
|
105526
|
+
throwIfAborted(signal);
|
|
103797
105527
|
serviceViolationResults.push(...runDeterministicServiceChecks(result, domainRules));
|
|
103798
105528
|
tracker?.detail(stepKey, "Module checks...");
|
|
105529
|
+
await new Promise((r) => setImmediate(r));
|
|
105530
|
+
throwIfAborted(signal);
|
|
103799
105531
|
moduleViolationResults.push(...runDeterministicModuleChecks(result, domainRules));
|
|
103800
105532
|
tracker?.detail(stepKey, "Method checks...");
|
|
105533
|
+
await new Promise((r) => setImmediate(r));
|
|
105534
|
+
throwIfAborted(signal);
|
|
103801
105535
|
methodViolationResults.push(...runDeterministicMethodChecks(result, domainRules));
|
|
103802
105536
|
tracker?.detail(stepKey, "Deterministic checks done");
|
|
103803
105537
|
}
|
|
103804
105538
|
}
|
|
103805
105539
|
const allCodeViolations = [];
|
|
103806
105540
|
if (enabledCodeRules.length > 0 && filesToScan.length > 0) {
|
|
105541
|
+
const activeCodeDomains = [];
|
|
103807
105542
|
for (const domain of DOMAIN_ORDER) {
|
|
103808
105543
|
if (domain === "architecture")
|
|
103809
105544
|
continue;
|
|
103810
105545
|
const domainRules = enabledDeterministic.filter((r) => (r.domain ?? "").startsWith(domain));
|
|
103811
|
-
if (domainRules.length > 0)
|
|
105546
|
+
if (domainRules.length > 0) {
|
|
103812
105547
|
tracker?.start(`${domain}`);
|
|
105548
|
+
activeCodeDomains.push(domain);
|
|
105549
|
+
}
|
|
103813
105550
|
}
|
|
103814
105551
|
await new Promise((r) => setImmediate(r));
|
|
105552
|
+
const totalFiles = filesToScan.length;
|
|
105553
|
+
let processed = 0;
|
|
105554
|
+
const SPINNER_YIELD_MS = 25;
|
|
105555
|
+
const DETAIL_UPDATE_MS = 100;
|
|
105556
|
+
let lastYieldMs = Date.now();
|
|
105557
|
+
let lastDetailMs = lastYieldMs;
|
|
103815
105558
|
for (const { filePath, resolve: resolve8 } of filesToScan) {
|
|
103816
105559
|
try {
|
|
103817
105560
|
const lang = detectLanguage(filePath);
|
|
@@ -103827,6 +105570,23 @@ async function runViolationPipeline(input) {
|
|
|
103827
105570
|
allCodeViolations.push(...codeRuleViolations);
|
|
103828
105571
|
} catch {
|
|
103829
105572
|
}
|
|
105573
|
+
processed++;
|
|
105574
|
+
if (signal?.aborted)
|
|
105575
|
+
throw new DOMException("Analysis cancelled", "AbortError");
|
|
105576
|
+
const now2 = Date.now();
|
|
105577
|
+
const isLast = processed === totalFiles;
|
|
105578
|
+
if (isLast || now2 - lastDetailMs >= DETAIL_UPDATE_MS) {
|
|
105579
|
+
const detail = `${processed}/${totalFiles} files`;
|
|
105580
|
+
for (const domain of activeCodeDomains)
|
|
105581
|
+
tracker?.detail(domain, detail);
|
|
105582
|
+
lastDetailMs = now2;
|
|
105583
|
+
}
|
|
105584
|
+
if (isLast || now2 - lastYieldMs >= SPINNER_YIELD_MS) {
|
|
105585
|
+
await new Promise((r) => setImmediate(r));
|
|
105586
|
+
lastYieldMs = Date.now();
|
|
105587
|
+
if (signal?.aborted)
|
|
105588
|
+
throw new DOMException("Analysis cancelled", "AbortError");
|
|
105589
|
+
}
|
|
103830
105590
|
}
|
|
103831
105591
|
}
|
|
103832
105592
|
log.info(`[Pipeline] Code scan: ${allCodeViolations.length} violations from ${filesToScan.length} files (${enabledCodeRules.length} det rules, ${enabledLlmCodeRules.length} LLM rules)`);
|
|
@@ -105212,7 +106972,6 @@ async function runAdd(options = {}) {
|
|
|
105212
106972
|
|
|
105213
106973
|
// tools/cli/src/commands/analyze.ts
|
|
105214
106974
|
init_dist4();
|
|
105215
|
-
import { execSync } from "node:child_process";
|
|
105216
106975
|
import path16 from "node:path";
|
|
105217
106976
|
|
|
105218
106977
|
// packages/core/dist/commands/analyze-in-process.js
|
|
@@ -105235,6 +106994,19 @@ init_registry();
|
|
|
105235
106994
|
init_project_config();
|
|
105236
106995
|
init_git();
|
|
105237
106996
|
init_logger();
|
|
106997
|
+
|
|
106998
|
+
// packages/core/dist/lib/cli-binary.js
|
|
106999
|
+
var import_cross_spawn2 = __toESM(require_cross_spawn(), 1);
|
|
107000
|
+
function isCliBinaryAvailable(binary2) {
|
|
107001
|
+
const result = (0, import_cross_spawn2.sync)(binary2, ["--version"], {
|
|
107002
|
+
stdio: "ignore",
|
|
107003
|
+
timeout: 5e3
|
|
107004
|
+
});
|
|
107005
|
+
return result.status === 0;
|
|
107006
|
+
}
|
|
107007
|
+
|
|
107008
|
+
// tools/cli/src/commands/analyze.ts
|
|
107009
|
+
init_config2();
|
|
105238
107010
|
init_helpers();
|
|
105239
107011
|
|
|
105240
107012
|
// tools/cli/src/commands/llm-prompt.ts
|
|
@@ -105322,14 +107094,13 @@ function showFirstRunNotice() {
|
|
|
105322
107094
|
|
|
105323
107095
|
// tools/cli/src/commands/analyze.ts
|
|
105324
107096
|
function ensureClaudeCli() {
|
|
105325
|
-
|
|
105326
|
-
|
|
105327
|
-
|
|
105328
|
-
|
|
105329
|
-
|
|
105330
|
-
|
|
105331
|
-
|
|
105332
|
-
}
|
|
107097
|
+
const binary2 = config.claudeCodeBinary;
|
|
107098
|
+
if (isCliBinaryAvailable(binary2)) return;
|
|
107099
|
+
O2.error(
|
|
107100
|
+
`Claude Code CLI not found (tried \`${binary2}\`). TrueCourse requires the Claude Code binary to run analysis.
|
|
107101
|
+
Install it from https://docs.anthropic.com/en/docs/claude-code, or set CLAUDE_CODE_BINARY to its name or absolute path if it's installed elsewhere.`
|
|
107102
|
+
);
|
|
107103
|
+
process.exit(1);
|
|
105333
107104
|
}
|
|
105334
107105
|
function resolveOrInitProject() {
|
|
105335
107106
|
const repoDir = resolveRepoDir(process.cwd()) ?? process.cwd();
|
|
@@ -105479,7 +107250,16 @@ async function runAnalyze(options = {}) {
|
|
|
105479
107250
|
if (payload.steps) renderSteps(payload.steps);
|
|
105480
107251
|
}, stepDefs);
|
|
105481
107252
|
const abortController = new AbortController();
|
|
105482
|
-
|
|
107253
|
+
let sigintRequested = false;
|
|
107254
|
+
const onSigint = () => {
|
|
107255
|
+
if (sigintRequested) {
|
|
107256
|
+
process.stderr.write("\nForce quit.\n");
|
|
107257
|
+
process.exit(130);
|
|
107258
|
+
}
|
|
107259
|
+
sigintRequested = true;
|
|
107260
|
+
abortController.abort();
|
|
107261
|
+
process.stderr.write("\nCancelling\u2026 (press Ctrl+C again to force quit)\n");
|
|
107262
|
+
};
|
|
105483
107263
|
process.on("SIGINT", onSigint);
|
|
105484
107264
|
try {
|
|
105485
107265
|
const result = await analyzeInProcess(project, {
|
|
@@ -105536,7 +107316,16 @@ async function runAnalyzeDiff(options = {}) {
|
|
|
105536
107316
|
if (payload.steps) renderSteps(payload.steps);
|
|
105537
107317
|
}, stepDefs);
|
|
105538
107318
|
const abortController = new AbortController();
|
|
105539
|
-
|
|
107319
|
+
let sigintRequested = false;
|
|
107320
|
+
const onSigint = () => {
|
|
107321
|
+
if (sigintRequested) {
|
|
107322
|
+
process.stderr.write("\nForce quit.\n");
|
|
107323
|
+
process.exit(130);
|
|
107324
|
+
}
|
|
107325
|
+
sigintRequested = true;
|
|
107326
|
+
abortController.abort();
|
|
107327
|
+
process.stderr.write("\nCancelling\u2026 (press Ctrl+C again to force quit)\n");
|
|
107328
|
+
};
|
|
105540
107329
|
process.on("SIGINT", onSigint);
|
|
105541
107330
|
try {
|
|
105542
107331
|
const { diff } = await diffInProcess2(project, {
|
|
@@ -105591,7 +107380,7 @@ import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
|
105591
107380
|
import fs12 from "node:fs";
|
|
105592
107381
|
import path17 from "node:path";
|
|
105593
107382
|
import os5 from "node:os";
|
|
105594
|
-
import { execSync
|
|
107383
|
+
import { execSync } from "node:child_process";
|
|
105595
107384
|
|
|
105596
107385
|
// tools/cli/src/commands/service/env.ts
|
|
105597
107386
|
import fs11 from "node:fs";
|
|
@@ -105672,11 +107461,11 @@ var MacOSService = class {
|
|
|
105672
107461
|
fs12.mkdirSync(path17.dirname(logPath), { recursive: true });
|
|
105673
107462
|
const plist = buildPlist(serverPath, logPath, envVars);
|
|
105674
107463
|
fs12.writeFileSync(PLIST_PATH, plist, "utf-8");
|
|
105675
|
-
|
|
107464
|
+
execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
|
|
105676
107465
|
}
|
|
105677
107466
|
async uninstall() {
|
|
105678
107467
|
try {
|
|
105679
|
-
|
|
107468
|
+
execSync(`launchctl unload "${PLIST_PATH}"`, { stdio: "pipe" });
|
|
105680
107469
|
} catch {
|
|
105681
107470
|
}
|
|
105682
107471
|
if (fs12.existsSync(PLIST_PATH)) {
|
|
@@ -105687,17 +107476,17 @@ var MacOSService = class {
|
|
|
105687
107476
|
if (!fs12.existsSync(PLIST_PATH)) {
|
|
105688
107477
|
throw new Error("Service is not installed. Run 'truecourse service install' first.");
|
|
105689
107478
|
}
|
|
105690
|
-
|
|
107479
|
+
execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
|
|
105691
107480
|
}
|
|
105692
107481
|
async stop() {
|
|
105693
107482
|
try {
|
|
105694
|
-
|
|
107483
|
+
execSync(`launchctl unload "${PLIST_PATH}"`, { stdio: "pipe" });
|
|
105695
107484
|
} catch {
|
|
105696
107485
|
}
|
|
105697
107486
|
}
|
|
105698
107487
|
async status() {
|
|
105699
107488
|
try {
|
|
105700
|
-
const output =
|
|
107489
|
+
const output = execSync(`launchctl list ${SERVICE_LABEL}`, {
|
|
105701
107490
|
stdio: ["pipe", "pipe", "pipe"],
|
|
105702
107491
|
encoding: "utf-8"
|
|
105703
107492
|
});
|
|
@@ -105722,7 +107511,7 @@ var MacOSService = class {
|
|
|
105722
107511
|
import fs13 from "node:fs";
|
|
105723
107512
|
import path18 from "node:path";
|
|
105724
107513
|
import os6 from "node:os";
|
|
105725
|
-
import { execSync as
|
|
107514
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
105726
107515
|
var SERVICE_NAME = "truecourse";
|
|
105727
107516
|
var UNIT_DIR = path18.join(os6.homedir(), ".config", "systemd", "user");
|
|
105728
107517
|
var UNIT_PATH = path18.join(UNIT_DIR, `${SERVICE_NAME}.service`);
|
|
@@ -105752,35 +107541,35 @@ var LinuxService = class {
|
|
|
105752
107541
|
fs13.mkdirSync(path18.dirname(logPath), { recursive: true });
|
|
105753
107542
|
const unit = buildUnitFile(serverPath, logPath);
|
|
105754
107543
|
fs13.writeFileSync(UNIT_PATH, unit, "utf-8");
|
|
105755
|
-
|
|
105756
|
-
|
|
107544
|
+
execSync2("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
107545
|
+
execSync2(`systemctl --user enable ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
105757
107546
|
}
|
|
105758
107547
|
async uninstall() {
|
|
105759
107548
|
try {
|
|
105760
|
-
|
|
107549
|
+
execSync2(`systemctl --user stop ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
105761
107550
|
} catch {
|
|
105762
107551
|
}
|
|
105763
107552
|
try {
|
|
105764
|
-
|
|
107553
|
+
execSync2(`systemctl --user disable ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
105765
107554
|
} catch {
|
|
105766
107555
|
}
|
|
105767
107556
|
if (fs13.existsSync(UNIT_PATH)) {
|
|
105768
107557
|
fs13.unlinkSync(UNIT_PATH);
|
|
105769
107558
|
}
|
|
105770
107559
|
try {
|
|
105771
|
-
|
|
107560
|
+
execSync2("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
105772
107561
|
} catch {
|
|
105773
107562
|
}
|
|
105774
107563
|
}
|
|
105775
107564
|
async start() {
|
|
105776
|
-
|
|
107565
|
+
execSync2(`systemctl --user start ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
105777
107566
|
}
|
|
105778
107567
|
async stop() {
|
|
105779
|
-
|
|
107568
|
+
execSync2(`systemctl --user stop ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
105780
107569
|
}
|
|
105781
107570
|
async status() {
|
|
105782
107571
|
try {
|
|
105783
|
-
const output =
|
|
107572
|
+
const output = execSync2(
|
|
105784
107573
|
`systemctl --user show ${SERVICE_NAME} --property=ActiveState,MainPID`,
|
|
105785
107574
|
{ stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8" }
|
|
105786
107575
|
);
|
|
@@ -105799,7 +107588,7 @@ var LinuxService = class {
|
|
|
105799
107588
|
};
|
|
105800
107589
|
|
|
105801
107590
|
// tools/cli/src/commands/service/windows.ts
|
|
105802
|
-
import { execSync as
|
|
107591
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
105803
107592
|
var SERVICE_NAME2 = "TrueCourse";
|
|
105804
107593
|
var WindowsService = class {
|
|
105805
107594
|
svc;
|
|
@@ -105849,14 +107638,14 @@ var WindowsService = class {
|
|
|
105849
107638
|
});
|
|
105850
107639
|
}
|
|
105851
107640
|
async start() {
|
|
105852
|
-
|
|
107641
|
+
execSync3(`sc.exe start ${SERVICE_NAME2}`, { stdio: "pipe" });
|
|
105853
107642
|
}
|
|
105854
107643
|
async stop() {
|
|
105855
|
-
|
|
107644
|
+
execSync3(`sc.exe stop ${SERVICE_NAME2}`, { stdio: "pipe" });
|
|
105856
107645
|
}
|
|
105857
107646
|
async status() {
|
|
105858
107647
|
try {
|
|
105859
|
-
const output =
|
|
107648
|
+
const output = execSync3(`sc.exe query ${SERVICE_NAME2}`, {
|
|
105860
107649
|
stdio: ["pipe", "pipe", "pipe"],
|
|
105861
107650
|
encoding: "utf-8"
|
|
105862
107651
|
});
|
|
@@ -105872,7 +107661,7 @@ var WindowsService = class {
|
|
|
105872
107661
|
}
|
|
105873
107662
|
async isInstalled() {
|
|
105874
107663
|
try {
|
|
105875
|
-
|
|
107664
|
+
execSync3(`sc.exe query ${SERVICE_NAME2}`, { stdio: "pipe" });
|
|
105876
107665
|
return true;
|
|
105877
107666
|
} catch {
|
|
105878
107667
|
return false;
|
|
@@ -106462,7 +108251,7 @@ async function runRulesLlm(options) {
|
|
|
106462
108251
|
}
|
|
106463
108252
|
|
|
106464
108253
|
// tools/cli/src/commands/hooks.ts
|
|
106465
|
-
import { execSync as
|
|
108254
|
+
import { execSync as execSync4 } from "node:child_process";
|
|
106466
108255
|
import fs16 from "node:fs";
|
|
106467
108256
|
import path21 from "node:path";
|
|
106468
108257
|
|
|
@@ -109290,7 +111079,7 @@ async function runHooksRun() {
|
|
|
109290
111079
|
}
|
|
109291
111080
|
let hasStaged = false;
|
|
109292
111081
|
try {
|
|
109293
|
-
const output =
|
|
111082
|
+
const output = execSync4("git diff --cached --name-only --diff-filter=ACM", {
|
|
109294
111083
|
encoding: "utf-8",
|
|
109295
111084
|
cwd: projectRoot
|
|
109296
111085
|
}).trim();
|
|
@@ -109367,7 +111156,7 @@ async function runHooksRun() {
|
|
|
109367
111156
|
|
|
109368
111157
|
// tools/cli/src/index.ts
|
|
109369
111158
|
var program2 = new Command();
|
|
109370
|
-
program2.name("truecourse").version("0.5.
|
|
111159
|
+
program2.name("truecourse").version("0.5.8-windows.2").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
|
|
109371
111160
|
var dashboardCmd = program2.command("dashboard").description("Start the TrueCourse dashboard and open it in your browser").option("--reconfigure", "Re-prompt for console vs background service mode").option("--service", "Run as a background service (skips mode prompt)").option("--console", "Run in this terminal (skips mode prompt)").action(async (options) => {
|
|
109372
111161
|
if (options.service && options.console) {
|
|
109373
111162
|
console.error("error: --service and --console are mutually exclusive");
|