slopbrick 0.34.3 → 0.34.6
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/engine/worker.cjs +137 -18
- package/dist/engine/worker.js +137 -18
- package/dist/index.cjs +141 -22
- package/dist/index.js +141 -22
- package/package.json +1 -1
package/dist/engine/worker.cjs
CHANGED
|
@@ -34497,6 +34497,7 @@ var cppMagicNumbersRule = createRule({
|
|
|
34497
34497
|
if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
|
|
34498
34498
|
const lines = source.split("\n");
|
|
34499
34499
|
const allowSet = /* @__PURE__ */ new Set([
|
|
34500
|
+
// v0.24 originals
|
|
34500
34501
|
"1024",
|
|
34501
34502
|
"65535",
|
|
34502
34503
|
"65536",
|
|
@@ -34515,17 +34516,42 @@ var cppMagicNumbersRule = createRule({
|
|
|
34515
34516
|
"2",
|
|
34516
34517
|
"3",
|
|
34517
34518
|
"4",
|
|
34518
|
-
"5"
|
|
34519
|
+
"5",
|
|
34520
|
+
// v0.34.4 additions
|
|
34521
|
+
"-1",
|
|
34522
|
+
// sentinel value for "not found" / "all bits set"
|
|
34523
|
+
"100",
|
|
34524
|
+
// percent literal, very common
|
|
34525
|
+
"0.0",
|
|
34526
|
+
"0.5",
|
|
34527
|
+
"1.0",
|
|
34528
|
+
"2.0",
|
|
34529
|
+
// common probability / ratio
|
|
34530
|
+
"4096",
|
|
34531
|
+
// page size, hash bucket count
|
|
34532
|
+
"2048",
|
|
34533
|
+
"512",
|
|
34534
|
+
// power-of-2 sizes
|
|
34535
|
+
"32",
|
|
34536
|
+
"64",
|
|
34537
|
+
"128",
|
|
34538
|
+
// bit widths, byte sizes
|
|
34539
|
+
"16",
|
|
34540
|
+
"8",
|
|
34541
|
+
// common small constants
|
|
34542
|
+
"50"
|
|
34543
|
+
// percentile literal
|
|
34519
34544
|
]);
|
|
34520
34545
|
for (let i = 0; i < lines.length; i++) {
|
|
34521
34546
|
const line = lines[i] ?? "";
|
|
34522
34547
|
if (!COMPARE_OR_RETURN_REGEX.test(line)) continue;
|
|
34548
|
+
const codeLine = line.replace(/\/\/.*$/, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
34523
34549
|
let m;
|
|
34524
34550
|
MAGIC_NUMBER_REGEX.lastIndex = 0;
|
|
34525
|
-
while ((m = MAGIC_NUMBER_REGEX.exec(
|
|
34551
|
+
while ((m = MAGIC_NUMBER_REGEX.exec(codeLine)) !== null) {
|
|
34526
34552
|
const literal = m[0] ?? "";
|
|
34527
34553
|
if (allowSet.has(literal)) continue;
|
|
34528
|
-
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(
|
|
34554
|
+
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(codeLine.slice(0, m.index).trimEnd())) continue;
|
|
34529
34555
|
const prevLine = i > 0 ? lines[i - 1] ?? "" : "";
|
|
34530
34556
|
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(prevLine.trim())) continue;
|
|
34531
34557
|
issues.push({
|
|
@@ -34536,7 +34562,7 @@ var cppMagicNumbersRule = createRule({
|
|
|
34536
34562
|
message: `magic number ${literal} at line ${i + 1} \u2014 name it: constexpr int MAX = ${literal};`,
|
|
34537
34563
|
line: i + 1,
|
|
34538
34564
|
column: 1,
|
|
34539
|
-
advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.
|
|
34565
|
+
advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.34.4 (expanded allowSet + string/comment exclusion).'
|
|
34540
34566
|
});
|
|
34541
34567
|
}
|
|
34542
34568
|
}
|
|
@@ -37189,7 +37215,6 @@ var javaSystemOutPrintlnRule = createRule({
|
|
|
37189
37215
|
});
|
|
37190
37216
|
|
|
37191
37217
|
// src/rules/java/thread-sleep-in-loop.ts
|
|
37192
|
-
var THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
|
|
37193
37218
|
var javaThreadSleepInLoopRule = createRule({
|
|
37194
37219
|
id: "java/thread-sleep-in-loop",
|
|
37195
37220
|
category: "perf",
|
|
@@ -37205,11 +37230,104 @@ var javaThreadSleepInLoopRule = createRule({
|
|
|
37205
37230
|
if (!source) return issues;
|
|
37206
37231
|
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37207
37232
|
if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
|
|
37208
|
-
|
|
37209
|
-
|
|
37210
|
-
|
|
37211
|
-
|
|
37212
|
-
|
|
37233
|
+
const braceStack = [];
|
|
37234
|
+
const loopSet = /* @__PURE__ */ new Set();
|
|
37235
|
+
let pendingLoopKeyword = null;
|
|
37236
|
+
let parenDepth = 0;
|
|
37237
|
+
const sleepEvents = [];
|
|
37238
|
+
let inString = false;
|
|
37239
|
+
let inLineComment = false;
|
|
37240
|
+
let inBlockComment = false;
|
|
37241
|
+
for (let i = 0; i < source.length; i++) {
|
|
37242
|
+
const c = source[i] ?? "";
|
|
37243
|
+
const next = source[i + 1] ?? "";
|
|
37244
|
+
const prev = source[i - 1] ?? "";
|
|
37245
|
+
if (inLineComment) {
|
|
37246
|
+
if (c === "\n") inLineComment = false;
|
|
37247
|
+
continue;
|
|
37248
|
+
}
|
|
37249
|
+
if (inBlockComment) {
|
|
37250
|
+
if (c === "*" && next === "/") {
|
|
37251
|
+
inBlockComment = false;
|
|
37252
|
+
i++;
|
|
37253
|
+
}
|
|
37254
|
+
continue;
|
|
37255
|
+
}
|
|
37256
|
+
if (inString) {
|
|
37257
|
+
if (c === "\\") {
|
|
37258
|
+
i++;
|
|
37259
|
+
continue;
|
|
37260
|
+
}
|
|
37261
|
+
if (c === inString) inString = false;
|
|
37262
|
+
continue;
|
|
37263
|
+
}
|
|
37264
|
+
if (c === "/" && next === "/") {
|
|
37265
|
+
inLineComment = true;
|
|
37266
|
+
i++;
|
|
37267
|
+
continue;
|
|
37268
|
+
}
|
|
37269
|
+
if (c === "/" && next === "*") {
|
|
37270
|
+
inBlockComment = true;
|
|
37271
|
+
i++;
|
|
37272
|
+
continue;
|
|
37273
|
+
}
|
|
37274
|
+
if (c === '"' || c === "'") {
|
|
37275
|
+
inString = c;
|
|
37276
|
+
continue;
|
|
37277
|
+
}
|
|
37278
|
+
if (c === "(") {
|
|
37279
|
+
parenDepth++;
|
|
37280
|
+
continue;
|
|
37281
|
+
}
|
|
37282
|
+
if (c === ")") {
|
|
37283
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
37284
|
+
continue;
|
|
37285
|
+
}
|
|
37286
|
+
if (/[A-Za-z_]/.test(c) && !/[A-Za-z0-9_]/.test(prev)) {
|
|
37287
|
+
const next3 = source.slice(i, i + 3);
|
|
37288
|
+
const next5 = source.slice(i, i + 5);
|
|
37289
|
+
const next2 = source.slice(i, i + 2);
|
|
37290
|
+
const after3 = source[i + 3] ?? "";
|
|
37291
|
+
const after5 = source[i + 5] ?? "";
|
|
37292
|
+
const after2 = source[i + 2] ?? "";
|
|
37293
|
+
if (next3 === "for" && !/[A-Za-z0-9_]/.test(after3)) {
|
|
37294
|
+
pendingLoopKeyword = { kind: "for", idx: i };
|
|
37295
|
+
i += 2;
|
|
37296
|
+
continue;
|
|
37297
|
+
}
|
|
37298
|
+
if (next5 === "while" && !/[A-Za-z0-9_]/.test(after5)) {
|
|
37299
|
+
pendingLoopKeyword = { kind: "while", idx: i };
|
|
37300
|
+
i += 4;
|
|
37301
|
+
continue;
|
|
37302
|
+
}
|
|
37303
|
+
if (next2 === "do" && !/[A-Za-z0-9_]/.test(after2)) {
|
|
37304
|
+
pendingLoopKeyword = { kind: "do", idx: i };
|
|
37305
|
+
i += 1;
|
|
37306
|
+
continue;
|
|
37307
|
+
}
|
|
37308
|
+
}
|
|
37309
|
+
if (c === "{") {
|
|
37310
|
+
braceStack.push(i);
|
|
37311
|
+
if (pendingLoopKeyword && parenDepth === 0) {
|
|
37312
|
+
loopSet.add(i);
|
|
37313
|
+
pendingLoopKeyword = null;
|
|
37314
|
+
}
|
|
37315
|
+
} else if (c === "}") {
|
|
37316
|
+
const popped = braceStack.pop();
|
|
37317
|
+
if (popped !== void 0 && loopSet.has(popped)) {
|
|
37318
|
+
loopSet.delete(popped);
|
|
37319
|
+
}
|
|
37320
|
+
if (pendingLoopKeyword) {
|
|
37321
|
+
pendingLoopKeyword = null;
|
|
37322
|
+
}
|
|
37323
|
+
} else if (c === "T" && source.slice(i, i + 13) === "Thread.sleep(") {
|
|
37324
|
+
sleepEvents.push({ idx: i, loopDepth: loopSet.size });
|
|
37325
|
+
i += 12;
|
|
37326
|
+
}
|
|
37327
|
+
}
|
|
37328
|
+
for (const ev of sleepEvents) {
|
|
37329
|
+
if (ev.loopDepth === 0) continue;
|
|
37330
|
+
const line = source.slice(0, ev.idx).split("\n").length;
|
|
37213
37331
|
issues.push({
|
|
37214
37332
|
ruleId: "java/thread-sleep-in-loop",
|
|
37215
37333
|
category: "perf",
|
|
@@ -37218,7 +37336,7 @@ var javaThreadSleepInLoopRule = createRule({
|
|
|
37218
37336
|
message: `Thread.sleep() at line ${line}`,
|
|
37219
37337
|
line,
|
|
37220
37338
|
column: 1,
|
|
37221
|
-
advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.
|
|
37339
|
+
advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.34.6 (refined to require Thread.sleep inside the loop block, not just in the file).'
|
|
37222
37340
|
});
|
|
37223
37341
|
}
|
|
37224
37342
|
return issues;
|
|
@@ -37465,6 +37583,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
|
|
|
37465
37583
|
// src/rules/kotlin/println-as-log.ts
|
|
37466
37584
|
var PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
37467
37585
|
var REAL_LOGGING_IMPORT_REGEX2 = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
|
|
37586
|
+
var TEST_FILE_REGEX = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
|
|
37468
37587
|
var kotlinPrintlnAsLogRule = createRule({
|
|
37469
37588
|
id: "kotlin/println-as-log",
|
|
37470
37589
|
category: "logic",
|
|
@@ -37479,7 +37598,7 @@ var kotlinPrintlnAsLogRule = createRule({
|
|
|
37479
37598
|
const source = facts.v2?._source;
|
|
37480
37599
|
if (!source) return issues;
|
|
37481
37600
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37482
|
-
if (
|
|
37601
|
+
if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
|
|
37483
37602
|
if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
|
|
37484
37603
|
let m;
|
|
37485
37604
|
PRINTLN_REGEX.lastIndex = 0;
|
|
@@ -37493,7 +37612,7 @@ var kotlinPrintlnAsLogRule = createRule({
|
|
|
37493
37612
|
message: `println() as logger at line ${line}`,
|
|
37494
37613
|
line,
|
|
37495
37614
|
column: 1,
|
|
37496
|
-
advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.
|
|
37615
|
+
advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.34.5 (refined to skip test files for higher precision)."
|
|
37497
37616
|
});
|
|
37498
37617
|
}
|
|
37499
37618
|
return issues;
|
|
@@ -40516,7 +40635,7 @@ var swiftImplicitlyUnwrappedOptionalRule = createRule({
|
|
|
40516
40635
|
// src/rules/swift/print-debug.ts
|
|
40517
40636
|
var PRINT_REGEX = /\bprint\s*\(/g;
|
|
40518
40637
|
var DEFAULT_THRESHOLD2 = 1;
|
|
40519
|
-
var
|
|
40638
|
+
var TEST_FILE_REGEX2 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
|
|
40520
40639
|
var swiftPrintDebugRule = createRule({
|
|
40521
40640
|
id: "swift/print-debug",
|
|
40522
40641
|
category: "typo",
|
|
@@ -40531,7 +40650,7 @@ var swiftPrintDebugRule = createRule({
|
|
|
40531
40650
|
const source = facts.v2?._source;
|
|
40532
40651
|
if (!source) return issues;
|
|
40533
40652
|
if (!/\.swift$/i.test(facts.filePath)) return issues;
|
|
40534
|
-
if (
|
|
40653
|
+
if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
|
|
40535
40654
|
const matches = [];
|
|
40536
40655
|
let m;
|
|
40537
40656
|
PRINT_REGEX.lastIndex = 0;
|
|
@@ -45467,7 +45586,7 @@ var signal_strength_default = {
|
|
|
45467
45586
|
precision: 0.1268,
|
|
45468
45587
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45469
45588
|
verdict: "OK",
|
|
45470
|
-
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
|
|
45589
|
+
_calibrationNote: "v0.34.5: REFINED \u2014 rule now skips test files (JUnit4/5 + Android: *Tests.kt, *Test.kt, src/test/, test/ dirs). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): ratio=1.84 (\u22651.5, OK). The refinement mirrors v0.34.2's swift/print-debug fix: the previous exclusion only matched `\\/test\\/` and `.test.kts?$`, missing JUnit5's `FooTests.kt` naming. Expected post-refinement precision: 12.7% \u2192 25%+ by removing the JUnit assertion / debug-output fires that were ~50% of the FPs. Same direction as java/system-out-println (1.73 refined) and the v0.34.2 swift/print-debug pipeline. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
|
|
45471
45590
|
aiSpecific: false,
|
|
45472
45591
|
_v9Verdict: "OK",
|
|
45473
45592
|
_v9Lift: 1.84,
|
|
@@ -45531,7 +45650,7 @@ var signal_strength_default = {
|
|
|
45531
45650
|
precision: 0.1091,
|
|
45532
45651
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45533
45652
|
verdict: "DORMANT",
|
|
45534
|
-
_calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
|
|
45653
|
+
_calibrationNote: "v0.34.6: REFINED \u2014 rule now requires Thread.sleep to be INSIDE the loop block (verified via brace-counting), not just in a file that happens to contain a for/while/do keyword. v0.30 baseline: 2154 total fires, ratio 0.97 (DORMANT). The refinement targets: (1) Thread.sleep in `main()` no longer fires if a different method has a `for` loop (the v0.30 heuristic fired on every Thread.sleep in the file); (2) Thread.sleep before/after a loop block (not inside it) no longer fires; (3) string literals containing `Thread.sleep(...)` are skipped via the string-state-machine. Expected post-refinement ratio: 1.5+ (positive-signal OK verdict) by removing the cross-method over-fires. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
|
|
45535
45654
|
aiSpecific: false,
|
|
45536
45655
|
_v9Verdict: "DORMANT",
|
|
45537
45656
|
_v9Lift: 0.97,
|
|
@@ -45786,7 +45905,7 @@ var signal_strength_default = {
|
|
|
45786
45905
|
precision: 0.2187,
|
|
45787
45906
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45788
45907
|
verdict: "DORMANT",
|
|
45789
|
-
_calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
|
|
45908
|
+
_calibrationNote: "v0.34.4: REFINED \u2014 expanded allowSet with common constants (-1 sentinel, 100 percent literal, 0.5/1.0/2.0 ratios, 4096/2048/512/128/64/32/16/8 power-of-2 sizes, 50 percentile). Added string-literal and `//` comment stripping so substrings like `\"got 42 errors\"` and `// ticket #4242` no longer fire. v0.33 baseline: 220 TP / 786 FP per-file (ratio 0.86, DORMANT). The refinement targets: (1) sentinel `-1` literals are skipped because `1` is allowlisted and the `-` doesn't match MAGIC_NUMBER_REGEX anyway; (2) hex literals (`0xFF`) are skipped because the digit-only regex requires a word-boundary after digits, and `0x...` has none; (3) literals inside string/comment contexts no longer fire. Expected post-refinement ratio: 1.0-1.2 (still DORMANT but better-targeted). Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
|
|
45790
45909
|
aiSpecific: true,
|
|
45791
45910
|
_v7Verdict: "DORMANT",
|
|
45792
45911
|
_v7Lift: 1,
|
package/dist/engine/worker.js
CHANGED
|
@@ -34468,6 +34468,7 @@ var cppMagicNumbersRule = createRule({
|
|
|
34468
34468
|
if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
|
|
34469
34469
|
const lines = source.split("\n");
|
|
34470
34470
|
const allowSet = /* @__PURE__ */ new Set([
|
|
34471
|
+
// v0.24 originals
|
|
34471
34472
|
"1024",
|
|
34472
34473
|
"65535",
|
|
34473
34474
|
"65536",
|
|
@@ -34486,17 +34487,42 @@ var cppMagicNumbersRule = createRule({
|
|
|
34486
34487
|
"2",
|
|
34487
34488
|
"3",
|
|
34488
34489
|
"4",
|
|
34489
|
-
"5"
|
|
34490
|
+
"5",
|
|
34491
|
+
// v0.34.4 additions
|
|
34492
|
+
"-1",
|
|
34493
|
+
// sentinel value for "not found" / "all bits set"
|
|
34494
|
+
"100",
|
|
34495
|
+
// percent literal, very common
|
|
34496
|
+
"0.0",
|
|
34497
|
+
"0.5",
|
|
34498
|
+
"1.0",
|
|
34499
|
+
"2.0",
|
|
34500
|
+
// common probability / ratio
|
|
34501
|
+
"4096",
|
|
34502
|
+
// page size, hash bucket count
|
|
34503
|
+
"2048",
|
|
34504
|
+
"512",
|
|
34505
|
+
// power-of-2 sizes
|
|
34506
|
+
"32",
|
|
34507
|
+
"64",
|
|
34508
|
+
"128",
|
|
34509
|
+
// bit widths, byte sizes
|
|
34510
|
+
"16",
|
|
34511
|
+
"8",
|
|
34512
|
+
// common small constants
|
|
34513
|
+
"50"
|
|
34514
|
+
// percentile literal
|
|
34490
34515
|
]);
|
|
34491
34516
|
for (let i = 0; i < lines.length; i++) {
|
|
34492
34517
|
const line = lines[i] ?? "";
|
|
34493
34518
|
if (!COMPARE_OR_RETURN_REGEX.test(line)) continue;
|
|
34519
|
+
const codeLine = line.replace(/\/\/.*$/, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
34494
34520
|
let m;
|
|
34495
34521
|
MAGIC_NUMBER_REGEX.lastIndex = 0;
|
|
34496
|
-
while ((m = MAGIC_NUMBER_REGEX.exec(
|
|
34522
|
+
while ((m = MAGIC_NUMBER_REGEX.exec(codeLine)) !== null) {
|
|
34497
34523
|
const literal = m[0] ?? "";
|
|
34498
34524
|
if (allowSet.has(literal)) continue;
|
|
34499
|
-
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(
|
|
34525
|
+
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(codeLine.slice(0, m.index).trimEnd())) continue;
|
|
34500
34526
|
const prevLine = i > 0 ? lines[i - 1] ?? "" : "";
|
|
34501
34527
|
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(prevLine.trim())) continue;
|
|
34502
34528
|
issues.push({
|
|
@@ -34507,7 +34533,7 @@ var cppMagicNumbersRule = createRule({
|
|
|
34507
34533
|
message: `magic number ${literal} at line ${i + 1} \u2014 name it: constexpr int MAX = ${literal};`,
|
|
34508
34534
|
line: i + 1,
|
|
34509
34535
|
column: 1,
|
|
34510
|
-
advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.
|
|
34536
|
+
advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.34.4 (expanded allowSet + string/comment exclusion).'
|
|
34511
34537
|
});
|
|
34512
34538
|
}
|
|
34513
34539
|
}
|
|
@@ -37160,7 +37186,6 @@ var javaSystemOutPrintlnRule = createRule({
|
|
|
37160
37186
|
});
|
|
37161
37187
|
|
|
37162
37188
|
// src/rules/java/thread-sleep-in-loop.ts
|
|
37163
|
-
var THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
|
|
37164
37189
|
var javaThreadSleepInLoopRule = createRule({
|
|
37165
37190
|
id: "java/thread-sleep-in-loop",
|
|
37166
37191
|
category: "perf",
|
|
@@ -37176,11 +37201,104 @@ var javaThreadSleepInLoopRule = createRule({
|
|
|
37176
37201
|
if (!source) return issues;
|
|
37177
37202
|
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37178
37203
|
if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
|
|
37179
|
-
|
|
37180
|
-
|
|
37181
|
-
|
|
37182
|
-
|
|
37183
|
-
|
|
37204
|
+
const braceStack = [];
|
|
37205
|
+
const loopSet = /* @__PURE__ */ new Set();
|
|
37206
|
+
let pendingLoopKeyword = null;
|
|
37207
|
+
let parenDepth = 0;
|
|
37208
|
+
const sleepEvents = [];
|
|
37209
|
+
let inString = false;
|
|
37210
|
+
let inLineComment = false;
|
|
37211
|
+
let inBlockComment = false;
|
|
37212
|
+
for (let i = 0; i < source.length; i++) {
|
|
37213
|
+
const c = source[i] ?? "";
|
|
37214
|
+
const next = source[i + 1] ?? "";
|
|
37215
|
+
const prev = source[i - 1] ?? "";
|
|
37216
|
+
if (inLineComment) {
|
|
37217
|
+
if (c === "\n") inLineComment = false;
|
|
37218
|
+
continue;
|
|
37219
|
+
}
|
|
37220
|
+
if (inBlockComment) {
|
|
37221
|
+
if (c === "*" && next === "/") {
|
|
37222
|
+
inBlockComment = false;
|
|
37223
|
+
i++;
|
|
37224
|
+
}
|
|
37225
|
+
continue;
|
|
37226
|
+
}
|
|
37227
|
+
if (inString) {
|
|
37228
|
+
if (c === "\\") {
|
|
37229
|
+
i++;
|
|
37230
|
+
continue;
|
|
37231
|
+
}
|
|
37232
|
+
if (c === inString) inString = false;
|
|
37233
|
+
continue;
|
|
37234
|
+
}
|
|
37235
|
+
if (c === "/" && next === "/") {
|
|
37236
|
+
inLineComment = true;
|
|
37237
|
+
i++;
|
|
37238
|
+
continue;
|
|
37239
|
+
}
|
|
37240
|
+
if (c === "/" && next === "*") {
|
|
37241
|
+
inBlockComment = true;
|
|
37242
|
+
i++;
|
|
37243
|
+
continue;
|
|
37244
|
+
}
|
|
37245
|
+
if (c === '"' || c === "'") {
|
|
37246
|
+
inString = c;
|
|
37247
|
+
continue;
|
|
37248
|
+
}
|
|
37249
|
+
if (c === "(") {
|
|
37250
|
+
parenDepth++;
|
|
37251
|
+
continue;
|
|
37252
|
+
}
|
|
37253
|
+
if (c === ")") {
|
|
37254
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
37255
|
+
continue;
|
|
37256
|
+
}
|
|
37257
|
+
if (/[A-Za-z_]/.test(c) && !/[A-Za-z0-9_]/.test(prev)) {
|
|
37258
|
+
const next3 = source.slice(i, i + 3);
|
|
37259
|
+
const next5 = source.slice(i, i + 5);
|
|
37260
|
+
const next2 = source.slice(i, i + 2);
|
|
37261
|
+
const after3 = source[i + 3] ?? "";
|
|
37262
|
+
const after5 = source[i + 5] ?? "";
|
|
37263
|
+
const after2 = source[i + 2] ?? "";
|
|
37264
|
+
if (next3 === "for" && !/[A-Za-z0-9_]/.test(after3)) {
|
|
37265
|
+
pendingLoopKeyword = { kind: "for", idx: i };
|
|
37266
|
+
i += 2;
|
|
37267
|
+
continue;
|
|
37268
|
+
}
|
|
37269
|
+
if (next5 === "while" && !/[A-Za-z0-9_]/.test(after5)) {
|
|
37270
|
+
pendingLoopKeyword = { kind: "while", idx: i };
|
|
37271
|
+
i += 4;
|
|
37272
|
+
continue;
|
|
37273
|
+
}
|
|
37274
|
+
if (next2 === "do" && !/[A-Za-z0-9_]/.test(after2)) {
|
|
37275
|
+
pendingLoopKeyword = { kind: "do", idx: i };
|
|
37276
|
+
i += 1;
|
|
37277
|
+
continue;
|
|
37278
|
+
}
|
|
37279
|
+
}
|
|
37280
|
+
if (c === "{") {
|
|
37281
|
+
braceStack.push(i);
|
|
37282
|
+
if (pendingLoopKeyword && parenDepth === 0) {
|
|
37283
|
+
loopSet.add(i);
|
|
37284
|
+
pendingLoopKeyword = null;
|
|
37285
|
+
}
|
|
37286
|
+
} else if (c === "}") {
|
|
37287
|
+
const popped = braceStack.pop();
|
|
37288
|
+
if (popped !== void 0 && loopSet.has(popped)) {
|
|
37289
|
+
loopSet.delete(popped);
|
|
37290
|
+
}
|
|
37291
|
+
if (pendingLoopKeyword) {
|
|
37292
|
+
pendingLoopKeyword = null;
|
|
37293
|
+
}
|
|
37294
|
+
} else if (c === "T" && source.slice(i, i + 13) === "Thread.sleep(") {
|
|
37295
|
+
sleepEvents.push({ idx: i, loopDepth: loopSet.size });
|
|
37296
|
+
i += 12;
|
|
37297
|
+
}
|
|
37298
|
+
}
|
|
37299
|
+
for (const ev of sleepEvents) {
|
|
37300
|
+
if (ev.loopDepth === 0) continue;
|
|
37301
|
+
const line = source.slice(0, ev.idx).split("\n").length;
|
|
37184
37302
|
issues.push({
|
|
37185
37303
|
ruleId: "java/thread-sleep-in-loop",
|
|
37186
37304
|
category: "perf",
|
|
@@ -37189,7 +37307,7 @@ var javaThreadSleepInLoopRule = createRule({
|
|
|
37189
37307
|
message: `Thread.sleep() at line ${line}`,
|
|
37190
37308
|
line,
|
|
37191
37309
|
column: 1,
|
|
37192
|
-
advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.
|
|
37310
|
+
advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.34.6 (refined to require Thread.sleep inside the loop block, not just in the file).'
|
|
37193
37311
|
});
|
|
37194
37312
|
}
|
|
37195
37313
|
return issues;
|
|
@@ -37436,6 +37554,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
|
|
|
37436
37554
|
// src/rules/kotlin/println-as-log.ts
|
|
37437
37555
|
var PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
37438
37556
|
var REAL_LOGGING_IMPORT_REGEX2 = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
|
|
37557
|
+
var TEST_FILE_REGEX = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
|
|
37439
37558
|
var kotlinPrintlnAsLogRule = createRule({
|
|
37440
37559
|
id: "kotlin/println-as-log",
|
|
37441
37560
|
category: "logic",
|
|
@@ -37450,7 +37569,7 @@ var kotlinPrintlnAsLogRule = createRule({
|
|
|
37450
37569
|
const source = facts.v2?._source;
|
|
37451
37570
|
if (!source) return issues;
|
|
37452
37571
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37453
|
-
if (
|
|
37572
|
+
if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
|
|
37454
37573
|
if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
|
|
37455
37574
|
let m;
|
|
37456
37575
|
PRINTLN_REGEX.lastIndex = 0;
|
|
@@ -37464,7 +37583,7 @@ var kotlinPrintlnAsLogRule = createRule({
|
|
|
37464
37583
|
message: `println() as logger at line ${line}`,
|
|
37465
37584
|
line,
|
|
37466
37585
|
column: 1,
|
|
37467
|
-
advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.
|
|
37586
|
+
advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.34.5 (refined to skip test files for higher precision)."
|
|
37468
37587
|
});
|
|
37469
37588
|
}
|
|
37470
37589
|
return issues;
|
|
@@ -40487,7 +40606,7 @@ var swiftImplicitlyUnwrappedOptionalRule = createRule({
|
|
|
40487
40606
|
// src/rules/swift/print-debug.ts
|
|
40488
40607
|
var PRINT_REGEX = /\bprint\s*\(/g;
|
|
40489
40608
|
var DEFAULT_THRESHOLD2 = 1;
|
|
40490
|
-
var
|
|
40609
|
+
var TEST_FILE_REGEX2 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
|
|
40491
40610
|
var swiftPrintDebugRule = createRule({
|
|
40492
40611
|
id: "swift/print-debug",
|
|
40493
40612
|
category: "typo",
|
|
@@ -40502,7 +40621,7 @@ var swiftPrintDebugRule = createRule({
|
|
|
40502
40621
|
const source = facts.v2?._source;
|
|
40503
40622
|
if (!source) return issues;
|
|
40504
40623
|
if (!/\.swift$/i.test(facts.filePath)) return issues;
|
|
40505
|
-
if (
|
|
40624
|
+
if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
|
|
40506
40625
|
const matches = [];
|
|
40507
40626
|
let m;
|
|
40508
40627
|
PRINT_REGEX.lastIndex = 0;
|
|
@@ -45438,7 +45557,7 @@ var signal_strength_default = {
|
|
|
45438
45557
|
precision: 0.1268,
|
|
45439
45558
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45440
45559
|
verdict: "OK",
|
|
45441
|
-
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
|
|
45560
|
+
_calibrationNote: "v0.34.5: REFINED \u2014 rule now skips test files (JUnit4/5 + Android: *Tests.kt, *Test.kt, src/test/, test/ dirs). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): ratio=1.84 (\u22651.5, OK). The refinement mirrors v0.34.2's swift/print-debug fix: the previous exclusion only matched `\\/test\\/` and `.test.kts?$`, missing JUnit5's `FooTests.kt` naming. Expected post-refinement precision: 12.7% \u2192 25%+ by removing the JUnit assertion / debug-output fires that were ~50% of the FPs. Same direction as java/system-out-println (1.73 refined) and the v0.34.2 swift/print-debug pipeline. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
|
|
45442
45561
|
aiSpecific: false,
|
|
45443
45562
|
_v9Verdict: "OK",
|
|
45444
45563
|
_v9Lift: 1.84,
|
|
@@ -45502,7 +45621,7 @@ var signal_strength_default = {
|
|
|
45502
45621
|
precision: 0.1091,
|
|
45503
45622
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45504
45623
|
verdict: "DORMANT",
|
|
45505
|
-
_calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
|
|
45624
|
+
_calibrationNote: "v0.34.6: REFINED \u2014 rule now requires Thread.sleep to be INSIDE the loop block (verified via brace-counting), not just in a file that happens to contain a for/while/do keyword. v0.30 baseline: 2154 total fires, ratio 0.97 (DORMANT). The refinement targets: (1) Thread.sleep in `main()` no longer fires if a different method has a `for` loop (the v0.30 heuristic fired on every Thread.sleep in the file); (2) Thread.sleep before/after a loop block (not inside it) no longer fires; (3) string literals containing `Thread.sleep(...)` are skipped via the string-state-machine. Expected post-refinement ratio: 1.5+ (positive-signal OK verdict) by removing the cross-method over-fires. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
|
|
45506
45625
|
aiSpecific: false,
|
|
45507
45626
|
_v9Verdict: "DORMANT",
|
|
45508
45627
|
_v9Lift: 0.97,
|
|
@@ -45757,7 +45876,7 @@ var signal_strength_default = {
|
|
|
45757
45876
|
precision: 0.2187,
|
|
45758
45877
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45759
45878
|
verdict: "DORMANT",
|
|
45760
|
-
_calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
|
|
45879
|
+
_calibrationNote: "v0.34.4: REFINED \u2014 expanded allowSet with common constants (-1 sentinel, 100 percent literal, 0.5/1.0/2.0 ratios, 4096/2048/512/128/64/32/16/8 power-of-2 sizes, 50 percentile). Added string-literal and `//` comment stripping so substrings like `\"got 42 errors\"` and `// ticket #4242` no longer fire. v0.33 baseline: 220 TP / 786 FP per-file (ratio 0.86, DORMANT). The refinement targets: (1) sentinel `-1` literals are skipped because `1` is allowlisted and the `-` doesn't match MAGIC_NUMBER_REGEX anyway; (2) hex literals (`0xFF`) are skipped because the digit-only regex requires a word-boundary after digits, and `0x...` has none; (3) literals inside string/comment contexts no longer fire. Expected post-refinement ratio: 1.0-1.2 (still DORMANT but better-targeted). Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
|
|
45761
45880
|
aiSpecific: true,
|
|
45762
45881
|
_v7Verdict: "DORMANT",
|
|
45763
45882
|
_v7Lift: 1,
|
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ var VERSION;
|
|
|
36
36
|
var init_header = __esm({
|
|
37
37
|
"src/types/_header.ts"() {
|
|
38
38
|
"use strict";
|
|
39
|
-
VERSION = "0.34.
|
|
39
|
+
VERSION = "0.34.6";
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -27734,6 +27734,7 @@ var init_magic_numbers = __esm({
|
|
|
27734
27734
|
if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
|
|
27735
27735
|
const lines = source.split("\n");
|
|
27736
27736
|
const allowSet = /* @__PURE__ */ new Set([
|
|
27737
|
+
// v0.24 originals
|
|
27737
27738
|
"1024",
|
|
27738
27739
|
"65535",
|
|
27739
27740
|
"65536",
|
|
@@ -27752,17 +27753,42 @@ var init_magic_numbers = __esm({
|
|
|
27752
27753
|
"2",
|
|
27753
27754
|
"3",
|
|
27754
27755
|
"4",
|
|
27755
|
-
"5"
|
|
27756
|
+
"5",
|
|
27757
|
+
// v0.34.4 additions
|
|
27758
|
+
"-1",
|
|
27759
|
+
// sentinel value for "not found" / "all bits set"
|
|
27760
|
+
"100",
|
|
27761
|
+
// percent literal, very common
|
|
27762
|
+
"0.0",
|
|
27763
|
+
"0.5",
|
|
27764
|
+
"1.0",
|
|
27765
|
+
"2.0",
|
|
27766
|
+
// common probability / ratio
|
|
27767
|
+
"4096",
|
|
27768
|
+
// page size, hash bucket count
|
|
27769
|
+
"2048",
|
|
27770
|
+
"512",
|
|
27771
|
+
// power-of-2 sizes
|
|
27772
|
+
"32",
|
|
27773
|
+
"64",
|
|
27774
|
+
"128",
|
|
27775
|
+
// bit widths, byte sizes
|
|
27776
|
+
"16",
|
|
27777
|
+
"8",
|
|
27778
|
+
// common small constants
|
|
27779
|
+
"50"
|
|
27780
|
+
// percentile literal
|
|
27756
27781
|
]);
|
|
27757
27782
|
for (let i = 0; i < lines.length; i++) {
|
|
27758
27783
|
const line = lines[i] ?? "";
|
|
27759
27784
|
if (!COMPARE_OR_RETURN_REGEX.test(line)) continue;
|
|
27785
|
+
const codeLine = line.replace(/\/\/.*$/, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
27760
27786
|
let m;
|
|
27761
27787
|
MAGIC_NUMBER_REGEX.lastIndex = 0;
|
|
27762
|
-
while ((m = MAGIC_NUMBER_REGEX.exec(
|
|
27788
|
+
while ((m = MAGIC_NUMBER_REGEX.exec(codeLine)) !== null) {
|
|
27763
27789
|
const literal = m[0] ?? "";
|
|
27764
27790
|
if (allowSet.has(literal)) continue;
|
|
27765
|
-
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(
|
|
27791
|
+
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(codeLine.slice(0, m.index).trimEnd())) continue;
|
|
27766
27792
|
const prevLine = i > 0 ? lines[i - 1] ?? "" : "";
|
|
27767
27793
|
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(prevLine.trim())) continue;
|
|
27768
27794
|
issues.push({
|
|
@@ -27773,7 +27799,7 @@ var init_magic_numbers = __esm({
|
|
|
27773
27799
|
message: `magic number ${literal} at line ${i + 1} \u2014 name it: constexpr int MAX = ${literal};`,
|
|
27774
27800
|
line: i + 1,
|
|
27775
27801
|
column: 1,
|
|
27776
|
-
advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.
|
|
27802
|
+
advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.34.4 (expanded allowSet + string/comment exclusion).'
|
|
27777
27803
|
});
|
|
27778
27804
|
}
|
|
27779
27805
|
}
|
|
@@ -30861,12 +30887,11 @@ var init_system_out_println = __esm({
|
|
|
30861
30887
|
});
|
|
30862
30888
|
|
|
30863
30889
|
// src/rules/java/thread-sleep-in-loop.ts
|
|
30864
|
-
var
|
|
30890
|
+
var javaThreadSleepInLoopRule;
|
|
30865
30891
|
var init_thread_sleep_in_loop = __esm({
|
|
30866
30892
|
"src/rules/java/thread-sleep-in-loop.ts"() {
|
|
30867
30893
|
"use strict";
|
|
30868
30894
|
init_rule();
|
|
30869
|
-
THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
|
|
30870
30895
|
javaThreadSleepInLoopRule = createRule({
|
|
30871
30896
|
id: "java/thread-sleep-in-loop",
|
|
30872
30897
|
category: "perf",
|
|
@@ -30882,11 +30907,104 @@ var init_thread_sleep_in_loop = __esm({
|
|
|
30882
30907
|
if (!source) return issues;
|
|
30883
30908
|
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30884
30909
|
if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
|
|
30885
|
-
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
30910
|
+
const braceStack = [];
|
|
30911
|
+
const loopSet = /* @__PURE__ */ new Set();
|
|
30912
|
+
let pendingLoopKeyword = null;
|
|
30913
|
+
let parenDepth = 0;
|
|
30914
|
+
const sleepEvents = [];
|
|
30915
|
+
let inString = false;
|
|
30916
|
+
let inLineComment = false;
|
|
30917
|
+
let inBlockComment = false;
|
|
30918
|
+
for (let i = 0; i < source.length; i++) {
|
|
30919
|
+
const c = source[i] ?? "";
|
|
30920
|
+
const next = source[i + 1] ?? "";
|
|
30921
|
+
const prev = source[i - 1] ?? "";
|
|
30922
|
+
if (inLineComment) {
|
|
30923
|
+
if (c === "\n") inLineComment = false;
|
|
30924
|
+
continue;
|
|
30925
|
+
}
|
|
30926
|
+
if (inBlockComment) {
|
|
30927
|
+
if (c === "*" && next === "/") {
|
|
30928
|
+
inBlockComment = false;
|
|
30929
|
+
i++;
|
|
30930
|
+
}
|
|
30931
|
+
continue;
|
|
30932
|
+
}
|
|
30933
|
+
if (inString) {
|
|
30934
|
+
if (c === "\\") {
|
|
30935
|
+
i++;
|
|
30936
|
+
continue;
|
|
30937
|
+
}
|
|
30938
|
+
if (c === inString) inString = false;
|
|
30939
|
+
continue;
|
|
30940
|
+
}
|
|
30941
|
+
if (c === "/" && next === "/") {
|
|
30942
|
+
inLineComment = true;
|
|
30943
|
+
i++;
|
|
30944
|
+
continue;
|
|
30945
|
+
}
|
|
30946
|
+
if (c === "/" && next === "*") {
|
|
30947
|
+
inBlockComment = true;
|
|
30948
|
+
i++;
|
|
30949
|
+
continue;
|
|
30950
|
+
}
|
|
30951
|
+
if (c === '"' || c === "'") {
|
|
30952
|
+
inString = c;
|
|
30953
|
+
continue;
|
|
30954
|
+
}
|
|
30955
|
+
if (c === "(") {
|
|
30956
|
+
parenDepth++;
|
|
30957
|
+
continue;
|
|
30958
|
+
}
|
|
30959
|
+
if (c === ")") {
|
|
30960
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
30961
|
+
continue;
|
|
30962
|
+
}
|
|
30963
|
+
if (/[A-Za-z_]/.test(c) && !/[A-Za-z0-9_]/.test(prev)) {
|
|
30964
|
+
const next3 = source.slice(i, i + 3);
|
|
30965
|
+
const next5 = source.slice(i, i + 5);
|
|
30966
|
+
const next2 = source.slice(i, i + 2);
|
|
30967
|
+
const after3 = source[i + 3] ?? "";
|
|
30968
|
+
const after5 = source[i + 5] ?? "";
|
|
30969
|
+
const after2 = source[i + 2] ?? "";
|
|
30970
|
+
if (next3 === "for" && !/[A-Za-z0-9_]/.test(after3)) {
|
|
30971
|
+
pendingLoopKeyword = { kind: "for", idx: i };
|
|
30972
|
+
i += 2;
|
|
30973
|
+
continue;
|
|
30974
|
+
}
|
|
30975
|
+
if (next5 === "while" && !/[A-Za-z0-9_]/.test(after5)) {
|
|
30976
|
+
pendingLoopKeyword = { kind: "while", idx: i };
|
|
30977
|
+
i += 4;
|
|
30978
|
+
continue;
|
|
30979
|
+
}
|
|
30980
|
+
if (next2 === "do" && !/[A-Za-z0-9_]/.test(after2)) {
|
|
30981
|
+
pendingLoopKeyword = { kind: "do", idx: i };
|
|
30982
|
+
i += 1;
|
|
30983
|
+
continue;
|
|
30984
|
+
}
|
|
30985
|
+
}
|
|
30986
|
+
if (c === "{") {
|
|
30987
|
+
braceStack.push(i);
|
|
30988
|
+
if (pendingLoopKeyword && parenDepth === 0) {
|
|
30989
|
+
loopSet.add(i);
|
|
30990
|
+
pendingLoopKeyword = null;
|
|
30991
|
+
}
|
|
30992
|
+
} else if (c === "}") {
|
|
30993
|
+
const popped = braceStack.pop();
|
|
30994
|
+
if (popped !== void 0 && loopSet.has(popped)) {
|
|
30995
|
+
loopSet.delete(popped);
|
|
30996
|
+
}
|
|
30997
|
+
if (pendingLoopKeyword) {
|
|
30998
|
+
pendingLoopKeyword = null;
|
|
30999
|
+
}
|
|
31000
|
+
} else if (c === "T" && source.slice(i, i + 13) === "Thread.sleep(") {
|
|
31001
|
+
sleepEvents.push({ idx: i, loopDepth: loopSet.size });
|
|
31002
|
+
i += 12;
|
|
31003
|
+
}
|
|
31004
|
+
}
|
|
31005
|
+
for (const ev of sleepEvents) {
|
|
31006
|
+
if (ev.loopDepth === 0) continue;
|
|
31007
|
+
const line = source.slice(0, ev.idx).split("\n").length;
|
|
30890
31008
|
issues.push({
|
|
30891
31009
|
ruleId: "java/thread-sleep-in-loop",
|
|
30892
31010
|
category: "perf",
|
|
@@ -30895,7 +31013,7 @@ var init_thread_sleep_in_loop = __esm({
|
|
|
30895
31013
|
message: `Thread.sleep() at line ${line}`,
|
|
30896
31014
|
line,
|
|
30897
31015
|
column: 1,
|
|
30898
|
-
advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.
|
|
31016
|
+
advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.34.6 (refined to require Thread.sleep inside the loop block, not just in the file).'
|
|
30899
31017
|
});
|
|
30900
31018
|
}
|
|
30901
31019
|
return issues;
|
|
@@ -31177,13 +31295,14 @@ var init_object_singleton_misuse = __esm({
|
|
|
31177
31295
|
});
|
|
31178
31296
|
|
|
31179
31297
|
// src/rules/kotlin/println-as-log.ts
|
|
31180
|
-
var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, kotlinPrintlnAsLogRule;
|
|
31298
|
+
var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, TEST_FILE_REGEX, kotlinPrintlnAsLogRule;
|
|
31181
31299
|
var init_println_as_log = __esm({
|
|
31182
31300
|
"src/rules/kotlin/println-as-log.ts"() {
|
|
31183
31301
|
"use strict";
|
|
31184
31302
|
init_rule();
|
|
31185
31303
|
PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
31186
31304
|
REAL_LOGGING_IMPORT_REGEX2 = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
|
|
31305
|
+
TEST_FILE_REGEX = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
|
|
31187
31306
|
kotlinPrintlnAsLogRule = createRule({
|
|
31188
31307
|
id: "kotlin/println-as-log",
|
|
31189
31308
|
category: "logic",
|
|
@@ -31198,7 +31317,7 @@ var init_println_as_log = __esm({
|
|
|
31198
31317
|
const source = facts.v2?._source;
|
|
31199
31318
|
if (!source) return issues;
|
|
31200
31319
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31201
|
-
if (
|
|
31320
|
+
if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
|
|
31202
31321
|
if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
|
|
31203
31322
|
let m;
|
|
31204
31323
|
PRINTLN_REGEX.lastIndex = 0;
|
|
@@ -31212,7 +31331,7 @@ var init_println_as_log = __esm({
|
|
|
31212
31331
|
message: `println() as logger at line ${line}`,
|
|
31213
31332
|
line,
|
|
31214
31333
|
column: 1,
|
|
31215
|
-
advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.
|
|
31334
|
+
advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.34.5 (refined to skip test files for higher precision)."
|
|
31216
31335
|
});
|
|
31217
31336
|
}
|
|
31218
31337
|
return issues;
|
|
@@ -41057,14 +41176,14 @@ var init_implicitly_unwrapped_optional = __esm({
|
|
|
41057
41176
|
});
|
|
41058
41177
|
|
|
41059
41178
|
// src/rules/swift/print-debug.ts
|
|
41060
|
-
var PRINT_REGEX, DEFAULT_THRESHOLD2,
|
|
41179
|
+
var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX2, swiftPrintDebugRule;
|
|
41061
41180
|
var init_print_debug = __esm({
|
|
41062
41181
|
"src/rules/swift/print-debug.ts"() {
|
|
41063
41182
|
"use strict";
|
|
41064
41183
|
init_rule();
|
|
41065
41184
|
PRINT_REGEX = /\bprint\s*\(/g;
|
|
41066
41185
|
DEFAULT_THRESHOLD2 = 1;
|
|
41067
|
-
|
|
41186
|
+
TEST_FILE_REGEX2 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
|
|
41068
41187
|
swiftPrintDebugRule = createRule({
|
|
41069
41188
|
id: "swift/print-debug",
|
|
41070
41189
|
category: "typo",
|
|
@@ -41079,7 +41198,7 @@ var init_print_debug = __esm({
|
|
|
41079
41198
|
const source = facts.v2?._source;
|
|
41080
41199
|
if (!source) return issues;
|
|
41081
41200
|
if (!/\.swift$/i.test(facts.filePath)) return issues;
|
|
41082
|
-
if (
|
|
41201
|
+
if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
|
|
41083
41202
|
const matches = [];
|
|
41084
41203
|
let m;
|
|
41085
41204
|
PRINT_REGEX.lastIndex = 0;
|
|
@@ -51554,7 +51673,7 @@ var init_signal_strength = __esm({
|
|
|
51554
51673
|
precision: 0.1268,
|
|
51555
51674
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51556
51675
|
verdict: "OK",
|
|
51557
|
-
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
|
|
51676
|
+
_calibrationNote: "v0.34.5: REFINED \u2014 rule now skips test files (JUnit4/5 + Android: *Tests.kt, *Test.kt, src/test/, test/ dirs). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): ratio=1.84 (\u22651.5, OK). The refinement mirrors v0.34.2's swift/print-debug fix: the previous exclusion only matched `\\/test\\/` and `.test.kts?$`, missing JUnit5's `FooTests.kt` naming. Expected post-refinement precision: 12.7% \u2192 25%+ by removing the JUnit assertion / debug-output fires that were ~50% of the FPs. Same direction as java/system-out-println (1.73 refined) and the v0.34.2 swift/print-debug pipeline. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
|
|
51558
51677
|
aiSpecific: false,
|
|
51559
51678
|
_v9Verdict: "OK",
|
|
51560
51679
|
_v9Lift: 1.84,
|
|
@@ -51618,7 +51737,7 @@ var init_signal_strength = __esm({
|
|
|
51618
51737
|
precision: 0.1091,
|
|
51619
51738
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51620
51739
|
verdict: "DORMANT",
|
|
51621
|
-
_calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
|
|
51740
|
+
_calibrationNote: "v0.34.6: REFINED \u2014 rule now requires Thread.sleep to be INSIDE the loop block (verified via brace-counting), not just in a file that happens to contain a for/while/do keyword. v0.30 baseline: 2154 total fires, ratio 0.97 (DORMANT). The refinement targets: (1) Thread.sleep in `main()` no longer fires if a different method has a `for` loop (the v0.30 heuristic fired on every Thread.sleep in the file); (2) Thread.sleep before/after a loop block (not inside it) no longer fires; (3) string literals containing `Thread.sleep(...)` are skipped via the string-state-machine. Expected post-refinement ratio: 1.5+ (positive-signal OK verdict) by removing the cross-method over-fires. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
|
|
51622
51741
|
aiSpecific: false,
|
|
51623
51742
|
_v9Verdict: "DORMANT",
|
|
51624
51743
|
_v9Lift: 0.97,
|
|
@@ -51873,7 +51992,7 @@ var init_signal_strength = __esm({
|
|
|
51873
51992
|
precision: 0.2187,
|
|
51874
51993
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51875
51994
|
verdict: "DORMANT",
|
|
51876
|
-
_calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
|
|
51995
|
+
_calibrationNote: "v0.34.4: REFINED \u2014 expanded allowSet with common constants (-1 sentinel, 100 percent literal, 0.5/1.0/2.0 ratios, 4096/2048/512/128/64/32/16/8 power-of-2 sizes, 50 percentile). Added string-literal and `//` comment stripping so substrings like `\"got 42 errors\"` and `// ticket #4242` no longer fire. v0.33 baseline: 220 TP / 786 FP per-file (ratio 0.86, DORMANT). The refinement targets: (1) sentinel `-1` literals are skipped because `1` is allowlisted and the `-` doesn't match MAGIC_NUMBER_REGEX anyway; (2) hex literals (`0xFF`) are skipped because the digit-only regex requires a word-boundary after digits, and `0x...` has none; (3) literals inside string/comment contexts no longer fire. Expected post-refinement ratio: 1.0-1.2 (still DORMANT but better-targeted). Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
|
|
51877
51996
|
aiSpecific: true,
|
|
51878
51997
|
_v7Verdict: "DORMANT",
|
|
51879
51998
|
_v7Lift: 1,
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var VERSION;
|
|
|
19
19
|
var init_header = __esm({
|
|
20
20
|
"src/types/_header.ts"() {
|
|
21
21
|
"use strict";
|
|
22
|
-
VERSION = "0.34.
|
|
22
|
+
VERSION = "0.34.6";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -27716,6 +27716,7 @@ var init_magic_numbers = __esm({
|
|
|
27716
27716
|
if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
|
|
27717
27717
|
const lines = source.split("\n");
|
|
27718
27718
|
const allowSet = /* @__PURE__ */ new Set([
|
|
27719
|
+
// v0.24 originals
|
|
27719
27720
|
"1024",
|
|
27720
27721
|
"65535",
|
|
27721
27722
|
"65536",
|
|
@@ -27734,17 +27735,42 @@ var init_magic_numbers = __esm({
|
|
|
27734
27735
|
"2",
|
|
27735
27736
|
"3",
|
|
27736
27737
|
"4",
|
|
27737
|
-
"5"
|
|
27738
|
+
"5",
|
|
27739
|
+
// v0.34.4 additions
|
|
27740
|
+
"-1",
|
|
27741
|
+
// sentinel value for "not found" / "all bits set"
|
|
27742
|
+
"100",
|
|
27743
|
+
// percent literal, very common
|
|
27744
|
+
"0.0",
|
|
27745
|
+
"0.5",
|
|
27746
|
+
"1.0",
|
|
27747
|
+
"2.0",
|
|
27748
|
+
// common probability / ratio
|
|
27749
|
+
"4096",
|
|
27750
|
+
// page size, hash bucket count
|
|
27751
|
+
"2048",
|
|
27752
|
+
"512",
|
|
27753
|
+
// power-of-2 sizes
|
|
27754
|
+
"32",
|
|
27755
|
+
"64",
|
|
27756
|
+
"128",
|
|
27757
|
+
// bit widths, byte sizes
|
|
27758
|
+
"16",
|
|
27759
|
+
"8",
|
|
27760
|
+
// common small constants
|
|
27761
|
+
"50"
|
|
27762
|
+
// percentile literal
|
|
27738
27763
|
]);
|
|
27739
27764
|
for (let i = 0; i < lines.length; i++) {
|
|
27740
27765
|
const line = lines[i] ?? "";
|
|
27741
27766
|
if (!COMPARE_OR_RETURN_REGEX.test(line)) continue;
|
|
27767
|
+
const codeLine = line.replace(/\/\/.*$/, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
27742
27768
|
let m;
|
|
27743
27769
|
MAGIC_NUMBER_REGEX.lastIndex = 0;
|
|
27744
|
-
while ((m = MAGIC_NUMBER_REGEX.exec(
|
|
27770
|
+
while ((m = MAGIC_NUMBER_REGEX.exec(codeLine)) !== null) {
|
|
27745
27771
|
const literal = m[0] ?? "";
|
|
27746
27772
|
if (allowSet.has(literal)) continue;
|
|
27747
|
-
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(
|
|
27773
|
+
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(codeLine.slice(0, m.index).trimEnd())) continue;
|
|
27748
27774
|
const prevLine = i > 0 ? lines[i - 1] ?? "" : "";
|
|
27749
27775
|
if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(prevLine.trim())) continue;
|
|
27750
27776
|
issues.push({
|
|
@@ -27755,7 +27781,7 @@ var init_magic_numbers = __esm({
|
|
|
27755
27781
|
message: `magic number ${literal} at line ${i + 1} \u2014 name it: constexpr int MAX = ${literal};`,
|
|
27756
27782
|
line: i + 1,
|
|
27757
27783
|
column: 1,
|
|
27758
|
-
advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.
|
|
27784
|
+
advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.34.4 (expanded allowSet + string/comment exclusion).'
|
|
27759
27785
|
});
|
|
27760
27786
|
}
|
|
27761
27787
|
}
|
|
@@ -30842,12 +30868,11 @@ var init_system_out_println = __esm({
|
|
|
30842
30868
|
});
|
|
30843
30869
|
|
|
30844
30870
|
// src/rules/java/thread-sleep-in-loop.ts
|
|
30845
|
-
var
|
|
30871
|
+
var javaThreadSleepInLoopRule;
|
|
30846
30872
|
var init_thread_sleep_in_loop = __esm({
|
|
30847
30873
|
"src/rules/java/thread-sleep-in-loop.ts"() {
|
|
30848
30874
|
"use strict";
|
|
30849
30875
|
init_rule();
|
|
30850
|
-
THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
|
|
30851
30876
|
javaThreadSleepInLoopRule = createRule({
|
|
30852
30877
|
id: "java/thread-sleep-in-loop",
|
|
30853
30878
|
category: "perf",
|
|
@@ -30863,11 +30888,104 @@ var init_thread_sleep_in_loop = __esm({
|
|
|
30863
30888
|
if (!source) return issues;
|
|
30864
30889
|
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30865
30890
|
if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
|
|
30866
|
-
|
|
30867
|
-
|
|
30868
|
-
|
|
30869
|
-
|
|
30870
|
-
|
|
30891
|
+
const braceStack = [];
|
|
30892
|
+
const loopSet = /* @__PURE__ */ new Set();
|
|
30893
|
+
let pendingLoopKeyword = null;
|
|
30894
|
+
let parenDepth = 0;
|
|
30895
|
+
const sleepEvents = [];
|
|
30896
|
+
let inString = false;
|
|
30897
|
+
let inLineComment = false;
|
|
30898
|
+
let inBlockComment = false;
|
|
30899
|
+
for (let i = 0; i < source.length; i++) {
|
|
30900
|
+
const c = source[i] ?? "";
|
|
30901
|
+
const next = source[i + 1] ?? "";
|
|
30902
|
+
const prev = source[i - 1] ?? "";
|
|
30903
|
+
if (inLineComment) {
|
|
30904
|
+
if (c === "\n") inLineComment = false;
|
|
30905
|
+
continue;
|
|
30906
|
+
}
|
|
30907
|
+
if (inBlockComment) {
|
|
30908
|
+
if (c === "*" && next === "/") {
|
|
30909
|
+
inBlockComment = false;
|
|
30910
|
+
i++;
|
|
30911
|
+
}
|
|
30912
|
+
continue;
|
|
30913
|
+
}
|
|
30914
|
+
if (inString) {
|
|
30915
|
+
if (c === "\\") {
|
|
30916
|
+
i++;
|
|
30917
|
+
continue;
|
|
30918
|
+
}
|
|
30919
|
+
if (c === inString) inString = false;
|
|
30920
|
+
continue;
|
|
30921
|
+
}
|
|
30922
|
+
if (c === "/" && next === "/") {
|
|
30923
|
+
inLineComment = true;
|
|
30924
|
+
i++;
|
|
30925
|
+
continue;
|
|
30926
|
+
}
|
|
30927
|
+
if (c === "/" && next === "*") {
|
|
30928
|
+
inBlockComment = true;
|
|
30929
|
+
i++;
|
|
30930
|
+
continue;
|
|
30931
|
+
}
|
|
30932
|
+
if (c === '"' || c === "'") {
|
|
30933
|
+
inString = c;
|
|
30934
|
+
continue;
|
|
30935
|
+
}
|
|
30936
|
+
if (c === "(") {
|
|
30937
|
+
parenDepth++;
|
|
30938
|
+
continue;
|
|
30939
|
+
}
|
|
30940
|
+
if (c === ")") {
|
|
30941
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
30942
|
+
continue;
|
|
30943
|
+
}
|
|
30944
|
+
if (/[A-Za-z_]/.test(c) && !/[A-Za-z0-9_]/.test(prev)) {
|
|
30945
|
+
const next3 = source.slice(i, i + 3);
|
|
30946
|
+
const next5 = source.slice(i, i + 5);
|
|
30947
|
+
const next2 = source.slice(i, i + 2);
|
|
30948
|
+
const after3 = source[i + 3] ?? "";
|
|
30949
|
+
const after5 = source[i + 5] ?? "";
|
|
30950
|
+
const after2 = source[i + 2] ?? "";
|
|
30951
|
+
if (next3 === "for" && !/[A-Za-z0-9_]/.test(after3)) {
|
|
30952
|
+
pendingLoopKeyword = { kind: "for", idx: i };
|
|
30953
|
+
i += 2;
|
|
30954
|
+
continue;
|
|
30955
|
+
}
|
|
30956
|
+
if (next5 === "while" && !/[A-Za-z0-9_]/.test(after5)) {
|
|
30957
|
+
pendingLoopKeyword = { kind: "while", idx: i };
|
|
30958
|
+
i += 4;
|
|
30959
|
+
continue;
|
|
30960
|
+
}
|
|
30961
|
+
if (next2 === "do" && !/[A-Za-z0-9_]/.test(after2)) {
|
|
30962
|
+
pendingLoopKeyword = { kind: "do", idx: i };
|
|
30963
|
+
i += 1;
|
|
30964
|
+
continue;
|
|
30965
|
+
}
|
|
30966
|
+
}
|
|
30967
|
+
if (c === "{") {
|
|
30968
|
+
braceStack.push(i);
|
|
30969
|
+
if (pendingLoopKeyword && parenDepth === 0) {
|
|
30970
|
+
loopSet.add(i);
|
|
30971
|
+
pendingLoopKeyword = null;
|
|
30972
|
+
}
|
|
30973
|
+
} else if (c === "}") {
|
|
30974
|
+
const popped = braceStack.pop();
|
|
30975
|
+
if (popped !== void 0 && loopSet.has(popped)) {
|
|
30976
|
+
loopSet.delete(popped);
|
|
30977
|
+
}
|
|
30978
|
+
if (pendingLoopKeyword) {
|
|
30979
|
+
pendingLoopKeyword = null;
|
|
30980
|
+
}
|
|
30981
|
+
} else if (c === "T" && source.slice(i, i + 13) === "Thread.sleep(") {
|
|
30982
|
+
sleepEvents.push({ idx: i, loopDepth: loopSet.size });
|
|
30983
|
+
i += 12;
|
|
30984
|
+
}
|
|
30985
|
+
}
|
|
30986
|
+
for (const ev of sleepEvents) {
|
|
30987
|
+
if (ev.loopDepth === 0) continue;
|
|
30988
|
+
const line = source.slice(0, ev.idx).split("\n").length;
|
|
30871
30989
|
issues.push({
|
|
30872
30990
|
ruleId: "java/thread-sleep-in-loop",
|
|
30873
30991
|
category: "perf",
|
|
@@ -30876,7 +30994,7 @@ var init_thread_sleep_in_loop = __esm({
|
|
|
30876
30994
|
message: `Thread.sleep() at line ${line}`,
|
|
30877
30995
|
line,
|
|
30878
30996
|
column: 1,
|
|
30879
|
-
advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.
|
|
30997
|
+
advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.34.6 (refined to require Thread.sleep inside the loop block, not just in the file).'
|
|
30880
30998
|
});
|
|
30881
30999
|
}
|
|
30882
31000
|
return issues;
|
|
@@ -31158,13 +31276,14 @@ var init_object_singleton_misuse = __esm({
|
|
|
31158
31276
|
});
|
|
31159
31277
|
|
|
31160
31278
|
// src/rules/kotlin/println-as-log.ts
|
|
31161
|
-
var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, kotlinPrintlnAsLogRule;
|
|
31279
|
+
var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, TEST_FILE_REGEX, kotlinPrintlnAsLogRule;
|
|
31162
31280
|
var init_println_as_log = __esm({
|
|
31163
31281
|
"src/rules/kotlin/println-as-log.ts"() {
|
|
31164
31282
|
"use strict";
|
|
31165
31283
|
init_rule();
|
|
31166
31284
|
PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
31167
31285
|
REAL_LOGGING_IMPORT_REGEX2 = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
|
|
31286
|
+
TEST_FILE_REGEX = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
|
|
31168
31287
|
kotlinPrintlnAsLogRule = createRule({
|
|
31169
31288
|
id: "kotlin/println-as-log",
|
|
31170
31289
|
category: "logic",
|
|
@@ -31179,7 +31298,7 @@ var init_println_as_log = __esm({
|
|
|
31179
31298
|
const source = facts.v2?._source;
|
|
31180
31299
|
if (!source) return issues;
|
|
31181
31300
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31182
|
-
if (
|
|
31301
|
+
if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
|
|
31183
31302
|
if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
|
|
31184
31303
|
let m;
|
|
31185
31304
|
PRINTLN_REGEX.lastIndex = 0;
|
|
@@ -31193,7 +31312,7 @@ var init_println_as_log = __esm({
|
|
|
31193
31312
|
message: `println() as logger at line ${line}`,
|
|
31194
31313
|
line,
|
|
31195
31314
|
column: 1,
|
|
31196
|
-
advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.
|
|
31315
|
+
advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.34.5 (refined to skip test files for higher precision)."
|
|
31197
31316
|
});
|
|
31198
31317
|
}
|
|
31199
31318
|
return issues;
|
|
@@ -41038,14 +41157,14 @@ var init_implicitly_unwrapped_optional = __esm({
|
|
|
41038
41157
|
});
|
|
41039
41158
|
|
|
41040
41159
|
// src/rules/swift/print-debug.ts
|
|
41041
|
-
var PRINT_REGEX, DEFAULT_THRESHOLD2,
|
|
41160
|
+
var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX2, swiftPrintDebugRule;
|
|
41042
41161
|
var init_print_debug = __esm({
|
|
41043
41162
|
"src/rules/swift/print-debug.ts"() {
|
|
41044
41163
|
"use strict";
|
|
41045
41164
|
init_rule();
|
|
41046
41165
|
PRINT_REGEX = /\bprint\s*\(/g;
|
|
41047
41166
|
DEFAULT_THRESHOLD2 = 1;
|
|
41048
|
-
|
|
41167
|
+
TEST_FILE_REGEX2 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
|
|
41049
41168
|
swiftPrintDebugRule = createRule({
|
|
41050
41169
|
id: "swift/print-debug",
|
|
41051
41170
|
category: "typo",
|
|
@@ -41060,7 +41179,7 @@ var init_print_debug = __esm({
|
|
|
41060
41179
|
const source = facts.v2?._source;
|
|
41061
41180
|
if (!source) return issues;
|
|
41062
41181
|
if (!/\.swift$/i.test(facts.filePath)) return issues;
|
|
41063
|
-
if (
|
|
41182
|
+
if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
|
|
41064
41183
|
const matches = [];
|
|
41065
41184
|
let m;
|
|
41066
41185
|
PRINT_REGEX.lastIndex = 0;
|
|
@@ -51532,7 +51651,7 @@ var init_signal_strength = __esm({
|
|
|
51532
51651
|
precision: 0.1268,
|
|
51533
51652
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51534
51653
|
verdict: "OK",
|
|
51535
|
-
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
|
|
51654
|
+
_calibrationNote: "v0.34.5: REFINED \u2014 rule now skips test files (JUnit4/5 + Android: *Tests.kt, *Test.kt, src/test/, test/ dirs). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): ratio=1.84 (\u22651.5, OK). The refinement mirrors v0.34.2's swift/print-debug fix: the previous exclusion only matched `\\/test\\/` and `.test.kts?$`, missing JUnit5's `FooTests.kt` naming. Expected post-refinement precision: 12.7% \u2192 25%+ by removing the JUnit assertion / debug-output fires that were ~50% of the FPs. Same direction as java/system-out-println (1.73 refined) and the v0.34.2 swift/print-debug pipeline. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
|
|
51536
51655
|
aiSpecific: false,
|
|
51537
51656
|
_v9Verdict: "OK",
|
|
51538
51657
|
_v9Lift: 1.84,
|
|
@@ -51596,7 +51715,7 @@ var init_signal_strength = __esm({
|
|
|
51596
51715
|
precision: 0.1091,
|
|
51597
51716
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51598
51717
|
verdict: "DORMANT",
|
|
51599
|
-
_calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
|
|
51718
|
+
_calibrationNote: "v0.34.6: REFINED \u2014 rule now requires Thread.sleep to be INSIDE the loop block (verified via brace-counting), not just in a file that happens to contain a for/while/do keyword. v0.30 baseline: 2154 total fires, ratio 0.97 (DORMANT). The refinement targets: (1) Thread.sleep in `main()` no longer fires if a different method has a `for` loop (the v0.30 heuristic fired on every Thread.sleep in the file); (2) Thread.sleep before/after a loop block (not inside it) no longer fires; (3) string literals containing `Thread.sleep(...)` are skipped via the string-state-machine. Expected post-refinement ratio: 1.5+ (positive-signal OK verdict) by removing the cross-method over-fires. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
|
|
51600
51719
|
aiSpecific: false,
|
|
51601
51720
|
_v9Verdict: "DORMANT",
|
|
51602
51721
|
_v9Lift: 0.97,
|
|
@@ -51851,7 +51970,7 @@ var init_signal_strength = __esm({
|
|
|
51851
51970
|
precision: 0.2187,
|
|
51852
51971
|
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51853
51972
|
verdict: "DORMANT",
|
|
51854
|
-
_calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
|
|
51973
|
+
_calibrationNote: "v0.34.4: REFINED \u2014 expanded allowSet with common constants (-1 sentinel, 100 percent literal, 0.5/1.0/2.0 ratios, 4096/2048/512/128/64/32/16/8 power-of-2 sizes, 50 percentile). Added string-literal and `//` comment stripping so substrings like `\"got 42 errors\"` and `// ticket #4242` no longer fire. v0.33 baseline: 220 TP / 786 FP per-file (ratio 0.86, DORMANT). The refinement targets: (1) sentinel `-1` literals are skipped because `1` is allowlisted and the `-` doesn't match MAGIC_NUMBER_REGEX anyway; (2) hex literals (`0xFF`) are skipped because the digit-only regex requires a word-boundary after digits, and `0x...` has none; (3) literals inside string/comment contexts no longer fire. Expected post-refinement ratio: 1.0-1.2 (still DORMANT but better-targeted). Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
|
|
51855
51974
|
aiSpecific: true,
|
|
51856
51975
|
_v7Verdict: "DORMANT",
|
|
51857
51976
|
_v7Lift: 1,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slopbrick",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.6",
|
|
4
4
|
"description": "Discovered, modeled, and governed repository structure. SlopBrick scans source code, classifies it against 95+ rules in 15 categories, computes 4 scores (aiSlopScore: lower=cleaner, engineeringHygiene, security, repositoryHealth composite), and persists the structure for AI agents and CI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|