aislop 0.10.0 → 0.10.1
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/dist/cli.js +417 -233
- package/dist/index.js +334 -231
- package/dist/{json-DaFOYHcf.js → json-Bqkcl1DF.js} +1 -1
- package/dist/mcp.js +332 -229
- package/dist/{sarif-BtSQ92c6.js → sarif-C-vh4wcC.js} +1 -1
- package/dist/version-rlhQD8Qh.js +5 -0
- package/package.json +1 -1
- package/dist/version-DYg_ShBx.js +0 -5
package/dist/cli.js
CHANGED
|
@@ -34,7 +34,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
34
34
|
|
|
35
35
|
//#endregion
|
|
36
36
|
//#region src/version.ts
|
|
37
|
-
const APP_VERSION = "0.10.
|
|
37
|
+
const APP_VERSION = "0.10.1";
|
|
38
38
|
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region src/telemetry/env.ts
|
|
@@ -183,7 +183,7 @@ const redactProperties = (props) => {
|
|
|
183
183
|
const POSTHOG_HOST = process.env.AISLOP_POSTHOG_HOST ?? "https://eu.i.posthog.com";
|
|
184
184
|
const POSTHOG_KEY = process.env.AISLOP_POSTHOG_KEY ?? "phc_eY2cOMFva9q24GrWeOuvuVIOhCIdjOALxeAR3ItrqbJ";
|
|
185
185
|
const SCHEMA_VERSION = "v2";
|
|
186
|
-
const REQUEST_TIMEOUT_MS = 3e3;
|
|
186
|
+
const REQUEST_TIMEOUT_MS$1 = 3e3;
|
|
187
187
|
const isTelemetryDisabled = (config) => {
|
|
188
188
|
const env = process.env;
|
|
189
189
|
if (env.AISLOP_NO_TELEMETRY === "1" || env.DO_NOT_TRACK === "1") return true;
|
|
@@ -237,7 +237,7 @@ const track = (input) => {
|
|
|
237
237
|
method: "POST",
|
|
238
238
|
headers: { "Content-Type": "application/json" },
|
|
239
239
|
body: JSON.stringify(payload),
|
|
240
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
240
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS$1)
|
|
241
241
|
}).then(() => {}).catch(() => {}).finally(() => {
|
|
242
242
|
pendingRequests.delete(request);
|
|
243
243
|
});
|
|
@@ -836,6 +836,218 @@ const loadConfig = (directory) => {
|
|
|
836
836
|
}
|
|
837
837
|
};
|
|
838
838
|
|
|
839
|
+
//#endregion
|
|
840
|
+
//#region src/utils/source-masker.ts
|
|
841
|
+
const JS_EXTS$2 = new Set([
|
|
842
|
+
".ts",
|
|
843
|
+
".tsx",
|
|
844
|
+
".js",
|
|
845
|
+
".jsx",
|
|
846
|
+
".mjs",
|
|
847
|
+
".cjs"
|
|
848
|
+
]);
|
|
849
|
+
const PY_EXTS = new Set([".py"]);
|
|
850
|
+
const RB_EXTS = new Set([".rb"]);
|
|
851
|
+
const PHP_EXTS = new Set([".php"]);
|
|
852
|
+
const familyForExt = (ext) => {
|
|
853
|
+
if (JS_EXTS$2.has(ext)) return "js";
|
|
854
|
+
if (PY_EXTS.has(ext)) return "py";
|
|
855
|
+
if (RB_EXTS.has(ext)) return "rb";
|
|
856
|
+
if (PHP_EXTS.has(ext)) return "php";
|
|
857
|
+
return "none";
|
|
858
|
+
};
|
|
859
|
+
const maskStringsAndComments = (content, ext) => {
|
|
860
|
+
const family = familyForExt(ext);
|
|
861
|
+
if (family === "none") return content;
|
|
862
|
+
if (family === "js") return maskJs(content, true);
|
|
863
|
+
return maskSimple(content, family, true);
|
|
864
|
+
};
|
|
865
|
+
const maskComments = (content, ext) => {
|
|
866
|
+
const family = familyForExt(ext);
|
|
867
|
+
if (family === "none") return content;
|
|
868
|
+
if (family === "js") return maskJs(content, false);
|
|
869
|
+
return maskSimple(content, family, false);
|
|
870
|
+
};
|
|
871
|
+
const handleQuotesAndComments = (content, i, tplStack, mask, maskStrings) => {
|
|
872
|
+
const len = content.length;
|
|
873
|
+
const c = content[i];
|
|
874
|
+
const next = content[i + 1];
|
|
875
|
+
if (c === "\"" || c === "'") {
|
|
876
|
+
const strStart = i;
|
|
877
|
+
const end = consumeQuotedString(content, i, c);
|
|
878
|
+
if (maskStrings) mask(strStart + 1, end - 1);
|
|
879
|
+
return {
|
|
880
|
+
handled: true,
|
|
881
|
+
nextI: end
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
if (c === "`") {
|
|
885
|
+
const scan = consumeTemplateString(content, i + 1);
|
|
886
|
+
if (maskStrings) mask(i + 1, scan.maskEnd);
|
|
887
|
+
if (scan.openedInterp) tplStack.push(0);
|
|
888
|
+
return {
|
|
889
|
+
handled: true,
|
|
890
|
+
nextI: scan.resumeAt
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
if (c === "/" && next === "/") {
|
|
894
|
+
const strStart = i;
|
|
895
|
+
let k = i;
|
|
896
|
+
while (k < len && content[k] !== "\n") k++;
|
|
897
|
+
mask(strStart, k);
|
|
898
|
+
return {
|
|
899
|
+
handled: true,
|
|
900
|
+
nextI: k
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
if (c === "/" && next === "*") {
|
|
904
|
+
const strStart = i;
|
|
905
|
+
let k = i + 2;
|
|
906
|
+
while (k < len - 1 && !(content[k] === "*" && content[k + 1] === "/")) k++;
|
|
907
|
+
if (k < len - 1) k += 2;
|
|
908
|
+
mask(strStart, k);
|
|
909
|
+
return {
|
|
910
|
+
handled: true,
|
|
911
|
+
nextI: k
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
return {
|
|
915
|
+
handled: false,
|
|
916
|
+
nextI: i
|
|
917
|
+
};
|
|
918
|
+
};
|
|
919
|
+
const maskJs = (content, maskStrings) => {
|
|
920
|
+
const out = content.split("");
|
|
921
|
+
const len = content.length;
|
|
922
|
+
const tplStack = [];
|
|
923
|
+
let i = 0;
|
|
924
|
+
const mask = (start, end) => {
|
|
925
|
+
for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
|
|
926
|
+
};
|
|
927
|
+
while (i < len) {
|
|
928
|
+
const c = content[i];
|
|
929
|
+
if (tplStack.length > 0) {
|
|
930
|
+
if (c === "{") {
|
|
931
|
+
tplStack[tplStack.length - 1]++;
|
|
932
|
+
i++;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (c === "}") {
|
|
936
|
+
if (tplStack[tplStack.length - 1] === 0) {
|
|
937
|
+
tplStack.pop();
|
|
938
|
+
const scan = consumeTemplateString(content, i + 1);
|
|
939
|
+
if (maskStrings) mask(i + 1, scan.maskEnd);
|
|
940
|
+
if (scan.openedInterp) tplStack.push(0);
|
|
941
|
+
i = scan.resumeAt;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
tplStack[tplStack.length - 1]--;
|
|
945
|
+
i++;
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
const handled = handleQuotesAndComments(content, i, tplStack, mask, maskStrings);
|
|
950
|
+
if (handled.handled) {
|
|
951
|
+
i = handled.nextI;
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
i++;
|
|
955
|
+
}
|
|
956
|
+
return out.join("");
|
|
957
|
+
};
|
|
958
|
+
const consumeQuotedString = (content, start, quote) => {
|
|
959
|
+
const len = content.length;
|
|
960
|
+
let i = start + 1;
|
|
961
|
+
while (i < len) {
|
|
962
|
+
const c = content[i];
|
|
963
|
+
if (c === "\\" && i + 1 < len) {
|
|
964
|
+
i += 2;
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if (c === quote) return i + 1;
|
|
968
|
+
if (c === "\n") return i;
|
|
969
|
+
i++;
|
|
970
|
+
}
|
|
971
|
+
return i;
|
|
972
|
+
};
|
|
973
|
+
const consumeTemplateString = (content, start) => {
|
|
974
|
+
const len = content.length;
|
|
975
|
+
let i = start;
|
|
976
|
+
while (i < len) {
|
|
977
|
+
const c = content[i];
|
|
978
|
+
if (c === "\\" && i + 1 < len) {
|
|
979
|
+
i += 2;
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (c === "`") return {
|
|
983
|
+
maskEnd: i,
|
|
984
|
+
resumeAt: i + 1,
|
|
985
|
+
openedInterp: false
|
|
986
|
+
};
|
|
987
|
+
if (c === "$" && content[i + 1] === "{") return {
|
|
988
|
+
maskEnd: i,
|
|
989
|
+
resumeAt: i + 2,
|
|
990
|
+
openedInterp: true
|
|
991
|
+
};
|
|
992
|
+
i++;
|
|
993
|
+
}
|
|
994
|
+
return {
|
|
995
|
+
maskEnd: i,
|
|
996
|
+
resumeAt: i,
|
|
997
|
+
openedInterp: false
|
|
998
|
+
};
|
|
999
|
+
};
|
|
1000
|
+
const maskSimple = (content, family, maskStrings) => {
|
|
1001
|
+
const out = content.split("");
|
|
1002
|
+
const len = content.length;
|
|
1003
|
+
let i = 0;
|
|
1004
|
+
const mask = (start, end) => {
|
|
1005
|
+
for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
|
|
1006
|
+
};
|
|
1007
|
+
while (i < len) {
|
|
1008
|
+
const c = content[i];
|
|
1009
|
+
const next = content[i + 1];
|
|
1010
|
+
if (family === "py" && (c === "\"" || c === "'")) {
|
|
1011
|
+
if (content[i + 1] === c && content[i + 2] === c) {
|
|
1012
|
+
const triple = c + c + c;
|
|
1013
|
+
const end = content.indexOf(triple, i + 3);
|
|
1014
|
+
const stop = end === -1 ? len : end + 3;
|
|
1015
|
+
if (maskStrings) mask(i + 3, stop - 3);
|
|
1016
|
+
i = stop;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (c === "\"" || c === "'") {
|
|
1021
|
+
const strStart = i;
|
|
1022
|
+
i = consumeQuotedString(content, i, c);
|
|
1023
|
+
if (maskStrings) mask(strStart + 1, i - 1);
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
if ((family === "py" || family === "rb" || family === "php") && c === "#") {
|
|
1027
|
+
const strStart = i;
|
|
1028
|
+
while (i < len && content[i] !== "\n") i++;
|
|
1029
|
+
mask(strStart, i);
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
if (family === "php" && c === "/" && next === "/") {
|
|
1033
|
+
const strStart = i;
|
|
1034
|
+
while (i < len && content[i] !== "\n") i++;
|
|
1035
|
+
mask(strStart, i);
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
if (family === "php" && c === "/" && next === "*") {
|
|
1039
|
+
const strStart = i;
|
|
1040
|
+
i += 2;
|
|
1041
|
+
while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
|
|
1042
|
+
if (i < len - 1) i += 2;
|
|
1043
|
+
mask(strStart, i);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
i++;
|
|
1047
|
+
}
|
|
1048
|
+
return out.join("");
|
|
1049
|
+
};
|
|
1050
|
+
|
|
839
1051
|
//#endregion
|
|
840
1052
|
//#region src/utils/source-files.ts
|
|
841
1053
|
const MAX_BUFFER$1 = 50 * 1024 * 1024;
|
|
@@ -1086,7 +1298,7 @@ const getSourceFilesWithExtras = (context, extraExtensions) => {
|
|
|
1086
1298
|
|
|
1087
1299
|
//#endregion
|
|
1088
1300
|
//#region src/engines/ai-slop/abstractions.ts
|
|
1089
|
-
const JS_EXTS$
|
|
1301
|
+
const JS_EXTS$1 = new Set([
|
|
1090
1302
|
".ts",
|
|
1091
1303
|
".tsx",
|
|
1092
1304
|
".js",
|
|
@@ -1097,11 +1309,11 @@ const JS_EXTS$2 = new Set([
|
|
|
1097
1309
|
const THIN_WRAPPER_PATTERNS = [
|
|
1098
1310
|
{
|
|
1099
1311
|
pattern: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*\w[^{]*)?\{\s*\n?\s*return\s+\w+\([^)]*\);\s*\n?\s*\}/g,
|
|
1100
|
-
extensions: JS_EXTS$
|
|
1312
|
+
extensions: JS_EXTS$1
|
|
1101
1313
|
},
|
|
1102
1314
|
{
|
|
1103
1315
|
pattern: /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w[^=]*)?\s*=>\s*\w+\([^)]*\);/g,
|
|
1104
|
-
extensions: JS_EXTS$
|
|
1316
|
+
extensions: JS_EXTS$1
|
|
1105
1317
|
},
|
|
1106
1318
|
{
|
|
1107
1319
|
pattern: /def\s+(\w+)\s*\([^)]*\)(?:\s*->[^:]*)?:\s*\n\s+return\s+\w+\([^)]*\)\s*$/gm,
|
|
@@ -1194,8 +1406,9 @@ const detectOverAbstraction = async (context) => {
|
|
|
1194
1406
|
}
|
|
1195
1407
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
1196
1408
|
const ext = path.extname(filePath);
|
|
1197
|
-
|
|
1198
|
-
diagnostics.push(...
|
|
1409
|
+
const codeOnly = maskComments(content, ext);
|
|
1410
|
+
diagnostics.push(...detectThinWrappers(codeOnly, relativePath, ext));
|
|
1411
|
+
diagnostics.push(...detectAiNaming(codeOnly, relativePath));
|
|
1199
1412
|
}
|
|
1200
1413
|
return diagnostics;
|
|
1201
1414
|
};
|
|
@@ -1551,9 +1764,10 @@ const detectDeadPatterns = async (context) => {
|
|
|
1551
1764
|
}
|
|
1552
1765
|
const ext = path.extname(filePath);
|
|
1553
1766
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
1554
|
-
|
|
1767
|
+
const codeOnly = maskComments(content, ext);
|
|
1768
|
+
diagnostics.push(...detectConsoleLeftovers(codeOnly, relativePath, ext));
|
|
1555
1769
|
diagnostics.push(...detectTodoStubs(content, relativePath));
|
|
1556
|
-
diagnostics.push(...detectDeadCodePatterns(
|
|
1770
|
+
diagnostics.push(...detectDeadCodePatterns(codeOnly, relativePath, ext));
|
|
1557
1771
|
diagnostics.push(...detectUnsafeTypePatterns(content, relativePath, ext));
|
|
1558
1772
|
}
|
|
1559
1773
|
return diagnostics;
|
|
@@ -1743,6 +1957,7 @@ const JS_EXTENSIONS$3 = new Set([
|
|
|
1743
1957
|
const IMPORT_FROM_RE$1 = /^\s*import\s+([^;]*?)\s+from\s+["']([^"']+)["']/;
|
|
1744
1958
|
const TYPE_ONLY_RE = /^\s*type\b/;
|
|
1745
1959
|
const VALUE_BINDING_RE = /\{([^}]*)\}/;
|
|
1960
|
+
const NAMESPACE_RE = /\*\s+as\s+/;
|
|
1746
1961
|
const isTypeOnly = (clause) => {
|
|
1747
1962
|
if (TYPE_ONLY_RE.test(clause)) return true;
|
|
1748
1963
|
const braces = VALUE_BINDING_RE.exec(clause);
|
|
@@ -1760,7 +1975,8 @@ const extractImportLines = (content) => {
|
|
|
1760
1975
|
results.push({
|
|
1761
1976
|
spec: match[2],
|
|
1762
1977
|
line: i + 1,
|
|
1763
|
-
typeOnly: isTypeOnly(match[1])
|
|
1978
|
+
typeOnly: isTypeOnly(match[1]),
|
|
1979
|
+
namespace: NAMESPACE_RE.test(match[1])
|
|
1764
1980
|
});
|
|
1765
1981
|
}
|
|
1766
1982
|
return results;
|
|
@@ -1777,11 +1993,11 @@ const detectDuplicateImports = async (context) => {
|
|
|
1777
1993
|
} catch {
|
|
1778
1994
|
continue;
|
|
1779
1995
|
}
|
|
1780
|
-
const imports = extractImportLines(content);
|
|
1996
|
+
const imports = extractImportLines(maskComments(content, path.extname(filePath)));
|
|
1781
1997
|
if (imports.length < 2) continue;
|
|
1782
1998
|
const byBucket = /* @__PURE__ */ new Map();
|
|
1783
1999
|
for (const imp of imports) {
|
|
1784
|
-
const key = `${imp.typeOnly ? "type" : "value"}\0${imp.spec}`;
|
|
2000
|
+
const key = `${imp.namespace ? "ns" : imp.typeOnly ? "type" : "value"}\0${imp.spec}`;
|
|
1785
2001
|
const list = byBucket.get(key) ?? [];
|
|
1786
2002
|
list.push(imp);
|
|
1787
2003
|
byBucket.set(key, list);
|
|
@@ -2148,7 +2364,7 @@ const detectHardcodedConfigLiterals = async (context) => {
|
|
|
2148
2364
|
}
|
|
2149
2365
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
2150
2366
|
const ext = path.extname(filePath);
|
|
2151
|
-
diagnostics.push(...scanFileForConfigLiterals(content, relativePath, ext));
|
|
2367
|
+
diagnostics.push(...scanFileForConfigLiterals(maskComments(content, ext), relativePath, ext));
|
|
2152
2368
|
}
|
|
2153
2369
|
return diagnostics;
|
|
2154
2370
|
};
|
|
@@ -3654,7 +3870,7 @@ const detectRustPatterns = async (context) => {
|
|
|
3654
3870
|
|
|
3655
3871
|
//#endregion
|
|
3656
3872
|
//#region src/engines/ai-slop/silent-recovery.ts
|
|
3657
|
-
const JS_EXTS
|
|
3873
|
+
const JS_EXTS = new Set([
|
|
3658
3874
|
".ts",
|
|
3659
3875
|
".tsx",
|
|
3660
3876
|
".js",
|
|
@@ -3783,7 +3999,7 @@ const detectSilentRecovery = async (context) => {
|
|
|
3783
3999
|
for (const filePath of files) {
|
|
3784
4000
|
if (isAutoGenerated(filePath)) continue;
|
|
3785
4001
|
const ext = path.extname(filePath);
|
|
3786
|
-
const isJs = JS_EXTS
|
|
4002
|
+
const isJs = JS_EXTS.has(ext);
|
|
3787
4003
|
if (!isJs && !(ext === ".py")) continue;
|
|
3788
4004
|
const relPath = path.relative(context.rootDirectory, filePath);
|
|
3789
4005
|
if (isNonProductionPath(relPath)) continue;
|
|
@@ -4263,12 +4479,92 @@ const findBraceFunctionEnd = (lines, startIndex) => {
|
|
|
4263
4479
|
maxNesting
|
|
4264
4480
|
};
|
|
4265
4481
|
};
|
|
4266
|
-
const
|
|
4267
|
-
|
|
4268
|
-
let
|
|
4482
|
+
const extractPythonSignature = (lines, startIndex) => {
|
|
4483
|
+
let depth = 0;
|
|
4484
|
+
let started = false;
|
|
4485
|
+
let params = "";
|
|
4486
|
+
for (let j = startIndex; j < lines.length; j++) {
|
|
4487
|
+
const l = lines[j];
|
|
4488
|
+
for (let ci = 0; ci < l.length; ci++) {
|
|
4489
|
+
const ch = l[ci];
|
|
4490
|
+
if (ch === "(") {
|
|
4491
|
+
depth++;
|
|
4492
|
+
if (depth === 1 && !started) {
|
|
4493
|
+
started = true;
|
|
4494
|
+
continue;
|
|
4495
|
+
}
|
|
4496
|
+
} else if (ch === ")") {
|
|
4497
|
+
depth--;
|
|
4498
|
+
if (depth === 0) return {
|
|
4499
|
+
params,
|
|
4500
|
+
sigEndIndex: j
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
if (started) params += ch;
|
|
4504
|
+
}
|
|
4505
|
+
if (started) params += " ";
|
|
4506
|
+
}
|
|
4507
|
+
return {
|
|
4508
|
+
params,
|
|
4509
|
+
sigEndIndex: startIndex
|
|
4510
|
+
};
|
|
4511
|
+
};
|
|
4512
|
+
const countPythonParams = (signature) => {
|
|
4513
|
+
let depth = 0;
|
|
4514
|
+
const parts = [];
|
|
4515
|
+
let current = "";
|
|
4516
|
+
for (const ch of signature) {
|
|
4517
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
4518
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
4519
|
+
if (ch === "," && depth === 0) {
|
|
4520
|
+
parts.push(current);
|
|
4521
|
+
current = "";
|
|
4522
|
+
continue;
|
|
4523
|
+
}
|
|
4524
|
+
current += ch;
|
|
4525
|
+
}
|
|
4526
|
+
parts.push(current);
|
|
4527
|
+
let count = 0;
|
|
4528
|
+
for (const raw of parts) {
|
|
4529
|
+
const p = raw.trim();
|
|
4530
|
+
if (p.length === 0 || p === "*" || p === "/") continue;
|
|
4531
|
+
if (p.startsWith("*")) continue;
|
|
4532
|
+
if (p.includes("=")) continue;
|
|
4533
|
+
const name = p.split(":")[0].trim();
|
|
4534
|
+
if (name === "self" || name === "cls") continue;
|
|
4535
|
+
count++;
|
|
4536
|
+
}
|
|
4537
|
+
return count;
|
|
4538
|
+
};
|
|
4539
|
+
const countPythonBodyCodeLines = (lines, sigEndIndex, endLine) => {
|
|
4540
|
+
let count = 0;
|
|
4541
|
+
let inDoc = false;
|
|
4542
|
+
let delim = "";
|
|
4543
|
+
for (let j = sigEndIndex + 1; j <= endLine && j < lines.length; j++) {
|
|
4544
|
+
const t = lines[j].trim();
|
|
4545
|
+
if (inDoc) {
|
|
4546
|
+
if (t.includes(delim)) inDoc = false;
|
|
4547
|
+
continue;
|
|
4548
|
+
}
|
|
4549
|
+
if (t === "" || t.startsWith("#")) continue;
|
|
4550
|
+
const opener = t.startsWith("\"\"\"") ? "\"\"\"" : t.startsWith("'''") ? "'''" : "";
|
|
4551
|
+
if (opener) {
|
|
4552
|
+
if (!t.slice(3).includes(opener)) {
|
|
4553
|
+
inDoc = true;
|
|
4554
|
+
delim = opener;
|
|
4555
|
+
}
|
|
4556
|
+
continue;
|
|
4557
|
+
}
|
|
4558
|
+
count++;
|
|
4559
|
+
}
|
|
4560
|
+
return count;
|
|
4561
|
+
};
|
|
4562
|
+
const findPythonFunctionEnd = (lines, defIndex, bodyStartIndex) => {
|
|
4563
|
+
const baseIndent = lines[defIndex].match(/^(\s*)/)?.[1].length ?? 0;
|
|
4564
|
+
let endLine = bodyStartIndex;
|
|
4269
4565
|
let maxNesting = 0;
|
|
4270
4566
|
const controlIndentStack = [];
|
|
4271
|
-
for (let j =
|
|
4567
|
+
for (let j = bodyStartIndex + 1; j < lines.length; j++) {
|
|
4272
4568
|
const l = lines[j];
|
|
4273
4569
|
if (l.trim() === "") {
|
|
4274
4570
|
endLine = j;
|
|
@@ -4290,7 +4586,10 @@ const findPythonFunctionEnd = (lines, startIndex) => {
|
|
|
4290
4586
|
};
|
|
4291
4587
|
};
|
|
4292
4588
|
const findFunctionEnd = (lines, startIndex, isPython) => {
|
|
4293
|
-
if (isPython)
|
|
4589
|
+
if (isPython) {
|
|
4590
|
+
const { sigEndIndex } = extractPythonSignature(lines, startIndex);
|
|
4591
|
+
return findPythonFunctionEnd(lines, startIndex, sigEndIndex);
|
|
4592
|
+
}
|
|
4294
4593
|
return findBraceFunctionEnd(lines, startIndex);
|
|
4295
4594
|
};
|
|
4296
4595
|
const isBlockArrow = (lines, startIndex) => {
|
|
@@ -4355,7 +4654,7 @@ const FUNCTION_PATTERNS = [
|
|
|
4355
4654
|
]
|
|
4356
4655
|
},
|
|
4357
4656
|
{
|
|
4358
|
-
regex: /^\s*def\s+(\w+)\s*\(
|
|
4657
|
+
regex: /^\s*(?:async\s+)?def\s+(\w+)\s*\(/,
|
|
4359
4658
|
langFilter: [".py"]
|
|
4360
4659
|
},
|
|
4361
4660
|
{
|
|
@@ -4412,14 +4711,23 @@ const analyzeFunctions = (content, ext) => {
|
|
|
4412
4711
|
const isPython = fnMatch.patternIndex === 2;
|
|
4413
4712
|
if (fnMatch.patternIndex === 1 && !isBlockArrow(lines, i)) continue;
|
|
4414
4713
|
const { endLine, maxNesting } = findFunctionEnd(lines, i, isPython);
|
|
4415
|
-
|
|
4416
|
-
|
|
4714
|
+
let templateLines;
|
|
4715
|
+
let paramCount;
|
|
4716
|
+
if (isPython) {
|
|
4717
|
+
const sig = extractPythonSignature(lines, i);
|
|
4718
|
+
const codeLines = countPythonBodyCodeLines(lines, sig.sigEndIndex, endLine);
|
|
4719
|
+
templateLines = endLine - i + 1 - codeLines;
|
|
4720
|
+
paramCount = countPythonParams(sig.params);
|
|
4721
|
+
} else {
|
|
4722
|
+
templateLines = countTemplateLines(lines.slice(i + 1, endLine));
|
|
4723
|
+
paramCount = countParams(fnMatch.params);
|
|
4724
|
+
}
|
|
4417
4725
|
functions.push({
|
|
4418
4726
|
name: fnMatch.name,
|
|
4419
4727
|
startLine: i + 1,
|
|
4420
4728
|
lineCount: endLine - i + 1,
|
|
4421
4729
|
maxNesting,
|
|
4422
|
-
paramCount
|
|
4730
|
+
paramCount,
|
|
4423
4731
|
templateLines
|
|
4424
4732
|
});
|
|
4425
4733
|
}
|
|
@@ -6629,212 +6937,6 @@ const runCargoAudit = async (rootDir, timeout) => {
|
|
|
6629
6937
|
}
|
|
6630
6938
|
};
|
|
6631
6939
|
|
|
6632
|
-
//#endregion
|
|
6633
|
-
//#region src/utils/source-masker.ts
|
|
6634
|
-
const JS_EXTS = new Set([
|
|
6635
|
-
".ts",
|
|
6636
|
-
".tsx",
|
|
6637
|
-
".js",
|
|
6638
|
-
".jsx",
|
|
6639
|
-
".mjs",
|
|
6640
|
-
".cjs"
|
|
6641
|
-
]);
|
|
6642
|
-
const PY_EXTS = new Set([".py"]);
|
|
6643
|
-
const RB_EXTS = new Set([".rb"]);
|
|
6644
|
-
const PHP_EXTS = new Set([".php"]);
|
|
6645
|
-
const familyForExt = (ext) => {
|
|
6646
|
-
if (JS_EXTS.has(ext)) return "js";
|
|
6647
|
-
if (PY_EXTS.has(ext)) return "py";
|
|
6648
|
-
if (RB_EXTS.has(ext)) return "rb";
|
|
6649
|
-
if (PHP_EXTS.has(ext)) return "php";
|
|
6650
|
-
return "none";
|
|
6651
|
-
};
|
|
6652
|
-
const maskStringsAndComments = (content, ext) => {
|
|
6653
|
-
const family = familyForExt(ext);
|
|
6654
|
-
if (family === "none") return content;
|
|
6655
|
-
if (family === "js") return maskJs(content);
|
|
6656
|
-
return maskSimple(content, family);
|
|
6657
|
-
};
|
|
6658
|
-
const handleQuotesAndComments = (content, i, tplStack, mask) => {
|
|
6659
|
-
const len = content.length;
|
|
6660
|
-
const c = content[i];
|
|
6661
|
-
const next = content[i + 1];
|
|
6662
|
-
if (c === "\"" || c === "'") {
|
|
6663
|
-
const strStart = i;
|
|
6664
|
-
const end = consumeQuotedString(content, i, c);
|
|
6665
|
-
mask(strStart + 1, end - 1);
|
|
6666
|
-
return {
|
|
6667
|
-
handled: true,
|
|
6668
|
-
nextI: end
|
|
6669
|
-
};
|
|
6670
|
-
}
|
|
6671
|
-
if (c === "`") {
|
|
6672
|
-
const scan = consumeTemplateString(content, i + 1);
|
|
6673
|
-
mask(i + 1, scan.maskEnd);
|
|
6674
|
-
if (scan.openedInterp) tplStack.push(0);
|
|
6675
|
-
return {
|
|
6676
|
-
handled: true,
|
|
6677
|
-
nextI: scan.resumeAt
|
|
6678
|
-
};
|
|
6679
|
-
}
|
|
6680
|
-
if (c === "/" && next === "/") {
|
|
6681
|
-
const strStart = i;
|
|
6682
|
-
let k = i;
|
|
6683
|
-
while (k < len && content[k] !== "\n") k++;
|
|
6684
|
-
mask(strStart, k);
|
|
6685
|
-
return {
|
|
6686
|
-
handled: true,
|
|
6687
|
-
nextI: k
|
|
6688
|
-
};
|
|
6689
|
-
}
|
|
6690
|
-
if (c === "/" && next === "*") {
|
|
6691
|
-
const strStart = i;
|
|
6692
|
-
let k = i + 2;
|
|
6693
|
-
while (k < len - 1 && !(content[k] === "*" && content[k + 1] === "/")) k++;
|
|
6694
|
-
if (k < len - 1) k += 2;
|
|
6695
|
-
mask(strStart, k);
|
|
6696
|
-
return {
|
|
6697
|
-
handled: true,
|
|
6698
|
-
nextI: k
|
|
6699
|
-
};
|
|
6700
|
-
}
|
|
6701
|
-
return {
|
|
6702
|
-
handled: false,
|
|
6703
|
-
nextI: i
|
|
6704
|
-
};
|
|
6705
|
-
};
|
|
6706
|
-
const maskJs = (content) => {
|
|
6707
|
-
const out = content.split("");
|
|
6708
|
-
const len = content.length;
|
|
6709
|
-
const tplStack = [];
|
|
6710
|
-
let i = 0;
|
|
6711
|
-
const mask = (start, end) => {
|
|
6712
|
-
for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
|
|
6713
|
-
};
|
|
6714
|
-
while (i < len) {
|
|
6715
|
-
const c = content[i];
|
|
6716
|
-
if (tplStack.length > 0) {
|
|
6717
|
-
if (c === "{") {
|
|
6718
|
-
tplStack[tplStack.length - 1]++;
|
|
6719
|
-
i++;
|
|
6720
|
-
continue;
|
|
6721
|
-
}
|
|
6722
|
-
if (c === "}") {
|
|
6723
|
-
if (tplStack[tplStack.length - 1] === 0) {
|
|
6724
|
-
tplStack.pop();
|
|
6725
|
-
const scan = consumeTemplateString(content, i + 1);
|
|
6726
|
-
mask(i + 1, scan.maskEnd);
|
|
6727
|
-
if (scan.openedInterp) tplStack.push(0);
|
|
6728
|
-
i = scan.resumeAt;
|
|
6729
|
-
continue;
|
|
6730
|
-
}
|
|
6731
|
-
tplStack[tplStack.length - 1]--;
|
|
6732
|
-
i++;
|
|
6733
|
-
continue;
|
|
6734
|
-
}
|
|
6735
|
-
}
|
|
6736
|
-
const handled = handleQuotesAndComments(content, i, tplStack, mask);
|
|
6737
|
-
if (handled.handled) {
|
|
6738
|
-
i = handled.nextI;
|
|
6739
|
-
continue;
|
|
6740
|
-
}
|
|
6741
|
-
i++;
|
|
6742
|
-
}
|
|
6743
|
-
return out.join("");
|
|
6744
|
-
};
|
|
6745
|
-
const consumeQuotedString = (content, start, quote) => {
|
|
6746
|
-
const len = content.length;
|
|
6747
|
-
let i = start + 1;
|
|
6748
|
-
while (i < len) {
|
|
6749
|
-
const c = content[i];
|
|
6750
|
-
if (c === "\\" && i + 1 < len) {
|
|
6751
|
-
i += 2;
|
|
6752
|
-
continue;
|
|
6753
|
-
}
|
|
6754
|
-
if (c === quote) return i + 1;
|
|
6755
|
-
if (c === "\n") return i;
|
|
6756
|
-
i++;
|
|
6757
|
-
}
|
|
6758
|
-
return i;
|
|
6759
|
-
};
|
|
6760
|
-
const consumeTemplateString = (content, start) => {
|
|
6761
|
-
const len = content.length;
|
|
6762
|
-
let i = start;
|
|
6763
|
-
while (i < len) {
|
|
6764
|
-
const c = content[i];
|
|
6765
|
-
if (c === "\\" && i + 1 < len) {
|
|
6766
|
-
i += 2;
|
|
6767
|
-
continue;
|
|
6768
|
-
}
|
|
6769
|
-
if (c === "`") return {
|
|
6770
|
-
maskEnd: i,
|
|
6771
|
-
resumeAt: i + 1,
|
|
6772
|
-
openedInterp: false
|
|
6773
|
-
};
|
|
6774
|
-
if (c === "$" && content[i + 1] === "{") return {
|
|
6775
|
-
maskEnd: i,
|
|
6776
|
-
resumeAt: i + 2,
|
|
6777
|
-
openedInterp: true
|
|
6778
|
-
};
|
|
6779
|
-
i++;
|
|
6780
|
-
}
|
|
6781
|
-
return {
|
|
6782
|
-
maskEnd: i,
|
|
6783
|
-
resumeAt: i,
|
|
6784
|
-
openedInterp: false
|
|
6785
|
-
};
|
|
6786
|
-
};
|
|
6787
|
-
const maskSimple = (content, family) => {
|
|
6788
|
-
const out = content.split("");
|
|
6789
|
-
const len = content.length;
|
|
6790
|
-
let i = 0;
|
|
6791
|
-
const mask = (start, end) => {
|
|
6792
|
-
for (let k = start; k < end; k++) if (out[k] !== "\n") out[k] = " ";
|
|
6793
|
-
};
|
|
6794
|
-
while (i < len) {
|
|
6795
|
-
const c = content[i];
|
|
6796
|
-
const next = content[i + 1];
|
|
6797
|
-
if (family === "py" && (c === "\"" || c === "'")) {
|
|
6798
|
-
if (content[i + 1] === c && content[i + 2] === c) {
|
|
6799
|
-
const triple = c + c + c;
|
|
6800
|
-
const end = content.indexOf(triple, i + 3);
|
|
6801
|
-
const stop = end === -1 ? len : end + 3;
|
|
6802
|
-
mask(i + 3, stop - 3);
|
|
6803
|
-
i = stop;
|
|
6804
|
-
continue;
|
|
6805
|
-
}
|
|
6806
|
-
}
|
|
6807
|
-
if (c === "\"" || c === "'") {
|
|
6808
|
-
const strStart = i;
|
|
6809
|
-
i = consumeQuotedString(content, i, c);
|
|
6810
|
-
mask(strStart + 1, i - 1);
|
|
6811
|
-
continue;
|
|
6812
|
-
}
|
|
6813
|
-
if ((family === "py" || family === "rb" || family === "php") && c === "#") {
|
|
6814
|
-
const strStart = i;
|
|
6815
|
-
while (i < len && content[i] !== "\n") i++;
|
|
6816
|
-
mask(strStart, i);
|
|
6817
|
-
continue;
|
|
6818
|
-
}
|
|
6819
|
-
if (family === "php" && c === "/" && next === "/") {
|
|
6820
|
-
const strStart = i;
|
|
6821
|
-
while (i < len && content[i] !== "\n") i++;
|
|
6822
|
-
mask(strStart, i);
|
|
6823
|
-
continue;
|
|
6824
|
-
}
|
|
6825
|
-
if (family === "php" && c === "/" && next === "*") {
|
|
6826
|
-
const strStart = i;
|
|
6827
|
-
i += 2;
|
|
6828
|
-
while (i < len - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
|
|
6829
|
-
if (i < len - 1) i += 2;
|
|
6830
|
-
mask(strStart, i);
|
|
6831
|
-
continue;
|
|
6832
|
-
}
|
|
6833
|
-
i++;
|
|
6834
|
-
}
|
|
6835
|
-
return out.join("");
|
|
6836
|
-
};
|
|
6837
|
-
|
|
6838
6940
|
//#endregion
|
|
6839
6941
|
//#region src/engines/security/risky.ts
|
|
6840
6942
|
const ev = "eval";
|
|
@@ -7109,6 +7211,7 @@ const scanSecrets = async (context) => {
|
|
|
7109
7211
|
} catch {
|
|
7110
7212
|
continue;
|
|
7111
7213
|
}
|
|
7214
|
+
content = maskComments(content, path.extname(filePath));
|
|
7112
7215
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
7113
7216
|
for (const { pattern, name, keywordPrefixed } of SECRET_PATTERNS) {
|
|
7114
7217
|
const regex = new RegExp(pattern.source, pattern.flags);
|
|
@@ -11730,7 +11833,7 @@ const runNpmAuditFix = async (rootDir, onProgress) => {
|
|
|
11730
11833
|
});
|
|
11731
11834
|
if (installResult.exitCode !== 0) throw new Error(installResult.stderr || installResult.stdout || "npm install failed after audit fix");
|
|
11732
11835
|
};
|
|
11733
|
-
const fetchLatestVersion = async (rootDir, pkgName, pm) => {
|
|
11836
|
+
const fetchLatestVersion$1 = async (rootDir, pkgName, pm) => {
|
|
11734
11837
|
try {
|
|
11735
11838
|
const result = await runSubprocess(pm, [
|
|
11736
11839
|
"view",
|
|
@@ -11750,7 +11853,7 @@ const collectOverrides = async (rootDir, vulnerabilities, pm) => {
|
|
|
11750
11853
|
const overrides = {};
|
|
11751
11854
|
for (const [pkgName, vuln] of Object.entries(vulnerabilities)) {
|
|
11752
11855
|
if (vuln.fixAvailable !== false || !vuln.range) continue;
|
|
11753
|
-
const latest = await fetchLatestVersion(rootDir, pkgName, pm);
|
|
11856
|
+
const latest = await fetchLatestVersion$1(rootDir, pkgName, pm);
|
|
11754
11857
|
if (latest) overrides[pkgName] = latest;
|
|
11755
11858
|
}
|
|
11756
11859
|
return overrides;
|
|
@@ -12713,6 +12816,86 @@ const trendCommand = (directory, limit) => {
|
|
|
12713
12816
|
}));
|
|
12714
12817
|
};
|
|
12715
12818
|
|
|
12819
|
+
//#endregion
|
|
12820
|
+
//#region src/update-notifier.ts
|
|
12821
|
+
const REGISTRY_URL = "https://registry.npmjs.org/aislop/latest";
|
|
12822
|
+
const CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
12823
|
+
const REQUEST_TIMEOUT_MS = 2e3;
|
|
12824
|
+
const CACHE_BASENAME = "update_check.json";
|
|
12825
|
+
const isUpdateNotifierDisabled = (env = process.env) => {
|
|
12826
|
+
if (env.AISLOP_NO_UPDATE_NOTIFIER === "1") return true;
|
|
12827
|
+
if (env.NO_UPDATE_NOTIFIER === "1") return true;
|
|
12828
|
+
if (env.DO_NOT_TRACK === "1") return true;
|
|
12829
|
+
return isCiEnv(env);
|
|
12830
|
+
};
|
|
12831
|
+
const resolveUpdateCachePath = (homedir = os.homedir(), env = process.env) => {
|
|
12832
|
+
if (process.platform === "linux" && env.XDG_STATE_HOME) return path.join(env.XDG_STATE_HOME, "aislop", CACHE_BASENAME);
|
|
12833
|
+
return path.join(homedir, ".aislop", CACHE_BASENAME);
|
|
12834
|
+
};
|
|
12835
|
+
const parseVersion = (raw) => {
|
|
12836
|
+
const m = raw.trim().replace(/^v/, "").split(/[-+]/, 1)[0].match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
12837
|
+
if (!m) return null;
|
|
12838
|
+
return {
|
|
12839
|
+
major: Number(m[1]),
|
|
12840
|
+
minor: Number(m[2]),
|
|
12841
|
+
patch: Number(m[3])
|
|
12842
|
+
};
|
|
12843
|
+
};
|
|
12844
|
+
const isOutdated = (current, latest) => {
|
|
12845
|
+
const c = parseVersion(current);
|
|
12846
|
+
const l = parseVersion(latest);
|
|
12847
|
+
if (!c || !l) return false;
|
|
12848
|
+
if (l.major !== c.major) return l.major > c.major;
|
|
12849
|
+
if (l.minor !== c.minor) return l.minor > c.minor;
|
|
12850
|
+
return l.patch > c.patch;
|
|
12851
|
+
};
|
|
12852
|
+
const formatUpdateNotice = (current, latest) => `\nUpdate available: ${current} -> ${latest}. Run npx aislop@latest to upgrade.\n`;
|
|
12853
|
+
const readCache = (cachePath) => {
|
|
12854
|
+
try {
|
|
12855
|
+
const parsed = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
|
|
12856
|
+
if (typeof parsed?.latest === "string" && typeof parsed?.checkedAt === "number") return {
|
|
12857
|
+
latest: parsed.latest,
|
|
12858
|
+
checkedAt: parsed.checkedAt
|
|
12859
|
+
};
|
|
12860
|
+
return null;
|
|
12861
|
+
} catch {
|
|
12862
|
+
return null;
|
|
12863
|
+
}
|
|
12864
|
+
};
|
|
12865
|
+
const writeCache = (cachePath, cache) => {
|
|
12866
|
+
try {
|
|
12867
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
12868
|
+
fs.writeFileSync(cachePath, JSON.stringify(cache));
|
|
12869
|
+
return true;
|
|
12870
|
+
} catch {
|
|
12871
|
+
return false;
|
|
12872
|
+
}
|
|
12873
|
+
};
|
|
12874
|
+
const fetchLatestVersion = async () => {
|
|
12875
|
+
try {
|
|
12876
|
+
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
12877
|
+
if (!res.ok) return null;
|
|
12878
|
+
const data = await res.json();
|
|
12879
|
+
return typeof data.version === "string" ? data.version : null;
|
|
12880
|
+
} catch {
|
|
12881
|
+
return null;
|
|
12882
|
+
}
|
|
12883
|
+
};
|
|
12884
|
+
const maybeNotifyUpdate = async (now = Date.now()) => {
|
|
12885
|
+
if (isUpdateNotifierDisabled()) return;
|
|
12886
|
+
if (!process.stderr.isTTY) return;
|
|
12887
|
+
const cachePath = resolveUpdateCachePath();
|
|
12888
|
+
const cache = readCache(cachePath);
|
|
12889
|
+
if (cache && isOutdated(APP_VERSION, cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
|
|
12890
|
+
if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
|
|
12891
|
+
const latest = await fetchLatestVersion();
|
|
12892
|
+
if (latest) writeCache(cachePath, {
|
|
12893
|
+
latest,
|
|
12894
|
+
checkedAt: now
|
|
12895
|
+
});
|
|
12896
|
+
}
|
|
12897
|
+
};
|
|
12898
|
+
|
|
12716
12899
|
//#endregion
|
|
12717
12900
|
//#region src/cli.ts
|
|
12718
12901
|
process.on("SIGINT", () => process.exit(0));
|
|
@@ -12966,6 +13149,7 @@ const main = async () => {
|
|
|
12966
13149
|
fireInstalledOnce();
|
|
12967
13150
|
await program.parseAsync();
|
|
12968
13151
|
await flushTelemetry();
|
|
13152
|
+
await maybeNotifyUpdate();
|
|
12969
13153
|
};
|
|
12970
13154
|
main();
|
|
12971
13155
|
|