slopbrick 0.26.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/worker.cjs +79 -782
- package/dist/engine/worker.js +79 -782
- package/dist/index.cjs +85 -888
- package/dist/index.js +85 -888
- package/package.json +1 -1
package/dist/engine/worker.cjs
CHANGED
|
@@ -4366,6 +4366,15 @@ function parseSource(source, filePath) {
|
|
|
4366
4366
|
case "go":
|
|
4367
4367
|
case "rs":
|
|
4368
4368
|
case "java":
|
|
4369
|
+
// v0.28.0: Kotlin files get the same parseBlankModule path as
|
|
4370
|
+
// Java (v0.24.5). All 5 `kotlin/*` rules are regex-based and
|
|
4371
|
+
// gate themselves on `/\.kts?$/i.test(filePath)` inside their
|
|
4372
|
+
// `analyze()`. The tree-sitter Kotlin integration is a larger
|
|
4373
|
+
// lift; the v0.27.0 methodology paper confirmed era-confounding
|
|
4374
|
+
// is the dominant signal anyway, so a regex-only Kotlin pass is
|
|
4375
|
+
// sufficient for the v9 calibration goal.
|
|
4376
|
+
case "kt":
|
|
4377
|
+
case "kts":
|
|
4369
4378
|
return parseBlankModule(source);
|
|
4370
4379
|
default:
|
|
4371
4380
|
return parseWithSwc(source, filePath);
|
|
@@ -36991,512 +37000,6 @@ var goStructTagInconsistencyRule = createRule({
|
|
|
36991
37000
|
}
|
|
36992
37001
|
});
|
|
36993
37002
|
|
|
36994
|
-
// src/rules/java/arraylist-vs-linkedlist.ts
|
|
36995
|
-
var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
|
|
36996
|
-
var javaArraylistVsLinkedlistRule = createRule({
|
|
36997
|
-
id: "java/arraylist-vs-linkedlist",
|
|
36998
|
-
category: "typo",
|
|
36999
|
-
severity: "low",
|
|
37000
|
-
aiSpecific: true,
|
|
37001
|
-
description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
|
|
37002
|
-
create(_context) {
|
|
37003
|
-
return {};
|
|
37004
|
-
},
|
|
37005
|
-
analyze(_context, facts) {
|
|
37006
|
-
const issues = [];
|
|
37007
|
-
const source = facts.v2?._source;
|
|
37008
|
-
if (!source) return issues;
|
|
37009
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37010
|
-
let m;
|
|
37011
|
-
NEW_LINKED_LIST_REGEX.lastIndex = 0;
|
|
37012
|
-
while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
|
|
37013
|
-
const line = source.slice(0, m.index).split("\n").length;
|
|
37014
|
-
issues.push({
|
|
37015
|
-
ruleId: "java/arraylist-vs-linkedlist",
|
|
37016
|
-
category: "typo",
|
|
37017
|
-
severity: "low",
|
|
37018
|
-
aiSpecific: true,
|
|
37019
|
-
message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
|
|
37020
|
-
line,
|
|
37021
|
-
column: 1,
|
|
37022
|
-
advice: "Replace `new LinkedList<>()` with `new ArrayList<>()`. LinkedList is rarely the right choice (worse cache locality, 5x more memory per element, O(n) indexed access). Joshua Bloch (Effective Java, Item 28) recommends ArrayList unless you specifically need a Deque. AI agents default to LinkedList because of textbook examples. Reference: java/arraylist-vs-linkedlist v0.20."
|
|
37023
|
-
});
|
|
37024
|
-
}
|
|
37025
|
-
return issues;
|
|
37026
|
-
}
|
|
37027
|
-
});
|
|
37028
|
-
|
|
37029
|
-
// src/rules/java/builder-overuse.ts
|
|
37030
|
-
var BUILDER_ANNOTATION_REGEX = /@Builder\b/;
|
|
37031
|
-
var FIELD_DECL_REGEX = /(?:private|public|protected)\s+(?:final\s+)?[\w<>,\s]+\s+(\w+)\s*[=;]/g;
|
|
37032
|
-
var DEFAULT_FIELD_COUNT_CAP = 3;
|
|
37033
|
-
var javaBuilderOveruseRule = createRule({
|
|
37034
|
-
id: "java/builder-overuse",
|
|
37035
|
-
category: "typo",
|
|
37036
|
-
severity: "low",
|
|
37037
|
-
aiSpecific: true,
|
|
37038
|
-
description: "@Builder on a class with few fields \u2014 plain constructor is simpler",
|
|
37039
|
-
create(_context) {
|
|
37040
|
-
return { fieldCountCap: DEFAULT_FIELD_COUNT_CAP };
|
|
37041
|
-
},
|
|
37042
|
-
analyze(context, facts) {
|
|
37043
|
-
const issues = [];
|
|
37044
|
-
const source = facts.v2?._source;
|
|
37045
|
-
if (!source) return issues;
|
|
37046
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37047
|
-
BUILDER_ANNOTATION_REGEX.lastIndex = 0;
|
|
37048
|
-
const builderMatch = BUILDER_ANNOTATION_REGEX.exec(source);
|
|
37049
|
-
if (!builderMatch) return issues;
|
|
37050
|
-
const firstBuilderLine = source.slice(0, builderMatch.index).split("\n").length;
|
|
37051
|
-
FIELD_DECL_REGEX.lastIndex = 0;
|
|
37052
|
-
let fieldCount = 0;
|
|
37053
|
-
while (FIELD_DECL_REGEX.exec(source) !== null) {
|
|
37054
|
-
fieldCount++;
|
|
37055
|
-
}
|
|
37056
|
-
if (fieldCount > context.fieldCountCap) return issues;
|
|
37057
|
-
issues.push({
|
|
37058
|
-
ruleId: "java/builder-overuse",
|
|
37059
|
-
category: "typo",
|
|
37060
|
-
severity: "low",
|
|
37061
|
-
aiSpecific: true,
|
|
37062
|
-
message: `@Builder on a class with ${fieldCount} field(s) \u2014 plain constructor is simpler`,
|
|
37063
|
-
line: firstBuilderLine,
|
|
37064
|
-
column: 1,
|
|
37065
|
-
advice: "Use a plain constructor for classes with \u2264 3 fields. AI agents default to @Builder because their training data emphasizes the pattern. Builder adds Lombok dependency and a Builder inner class for every annotated class. Reference: java/builder-overuse v0.26.0."
|
|
37066
|
-
});
|
|
37067
|
-
return issues;
|
|
37068
|
-
}
|
|
37069
|
-
});
|
|
37070
|
-
|
|
37071
|
-
// src/rules/java/empty-catch-block.ts
|
|
37072
|
-
var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
37073
|
-
var javaEmptyCatchBlockRule = createRule({
|
|
37074
|
-
id: "java/empty-catch-block",
|
|
37075
|
-
category: "logic",
|
|
37076
|
-
severity: "medium",
|
|
37077
|
-
aiSpecific: true,
|
|
37078
|
-
description: "Empty catch block \u2014 silently swallows exceptions",
|
|
37079
|
-
create(_context) {
|
|
37080
|
-
return {};
|
|
37081
|
-
},
|
|
37082
|
-
analyze(_context, facts) {
|
|
37083
|
-
const issues = [];
|
|
37084
|
-
const source = facts.v2?._source;
|
|
37085
|
-
if (!source) return issues;
|
|
37086
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37087
|
-
let m;
|
|
37088
|
-
SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
|
|
37089
|
-
while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
|
|
37090
|
-
const line = source.slice(0, m.index).split("\n").length;
|
|
37091
|
-
issues.push({
|
|
37092
|
-
ruleId: "java/empty-catch-block",
|
|
37093
|
-
category: "logic",
|
|
37094
|
-
severity: "medium",
|
|
37095
|
-
aiSpecific: true,
|
|
37096
|
-
message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
|
|
37097
|
-
line,
|
|
37098
|
-
column: 1,
|
|
37099
|
-
advice: 'Log the exception (`log.error("...", e)`), re-throw it, or both. Empty catch blocks hide bugs. The pattern is common in AI-generated code that wants to look defensive. Reference: java/empty-catch-block v0.20.'
|
|
37100
|
-
});
|
|
37101
|
-
}
|
|
37102
|
-
return issues;
|
|
37103
|
-
}
|
|
37104
|
-
});
|
|
37105
|
-
|
|
37106
|
-
// src/rules/java/immutable-collection-preference.ts
|
|
37107
|
-
var IMMUTABLE_REGEX = /\b(List|Map|Set)\.of\s*\(/g;
|
|
37108
|
-
var MUTABLE_REGEX = /new\s+(ArrayList|HashMap|HashSet|LinkedList|TreeMap)\s*[<(]/g;
|
|
37109
|
-
var DEFAULT_IMMUTABLE_THRESHOLD = 5;
|
|
37110
|
-
var DEFAULT_MUTABLE_CAP = 1;
|
|
37111
|
-
var javaImmutableCollectionPreferenceRule = createRule({
|
|
37112
|
-
id: "java/immutable-collection-preference",
|
|
37113
|
-
category: "typo",
|
|
37114
|
-
severity: "low",
|
|
37115
|
-
aiSpecific: true,
|
|
37116
|
-
description: "Immutable factory method over-use \u2014 prefer mutable when collection will be modified",
|
|
37117
|
-
create(_context) {
|
|
37118
|
-
return {
|
|
37119
|
-
immutableThreshold: DEFAULT_IMMUTABLE_THRESHOLD,
|
|
37120
|
-
mutableCap: DEFAULT_MUTABLE_CAP
|
|
37121
|
-
};
|
|
37122
|
-
},
|
|
37123
|
-
analyze(context, facts) {
|
|
37124
|
-
const issues = [];
|
|
37125
|
-
const source = facts.v2?._source;
|
|
37126
|
-
if (!source) return issues;
|
|
37127
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37128
|
-
IMMUTABLE_REGEX.lastIndex = 0;
|
|
37129
|
-
let immutableCount = 0;
|
|
37130
|
-
let firstIdx = 0;
|
|
37131
|
-
let m;
|
|
37132
|
-
while ((m = IMMUTABLE_REGEX.exec(source)) !== null) {
|
|
37133
|
-
immutableCount++;
|
|
37134
|
-
if (firstIdx === 0) firstIdx = m.index;
|
|
37135
|
-
}
|
|
37136
|
-
if (immutableCount < context.immutableThreshold) return issues;
|
|
37137
|
-
MUTABLE_REGEX.lastIndex = 0;
|
|
37138
|
-
let mutableCount = 0;
|
|
37139
|
-
while ((m = MUTABLE_REGEX.exec(source)) !== null) {
|
|
37140
|
-
mutableCount++;
|
|
37141
|
-
}
|
|
37142
|
-
if (mutableCount > context.mutableCap) return issues;
|
|
37143
|
-
const line = source.slice(0, firstIdx).split("\n").length;
|
|
37144
|
-
issues.push({
|
|
37145
|
-
ruleId: "java/immutable-collection-preference",
|
|
37146
|
-
category: "typo",
|
|
37147
|
-
severity: "low",
|
|
37148
|
-
aiSpecific: true,
|
|
37149
|
-
message: `${immutableCount} immutable factory calls (List/Map/Set.of) with only ${mutableCount} mutable collection \u2014 likely over-preferring immutability`,
|
|
37150
|
-
line,
|
|
37151
|
-
column: 1,
|
|
37152
|
-
advice: "Prefer mutable collections (new ArrayList<>()) when the collection will be modified later. AI agents default to List.of/Map.of/Set.of because their training data emphasizes functional-style Java. Reference: java/immutable-collection-preference v0.26.0."
|
|
37153
|
-
});
|
|
37154
|
-
return issues;
|
|
37155
|
-
}
|
|
37156
|
-
});
|
|
37157
|
-
|
|
37158
|
-
// src/rules/java/legacy-date-api.ts
|
|
37159
|
-
var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
|
|
37160
|
-
var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
|
|
37161
|
-
var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
|
|
37162
|
-
var javaLegacyDateApiRule = createRule({
|
|
37163
|
-
id: "java/legacy-date-api",
|
|
37164
|
-
category: "typo",
|
|
37165
|
-
severity: "low",
|
|
37166
|
-
aiSpecific: true,
|
|
37167
|
-
description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
|
|
37168
|
-
create(_context) {
|
|
37169
|
-
return {};
|
|
37170
|
-
},
|
|
37171
|
-
analyze(_context, facts) {
|
|
37172
|
-
const issues = [];
|
|
37173
|
-
const source = facts.v2?._source;
|
|
37174
|
-
if (!source) return issues;
|
|
37175
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37176
|
-
const flagged = /* @__PURE__ */ new Set();
|
|
37177
|
-
let m;
|
|
37178
|
-
LEGACY_IMPORT_REGEX.lastIndex = 0;
|
|
37179
|
-
while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
|
|
37180
|
-
const line = source.slice(0, m.index).split("\n").length;
|
|
37181
|
-
flagged.add(line);
|
|
37182
|
-
issues.push({
|
|
37183
|
-
ruleId: "java/legacy-date-api",
|
|
37184
|
-
category: "typo",
|
|
37185
|
-
severity: "low",
|
|
37186
|
-
aiSpecific: true,
|
|
37187
|
-
message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
|
|
37188
|
-
line,
|
|
37189
|
-
column: 1,
|
|
37190
|
-
advice: "Replace `java.util.Date` / `java.util.Calendar` with `java.time` (`LocalDate`, `LocalDateTime`, `Instant`, `ZonedDateTime`). java.time is immutable, thread-safe, and has a much better API. AI agents default to the legacy API because their training data predates Java 8 (2014). Reference: java/legacy-date-api v0.20."
|
|
37191
|
-
});
|
|
37192
|
-
}
|
|
37193
|
-
LEGACY_USAGE_REGEX.lastIndex = 0;
|
|
37194
|
-
while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
|
|
37195
|
-
const line = source.slice(0, m.index).split("\n").length;
|
|
37196
|
-
if (flagged.has(line)) continue;
|
|
37197
|
-
issues.push({
|
|
37198
|
-
ruleId: "java/legacy-date-api",
|
|
37199
|
-
category: "typo",
|
|
37200
|
-
severity: "low",
|
|
37201
|
-
aiSpecific: true,
|
|
37202
|
-
message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
|
|
37203
|
-
line,
|
|
37204
|
-
column: 1,
|
|
37205
|
-
advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
37206
|
-
});
|
|
37207
|
-
}
|
|
37208
|
-
CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
|
|
37209
|
-
while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
|
|
37210
|
-
const line = source.slice(0, m.index).split("\n").length;
|
|
37211
|
-
if (flagged.has(line)) continue;
|
|
37212
|
-
issues.push({
|
|
37213
|
-
ruleId: "java/legacy-date-api",
|
|
37214
|
-
category: "typo",
|
|
37215
|
-
severity: "low",
|
|
37216
|
-
aiSpecific: true,
|
|
37217
|
-
message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
|
|
37218
|
-
line,
|
|
37219
|
-
column: 1,
|
|
37220
|
-
advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
37221
|
-
});
|
|
37222
|
-
}
|
|
37223
|
-
return issues;
|
|
37224
|
-
}
|
|
37225
|
-
});
|
|
37226
|
-
|
|
37227
|
-
// src/rules/java/optional-overuse.ts
|
|
37228
|
-
var OR_ELSE_THROW_REGEX = /\.orElseThrow\s*\(/g;
|
|
37229
|
-
var NULL_CHECK_REGEX = /Objects\.requireNonNull\s*\(|if\s*\([^)]*==\s*null\)\s*(?:throw|return)/g;
|
|
37230
|
-
var DEFAULT_THRESHOLD = 2;
|
|
37231
|
-
var javaOptionalOveruseRule = createRule({
|
|
37232
|
-
id: "java/optional-overuse",
|
|
37233
|
-
category: "typo",
|
|
37234
|
-
severity: "low",
|
|
37235
|
-
aiSpecific: true,
|
|
37236
|
-
description: "Optional chain over-use \u2014 null check or Objects.requireNonNull is faster",
|
|
37237
|
-
create(_context) {
|
|
37238
|
-
return { threshold: DEFAULT_THRESHOLD };
|
|
37239
|
-
},
|
|
37240
|
-
analyze(context, facts) {
|
|
37241
|
-
const issues = [];
|
|
37242
|
-
const source = facts.v2?._source;
|
|
37243
|
-
if (!source) return issues;
|
|
37244
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37245
|
-
OR_ELSE_THROW_REGEX.lastIndex = 0;
|
|
37246
|
-
let orElseThrowCount = 0;
|
|
37247
|
-
let firstIdx = 0;
|
|
37248
|
-
let m;
|
|
37249
|
-
while ((m = OR_ELSE_THROW_REGEX.exec(source)) !== null) {
|
|
37250
|
-
orElseThrowCount++;
|
|
37251
|
-
if (firstIdx === 0) firstIdx = m.index;
|
|
37252
|
-
}
|
|
37253
|
-
if (orElseThrowCount < context.threshold) return issues;
|
|
37254
|
-
NULL_CHECK_REGEX.lastIndex = 0;
|
|
37255
|
-
const nullCheckCount = (source.match(NULL_CHECK_REGEX) ?? []).length;
|
|
37256
|
-
const optionalRatio = orElseThrowCount / Math.max(orElseThrowCount + nullCheckCount, 1);
|
|
37257
|
-
if (optionalRatio < 0.6) return issues;
|
|
37258
|
-
const line = source.slice(0, firstIdx).split("\n").length;
|
|
37259
|
-
issues.push({
|
|
37260
|
-
ruleId: "java/optional-overuse",
|
|
37261
|
-
category: "typo",
|
|
37262
|
-
severity: "low",
|
|
37263
|
-
aiSpecific: true,
|
|
37264
|
-
message: `${orElseThrowCount} .orElseThrow() calls with ${nullCheckCount} null checks \u2014 Optional chain over-use`,
|
|
37265
|
-
line,
|
|
37266
|
-
column: 1,
|
|
37267
|
-
advice: 'Use Objects.requireNonNull(x, "msg") for null checks; reserve Optional for return values. AI agents default to Optional chains because their training data emphasizes null-safety. Real Java code uses null checks in hot paths. Reference: java/optional-overuse v0.26.0.'
|
|
37268
|
-
});
|
|
37269
|
-
return issues;
|
|
37270
|
-
}
|
|
37271
|
-
});
|
|
37272
|
-
|
|
37273
|
-
// src/rules/java/raw-type-overuse.ts
|
|
37274
|
-
var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
|
|
37275
|
-
var javaRawTypeOveruseRule = createRule({
|
|
37276
|
-
id: "java/raw-type-overuse",
|
|
37277
|
-
category: "typo",
|
|
37278
|
-
severity: "low",
|
|
37279
|
-
aiSpecific: true,
|
|
37280
|
-
description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
|
|
37281
|
-
create(_context) {
|
|
37282
|
-
return {};
|
|
37283
|
-
},
|
|
37284
|
-
analyze(_context, facts) {
|
|
37285
|
-
const issues = [];
|
|
37286
|
-
const source = facts.v2?._source;
|
|
37287
|
-
if (!source) return issues;
|
|
37288
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37289
|
-
let m;
|
|
37290
|
-
RAW_TYPE_REGEX.lastIndex = 0;
|
|
37291
|
-
while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
|
|
37292
|
-
const typeName = m[1];
|
|
37293
|
-
const line = source.slice(0, m.index).split("\n").length;
|
|
37294
|
-
issues.push({
|
|
37295
|
-
ruleId: "java/raw-type-overuse",
|
|
37296
|
-
category: "typo",
|
|
37297
|
-
severity: "low",
|
|
37298
|
-
aiSpecific: true,
|
|
37299
|
-
message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
|
|
37300
|
-
line,
|
|
37301
|
-
column: 1,
|
|
37302
|
-
advice: `Replace raw \`${typeName}\` with \`${typeName}<...>\`. Raw types disable generic type checking. Effective Java, Item 23: 'Don't use raw types in new code'. AI agents default to raw types when unsure of the correct generic parameters. Reference: java/raw-type-overuse v0.20.`
|
|
37303
|
-
});
|
|
37304
|
-
}
|
|
37305
|
-
return issues;
|
|
37306
|
-
}
|
|
37307
|
-
});
|
|
37308
|
-
|
|
37309
|
-
// src/rules/java/stream-overuse.ts
|
|
37310
|
-
var STREAM_OPS = [
|
|
37311
|
-
".map(",
|
|
37312
|
-
".filter(",
|
|
37313
|
-
".flatMap(",
|
|
37314
|
-
".collect(",
|
|
37315
|
-
".reduce(",
|
|
37316
|
-
".sorted(",
|
|
37317
|
-
".distinct(",
|
|
37318
|
-
".limit(",
|
|
37319
|
-
".skip(",
|
|
37320
|
-
".anyMatch(",
|
|
37321
|
-
".allMatch(",
|
|
37322
|
-
".noneMatch(",
|
|
37323
|
-
".findFirst(",
|
|
37324
|
-
".findAny("
|
|
37325
|
-
];
|
|
37326
|
-
var DEFAULT_CHAIN_THRESHOLD = 3;
|
|
37327
|
-
var javaStreamOveruseRule = createRule({
|
|
37328
|
-
id: "java/stream-overuse",
|
|
37329
|
-
category: "typo",
|
|
37330
|
-
severity: "low",
|
|
37331
|
-
aiSpecific: true,
|
|
37332
|
-
description: "Stream API chain over-use \u2014 for-loop is faster for simple transformations",
|
|
37333
|
-
create(_context) {
|
|
37334
|
-
return { chainThreshold: DEFAULT_CHAIN_THRESHOLD };
|
|
37335
|
-
},
|
|
37336
|
-
analyze(context, facts) {
|
|
37337
|
-
const issues = [];
|
|
37338
|
-
const source = facts.v2?._source;
|
|
37339
|
-
if (!source) return issues;
|
|
37340
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37341
|
-
const lines = source.split("\n");
|
|
37342
|
-
const streamOpPattern = new RegExp(STREAM_OPS.map((op) => op.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g");
|
|
37343
|
-
let firstOffendingLine = 0;
|
|
37344
|
-
let firstOffendingCount = 0;
|
|
37345
|
-
for (let i = 0; i < lines.length; i++) {
|
|
37346
|
-
const line = lines[i] ?? "";
|
|
37347
|
-
if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;
|
|
37348
|
-
const matches = line.match(streamOpPattern);
|
|
37349
|
-
if (matches && matches.length >= context.chainThreshold) {
|
|
37350
|
-
if (firstOffendingLine === 0) {
|
|
37351
|
-
firstOffendingLine = i + 1;
|
|
37352
|
-
firstOffendingCount = matches.length;
|
|
37353
|
-
}
|
|
37354
|
-
}
|
|
37355
|
-
}
|
|
37356
|
-
if (firstOffendingLine === 0) return issues;
|
|
37357
|
-
issues.push({
|
|
37358
|
-
ruleId: "java/stream-overuse",
|
|
37359
|
-
category: "typo",
|
|
37360
|
-
severity: "low",
|
|
37361
|
-
aiSpecific: true,
|
|
37362
|
-
message: `${firstOffendingCount} stream operations on a single line \u2014 for-loop is faster`,
|
|
37363
|
-
line: firstOffendingLine,
|
|
37364
|
-
column: 1,
|
|
37365
|
-
advice: "Prefer a for-loop for simple transformations. AI agents default to Stream API chains because their training data emphasizes functional-style Java. Stream chains allocate intermediate objects; for small collections, a for-loop is faster and more readable. Reference: java/stream-overuse v0.26.0."
|
|
37366
|
-
});
|
|
37367
|
-
return issues;
|
|
37368
|
-
}
|
|
37369
|
-
});
|
|
37370
|
-
|
|
37371
|
-
// src/rules/java/string-concat-loop.ts
|
|
37372
|
-
var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
|
|
37373
|
-
var javaStringConcatLoopRule = createRule({
|
|
37374
|
-
id: "java/string-concat-loop",
|
|
37375
|
-
category: "perf",
|
|
37376
|
-
severity: "low",
|
|
37377
|
-
aiSpecific: true,
|
|
37378
|
-
description: "String concatenation in a loop \u2014 use StringBuilder",
|
|
37379
|
-
create(_context) {
|
|
37380
|
-
return {};
|
|
37381
|
-
},
|
|
37382
|
-
analyze(_context, facts) {
|
|
37383
|
-
const issues = [];
|
|
37384
|
-
const source = facts.v2?._source;
|
|
37385
|
-
if (!source) return issues;
|
|
37386
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37387
|
-
if (!/\b(for|while|do)\b/.test(source)) return issues;
|
|
37388
|
-
let m;
|
|
37389
|
-
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
37390
|
-
while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
|
|
37391
|
-
const line = source.slice(0, m.index).split("\n").length;
|
|
37392
|
-
issues.push({
|
|
37393
|
-
ruleId: "java/string-concat-loop",
|
|
37394
|
-
category: "perf",
|
|
37395
|
-
severity: "low",
|
|
37396
|
-
aiSpecific: true,
|
|
37397
|
-
message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
|
|
37398
|
-
line,
|
|
37399
|
-
column: 1,
|
|
37400
|
-
advice: "Declare a `StringBuilder` outside the loop: `StringBuilder sb = new StringBuilder(); sb.append(...);` then `return sb.toString();` after the loop. String concatenation in a loop is O(n\xB2) \u2014 each iteration copies the prior string. AI agents concatenate strings in loops because of training-data examples. Reference: java/string-concat-loop v0.20."
|
|
37401
|
-
});
|
|
37402
|
-
}
|
|
37403
|
-
return issues;
|
|
37404
|
-
}
|
|
37405
|
-
});
|
|
37406
|
-
|
|
37407
|
-
// src/rules/java/system-out-println.ts
|
|
37408
|
-
var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
|
|
37409
|
-
var DEFAULT_THRESHOLD2 = 1;
|
|
37410
|
-
var javaSystemOutPrintlnRule = createRule({
|
|
37411
|
-
id: "java/system-out-println",
|
|
37412
|
-
category: "typo",
|
|
37413
|
-
severity: "low",
|
|
37414
|
-
aiSpecific: true,
|
|
37415
|
-
description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
|
|
37416
|
-
create(_context) {
|
|
37417
|
-
return { threshold: DEFAULT_THRESHOLD2 };
|
|
37418
|
-
},
|
|
37419
|
-
analyze(context, facts) {
|
|
37420
|
-
const issues = [];
|
|
37421
|
-
const source = facts.v2?._source;
|
|
37422
|
-
if (!source) return issues;
|
|
37423
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37424
|
-
const matches = [];
|
|
37425
|
-
let m;
|
|
37426
|
-
PRINTLN_REGEX.lastIndex = 0;
|
|
37427
|
-
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
37428
|
-
matches.push(m.index);
|
|
37429
|
-
}
|
|
37430
|
-
if (matches.length <= context.threshold) return issues;
|
|
37431
|
-
const cap = Math.min(matches.length, 10);
|
|
37432
|
-
for (let i = 0; i < cap; i++) {
|
|
37433
|
-
const idx = matches[i];
|
|
37434
|
-
const line = source.slice(0, idx).split("\n").length;
|
|
37435
|
-
issues.push({
|
|
37436
|
-
ruleId: "java/system-out-println",
|
|
37437
|
-
category: "typo",
|
|
37438
|
-
severity: "low",
|
|
37439
|
-
aiSpecific: true,
|
|
37440
|
-
message: `System.out.println at line ${line} \u2014 use a logger for production output`,
|
|
37441
|
-
line,
|
|
37442
|
-
column: 1,
|
|
37443
|
-
advice: "Replace with `private static final Logger log = LoggerFactory.getLogger(...);` then `log.info(...)`. AI agents default to println because their training data has countless textbook examples. Real Java code uses a logger. Reference: java/system-out-println v0.20."
|
|
37444
|
-
});
|
|
37445
|
-
}
|
|
37446
|
-
return issues;
|
|
37447
|
-
}
|
|
37448
|
-
});
|
|
37449
|
-
|
|
37450
|
-
// src/rules/java/verbose-javadoc.ts
|
|
37451
|
-
var TAG_REGEX = /@(param|return|throws)\b/g;
|
|
37452
|
-
var DEFAULT_TAG_THRESHOLD = 3;
|
|
37453
|
-
var DEFAULT_BODY_LENGTH_CAP = 5;
|
|
37454
|
-
var javaVerboseJavadocRule = createRule({
|
|
37455
|
-
id: "java/verbose-javadoc",
|
|
37456
|
-
category: "typo",
|
|
37457
|
-
severity: "low",
|
|
37458
|
-
aiSpecific: true,
|
|
37459
|
-
description: "Excessive Javadoc tags on trivial methods \u2014 over-documentation is an AI fingerprint",
|
|
37460
|
-
create(_context) {
|
|
37461
|
-
return {
|
|
37462
|
-
tagThreshold: DEFAULT_TAG_THRESHOLD,
|
|
37463
|
-
bodyLengthCap: DEFAULT_BODY_LENGTH_CAP
|
|
37464
|
-
};
|
|
37465
|
-
},
|
|
37466
|
-
analyze(context, facts) {
|
|
37467
|
-
const issues = [];
|
|
37468
|
-
const source = facts.v2?._source;
|
|
37469
|
-
if (!source) return issues;
|
|
37470
|
-
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37471
|
-
TAG_REGEX.lastIndex = 0;
|
|
37472
|
-
let tagCount = 0;
|
|
37473
|
-
let firstTagLine = 0;
|
|
37474
|
-
let m;
|
|
37475
|
-
while ((m = TAG_REGEX.exec(source)) !== null) {
|
|
37476
|
-
tagCount++;
|
|
37477
|
-
if (firstTagLine === 0) {
|
|
37478
|
-
firstTagLine = source.slice(0, m.index).split("\n").length;
|
|
37479
|
-
}
|
|
37480
|
-
}
|
|
37481
|
-
if (tagCount < context.tagThreshold) return issues;
|
|
37482
|
-
const lineCount = source.split("\n").length;
|
|
37483
|
-
const tagDensity = tagCount / Math.max(lineCount, 1);
|
|
37484
|
-
if (tagDensity < 0.05) return issues;
|
|
37485
|
-
if (lineCount > 200) return issues;
|
|
37486
|
-
issues.push({
|
|
37487
|
-
ruleId: "java/verbose-javadoc",
|
|
37488
|
-
category: "typo",
|
|
37489
|
-
severity: "low",
|
|
37490
|
-
aiSpecific: true,
|
|
37491
|
-
message: `${tagCount} Javadoc tags in ${lineCount} lines (density ${(tagDensity * 100).toFixed(1)}%) \u2014 likely over-documented`,
|
|
37492
|
-
line: firstTagLine,
|
|
37493
|
-
column: 1,
|
|
37494
|
-
advice: "Skip Javadoc on trivial methods (getters, setters, builders). AI agents default to over-documentation because their training data has countless textbook Javadoc examples. Real Java code limits Javadoc to public API surface. Reference: java/verbose-javadoc v0.26.0."
|
|
37495
|
-
});
|
|
37496
|
-
return issues;
|
|
37497
|
-
}
|
|
37498
|
-
});
|
|
37499
|
-
|
|
37500
37003
|
// src/rules/kotlin/coroutine-global-scope.ts
|
|
37501
37004
|
var GLOBAL_SCOPE_REGEX = /GlobalScope\s*\.\s*(?:launch|async|runBlocking)\s*[({]/g;
|
|
37502
37005
|
var kotlinCoroutineGlobalScopeRule = createRule({
|
|
@@ -37512,7 +37015,7 @@ var kotlinCoroutineGlobalScopeRule = createRule({
|
|
|
37512
37015
|
const issues = [];
|
|
37513
37016
|
const source = facts.v2?._source;
|
|
37514
37017
|
if (!source) return issues;
|
|
37515
|
-
if (!/\.
|
|
37018
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37516
37019
|
let m;
|
|
37517
37020
|
GLOBAL_SCOPE_REGEX.lastIndex = 0;
|
|
37518
37021
|
while ((m = GLOBAL_SCOPE_REGEX.exec(source)) !== null) {
|
|
@@ -37549,7 +37052,7 @@ var kotlinDataClassDefaultsOveruseRule = createRule({
|
|
|
37549
37052
|
const issues = [];
|
|
37550
37053
|
const source = facts.v2?._source;
|
|
37551
37054
|
if (!source) return issues;
|
|
37552
|
-
if (!/\.
|
|
37055
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37553
37056
|
let m;
|
|
37554
37057
|
DATA_CLASS_HEAD_REGEX.lastIndex = 0;
|
|
37555
37058
|
while ((m = DATA_CLASS_HEAD_REGEX.exec(source)) !== null) {
|
|
@@ -37604,7 +37107,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
|
|
|
37604
37107
|
const issues = [];
|
|
37605
37108
|
const source = facts.v2?._source;
|
|
37606
37109
|
if (!source) return issues;
|
|
37607
|
-
if (!/\.
|
|
37110
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37608
37111
|
let m;
|
|
37609
37112
|
OBJECT_DECL_REGEX.lastIndex = 0;
|
|
37610
37113
|
while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
|
|
@@ -37640,8 +37143,8 @@ var kotlinObjectSingletonMisuseRule = createRule({
|
|
|
37640
37143
|
});
|
|
37641
37144
|
|
|
37642
37145
|
// src/rules/kotlin/println-debug.ts
|
|
37643
|
-
var
|
|
37644
|
-
var
|
|
37146
|
+
var PRINTLN_REGEX = /^\s*println\s*\(/gm;
|
|
37147
|
+
var DEFAULT_THRESHOLD = 1;
|
|
37645
37148
|
var kotlinPrintlnDebugRule = createRule({
|
|
37646
37149
|
id: "kotlin/println-debug",
|
|
37647
37150
|
category: "typo",
|
|
@@ -37649,17 +37152,17 @@ var kotlinPrintlnDebugRule = createRule({
|
|
|
37649
37152
|
aiSpecific: true,
|
|
37650
37153
|
description: "println(...) in production code \u2014 use Timber, android.util.Log, or an SLF4J facade",
|
|
37651
37154
|
create(_context) {
|
|
37652
|
-
return { threshold:
|
|
37155
|
+
return { threshold: DEFAULT_THRESHOLD };
|
|
37653
37156
|
},
|
|
37654
37157
|
analyze(context, facts) {
|
|
37655
37158
|
const issues = [];
|
|
37656
37159
|
const source = facts.v2?._source;
|
|
37657
37160
|
if (!source) return issues;
|
|
37658
|
-
if (!/\.
|
|
37161
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37659
37162
|
const matches = [];
|
|
37660
37163
|
let m;
|
|
37661
|
-
|
|
37662
|
-
while ((m =
|
|
37164
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
37165
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
37663
37166
|
matches.push(m.index);
|
|
37664
37167
|
}
|
|
37665
37168
|
if (matches.length <= context.threshold) return issues;
|
|
@@ -37683,7 +37186,7 @@ var kotlinPrintlnDebugRule = createRule({
|
|
|
37683
37186
|
});
|
|
37684
37187
|
|
|
37685
37188
|
// src/rules/kotlin/string-concat-loop.ts
|
|
37686
|
-
var
|
|
37189
|
+
var STRING_CONCAT_REGEX = /\b(\w+)\s*=\s*\1\s*\+\s*[^;}]+[;}\n]/g;
|
|
37687
37190
|
var kotlinStringConcatLoopRule = createRule({
|
|
37688
37191
|
id: "kotlin/string-concat-loop",
|
|
37689
37192
|
category: "perf",
|
|
@@ -37697,11 +37200,11 @@ var kotlinStringConcatLoopRule = createRule({
|
|
|
37697
37200
|
const issues = [];
|
|
37698
37201
|
const source = facts.v2?._source;
|
|
37699
37202
|
if (!source) return issues;
|
|
37700
|
-
if (!/\.
|
|
37203
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37701
37204
|
if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
|
|
37702
37205
|
let m;
|
|
37703
|
-
|
|
37704
|
-
while ((m =
|
|
37206
|
+
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
37207
|
+
while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
|
|
37705
37208
|
const line = source.slice(0, m.index).split("\n").length;
|
|
37706
37209
|
const lineText = source.slice(0, m.index).split("\n").pop() ?? "";
|
|
37707
37210
|
if (/\.append\s*\(/.test(lineText)) continue;
|
|
@@ -40579,7 +40082,7 @@ var swiftImplicitlyUnwrappedOptionalRule = createRule({
|
|
|
40579
40082
|
|
|
40580
40083
|
// src/rules/swift/print-debug.ts
|
|
40581
40084
|
var PRINT_REGEX = /\bprint\s*\(/g;
|
|
40582
|
-
var
|
|
40085
|
+
var DEFAULT_THRESHOLD2 = 1;
|
|
40583
40086
|
var swiftPrintDebugRule = createRule({
|
|
40584
40087
|
id: "swift/print-debug",
|
|
40585
40088
|
category: "typo",
|
|
@@ -40587,7 +40090,7 @@ var swiftPrintDebugRule = createRule({
|
|
|
40587
40090
|
aiSpecific: true,
|
|
40588
40091
|
description: "print(...) in production Swift \u2014 use Logger (os.log) for level-controlled output",
|
|
40589
40092
|
create(_context) {
|
|
40590
|
-
return { threshold:
|
|
40093
|
+
return { threshold: DEFAULT_THRESHOLD2 };
|
|
40591
40094
|
},
|
|
40592
40095
|
analyze(context, facts) {
|
|
40593
40096
|
const issues = [];
|
|
@@ -43242,17 +42745,6 @@ var builtinRules = [
|
|
|
43242
42745
|
goErrorWrapWithoutContextRule,
|
|
43243
42746
|
goNilSliceVsEmptyRule,
|
|
43244
42747
|
goStructTagInconsistencyRule,
|
|
43245
|
-
javaArraylistVsLinkedlistRule,
|
|
43246
|
-
javaBuilderOveruseRule,
|
|
43247
|
-
javaEmptyCatchBlockRule,
|
|
43248
|
-
javaImmutableCollectionPreferenceRule,
|
|
43249
|
-
javaLegacyDateApiRule,
|
|
43250
|
-
javaOptionalOveruseRule,
|
|
43251
|
-
javaRawTypeOveruseRule,
|
|
43252
|
-
javaStreamOveruseRule,
|
|
43253
|
-
javaStringConcatLoopRule,
|
|
43254
|
-
javaSystemOutPrintlnRule,
|
|
43255
|
-
javaVerboseJavadocRule,
|
|
43256
42748
|
kotlinCoroutineGlobalScopeRule,
|
|
43257
42749
|
kotlinDataClassDefaultsOveruseRule,
|
|
43258
42750
|
kotlinObjectSingletonMisuseRule,
|
|
@@ -44117,224 +43609,6 @@ var signal_strength_default = {
|
|
|
44117
43609
|
_v8Lift: 1,
|
|
44118
43610
|
defaultOff: true
|
|
44119
43611
|
},
|
|
44120
|
-
"java/arraylist-vs-linkedlist": {
|
|
44121
|
-
recall: 35e-4,
|
|
44122
|
-
fpRate: 94e-4,
|
|
44123
|
-
ratio: 0.37,
|
|
44124
|
-
precision: 0.0446,
|
|
44125
|
-
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
44126
|
-
verdict: "DORMANT",
|
|
44127
|
-
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files = 92196 total. v9 TP=36, FP=771, P=4.46%, FPR=0.94%, recall=0.35%, ratio=0.37, lift=P/FPR=4.74. Rule fires more on legacy enterprise Java (neg) than on AI/ML-integration Java (pos) because modern Java idiom defaults to ArrayList. v0.20: new rule (new LinkedList<>() \u2014 use ArrayList, Effective Java Item 28).",
|
|
44128
|
-
aiSpecific: true,
|
|
44129
|
-
_v7Verdict: "DORMANT",
|
|
44130
|
-
_v7Lift: 1,
|
|
44131
|
-
_v7Recall: 0,
|
|
44132
|
-
_v7FpRate: 0,
|
|
44133
|
-
_v7Precision: 0,
|
|
44134
|
-
_v8Verdict: "DORMANT",
|
|
44135
|
-
_v8Lift: 1,
|
|
44136
|
-
_v9Verdict: "DORMANT",
|
|
44137
|
-
_v9Lift: 4.74,
|
|
44138
|
-
_v9Recall: 35e-4,
|
|
44139
|
-
_v9FpRate: 94e-4,
|
|
44140
|
-
_v9Precision: 0.0446,
|
|
44141
|
-
defaultOff: true
|
|
44142
|
-
},
|
|
44143
|
-
"java/empty-catch-block": {
|
|
44144
|
-
recall: 8e-3,
|
|
44145
|
-
fpRate: 0.1118,
|
|
44146
|
-
ratio: 0.07,
|
|
44147
|
-
precision: 89e-4,
|
|
44148
|
-
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
44149
|
-
verdict: "DORMANT",
|
|
44150
|
-
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=82, FP=9159, P=0.89%, FPR=11.18%, recall=0.80%, ratio=0.07, lift=P/FPR=0.08. Strongly INVERTED: empty catch blocks are 14x more common in legacy enterprise Java than in AI/Java-modern pos repos. Heuristic likely needs to be tightened (e.g., require at least a comment) before it can serve as a meaningful discriminator. v0.20: new rule.",
|
|
44151
|
-
aiSpecific: true,
|
|
44152
|
-
_v7Verdict: "DORMANT",
|
|
44153
|
-
_v7Lift: 1,
|
|
44154
|
-
_v7Recall: 0,
|
|
44155
|
-
_v7FpRate: 0,
|
|
44156
|
-
_v7Precision: 0,
|
|
44157
|
-
_v8Verdict: "DORMANT",
|
|
44158
|
-
_v8Lift: 1,
|
|
44159
|
-
_v9Verdict: "DORMANT",
|
|
44160
|
-
_v9Lift: 0.08,
|
|
44161
|
-
_v9Recall: 8e-3,
|
|
44162
|
-
_v9FpRate: 0.1118,
|
|
44163
|
-
_v9Precision: 89e-4,
|
|
44164
|
-
defaultOff: true
|
|
44165
|
-
},
|
|
44166
|
-
"java/legacy-date-api": {
|
|
44167
|
-
recall: 0.0285,
|
|
44168
|
-
fpRate: 0.0511,
|
|
44169
|
-
ratio: 0.56,
|
|
44170
|
-
precision: 0.0657,
|
|
44171
|
-
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
44172
|
-
verdict: "DORMANT",
|
|
44173
|
-
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=294, FP=4181, P=6.57%, FPR=5.11%, recall=2.85%, ratio=0.56, lift=P/FPR=1.29. INVERTED-to-NOISY: legacy date API is more common in older enterprise Java (neg) than in modern AI/Java repos (pos). The heuristic correctly flags the legacy pattern, but the legacy pattern is over-represented in the neg baseline (older codebases). v0.20: new rule.",
|
|
44174
|
-
aiSpecific: true,
|
|
44175
|
-
_v7Verdict: "DORMANT",
|
|
44176
|
-
_v7Lift: 1,
|
|
44177
|
-
_v7Recall: 0,
|
|
44178
|
-
_v7FpRate: 0,
|
|
44179
|
-
_v7Precision: 0,
|
|
44180
|
-
_v8Verdict: "DORMANT",
|
|
44181
|
-
_v8Lift: 1,
|
|
44182
|
-
_v9Verdict: "DORMANT",
|
|
44183
|
-
_v9Lift: 1.29,
|
|
44184
|
-
_v9Recall: 0.0285,
|
|
44185
|
-
_v9FpRate: 0.0511,
|
|
44186
|
-
_v9Precision: 0.0657,
|
|
44187
|
-
defaultOff: true
|
|
44188
|
-
},
|
|
44189
|
-
"java/verbose-javadoc": {
|
|
44190
|
-
recall: 0,
|
|
44191
|
-
fpRate: 0,
|
|
44192
|
-
ratio: 1,
|
|
44193
|
-
precision: 0,
|
|
44194
|
-
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44195
|
-
verdict: "DORMANT",
|
|
44196
|
-
_calibrationNote: "v0.26.0: new positive AI-signal rule. Triggers when a small Java file (< 200 lines) has 3+ Javadoc tags with density >= 0.05. v0.26.1 CALIBRATION (14769-file biased sample from v9-java corpus, files that fired >=1 of the 6 existing java rules): TP=0, FP=0, ratio=N/A. HYPOTHESIS FAILED. The v0.26.0 intuition that 'AI over-documents trivial methods' is NOT supported by the v9 corpus \u2014 AI code in Spring AI, LangChain4j, etc. has the same Javadoc density as human code. v0.20 hypothesis (humans avoid println/Date/raw types) and v0.26 hypothesis (AI over-documents/over-Opts/over-Builders) are both wrong for the v9 corpus. v0.27+ will redesign with a different approach: either train a classifier on the v9 features or use AST-based detection rather than regex heuristics. Reference: java/verbose-javadoc v0.26.0; calibration v0.26.1.",
|
|
44197
|
-
aiSpecific: true,
|
|
44198
|
-
_v9Verdict: "DORMANT",
|
|
44199
|
-
_v9Lift: 1,
|
|
44200
|
-
_v9Recall: 0,
|
|
44201
|
-
_v9FpRate: 0,
|
|
44202
|
-
_v9Precision: 0,
|
|
44203
|
-
defaultOff: true
|
|
44204
|
-
},
|
|
44205
|
-
"java/optional-overuse": {
|
|
44206
|
-
recall: 0,
|
|
44207
|
-
fpRate: 0,
|
|
44208
|
-
ratio: 1,
|
|
44209
|
-
precision: 0,
|
|
44210
|
-
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44211
|
-
verdict: "DORMANT",
|
|
44212
|
-
_calibrationNote: "v0.26.0: new positive AI-signal rule. Triggers when a Java file has 2+ .orElseThrow() calls AND 0 Objects.requireNonNull()/null checks (optionalRatio > 0.6). v0.26.1 CALIBRATION (14769-file biased sample): TP=0, FP=0, ratio=N/A. HYPOTHESIS FAILED. AI in the v9 corpus does NOT use Optional chains more than humans. v0.27+ will redesign. Reference: java/optional-overuse v0.26.0; calibration v0.26.1.",
|
|
44213
|
-
aiSpecific: true,
|
|
44214
|
-
_v9Verdict: "DORMANT",
|
|
44215
|
-
_v9Lift: 1,
|
|
44216
|
-
_v9Recall: 0,
|
|
44217
|
-
_v9FpRate: 0,
|
|
44218
|
-
_v9Precision: 0,
|
|
44219
|
-
defaultOff: true
|
|
44220
|
-
},
|
|
44221
|
-
"java/immutable-collection-preference": {
|
|
44222
|
-
recall: 0,
|
|
44223
|
-
fpRate: 0,
|
|
44224
|
-
ratio: 1,
|
|
44225
|
-
precision: 0,
|
|
44226
|
-
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44227
|
-
verdict: "DORMANT",
|
|
44228
|
-
_calibrationNote: "v0.26.0: new positive AI-signal rule. Triggers when a Java file has 5+ List.of/Map.of/Set.of calls AND < 1 new ArrayList/HashMap/HashSet/LinkedList/TreeMap calls. v0.26.1 CALIBRATION (14769-file biased sample): TP=0, FP=0, ratio=N/A. HYPOTHESIS FAILED. AI does NOT use immutable factory methods more than humans. v0.27+ will redesign. Reference: java/immutable-collection-preference v0.26.0; calibration v0.26.1.",
|
|
44229
|
-
aiSpecific: true,
|
|
44230
|
-
_v9Verdict: "DORMANT",
|
|
44231
|
-
_v9Lift: 1,
|
|
44232
|
-
_v9Recall: 0,
|
|
44233
|
-
_v9FpRate: 0,
|
|
44234
|
-
_v9Precision: 0,
|
|
44235
|
-
defaultOff: true
|
|
44236
|
-
},
|
|
44237
|
-
"java/builder-overuse": {
|
|
44238
|
-
recall: 0,
|
|
44239
|
-
fpRate: 0,
|
|
44240
|
-
ratio: 1,
|
|
44241
|
-
precision: 0,
|
|
44242
|
-
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44243
|
-
verdict: "DORMANT",
|
|
44244
|
-
_calibrationNote: "v0.26.0: new positive AI-signal rule. Triggers when @Builder annotation (Lombok) is used on a class with <= 3 fields. v0.26.1 CALIBRATION (14769-file biased sample): TP=0, FP=0, ratio=N/A. HYPOTHESIS FAILED. AI does NOT use @Builder on small classes more than humans. v0.27+ will redesign. Reference: java/builder-overuse v0.26.0; calibration v0.26.1.",
|
|
44245
|
-
aiSpecific: true,
|
|
44246
|
-
_v9Verdict: "DORMANT",
|
|
44247
|
-
_v9Lift: 1,
|
|
44248
|
-
_v9Recall: 0,
|
|
44249
|
-
_v9FpRate: 0,
|
|
44250
|
-
_v9Precision: 0,
|
|
44251
|
-
defaultOff: true
|
|
44252
|
-
},
|
|
44253
|
-
"java/stream-overuse": {
|
|
44254
|
-
recall: 0,
|
|
44255
|
-
fpRate: 0,
|
|
44256
|
-
ratio: 1,
|
|
44257
|
-
precision: 0,
|
|
44258
|
-
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44259
|
-
verdict: "DORMANT",
|
|
44260
|
-
_calibrationNote: "v0.26.0: new positive AI-signal rule. Triggers when a single line has 3+ Stream API operations (.map, .filter, .flatMap, .collect, .reduce, .sorted, .distinct, .limit, .skip, .anyMatch, .allMatch, .noneMatch, .findFirst, .findAny). v0.26.1 CALIBRATION (14769-file biased sample): TP=0, FP=0, ratio=N/A. HYPOTHESIS FAILED. AI does NOT use Stream API chains more than humans. v0.27+ will redesign. Reference: java/stream-overuse v0.26.0; calibration v0.26.1.",
|
|
44261
|
-
aiSpecific: true,
|
|
44262
|
-
_v9Verdict: "DORMANT",
|
|
44263
|
-
_v9Lift: 1,
|
|
44264
|
-
_v9Recall: 0,
|
|
44265
|
-
_v9FpRate: 0,
|
|
44266
|
-
_v9Precision: 0,
|
|
44267
|
-
defaultOff: true
|
|
44268
|
-
},
|
|
44269
|
-
"java/raw-type-overuse": {
|
|
44270
|
-
recall: 0.1093,
|
|
44271
|
-
fpRate: 0.2041,
|
|
44272
|
-
ratio: 0.54,
|
|
44273
|
-
precision: 0.0632,
|
|
44274
|
-
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
44275
|
-
verdict: "DORMANT",
|
|
44276
|
-
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=1126, FP=16712, P=6.32%, FPR=20.41%, recall=10.93%, ratio=0.54, lift=P/FPR=0.31. Strongly INVERTED: raw types (no generics) are 2x more common in legacy enterprise Java (neg) than in modern Java repos (pos). Heuristic is correctly identifying the legacy anti-pattern, but it's an age marker more than an AI-fingerprint. v0.20: new rule.",
|
|
44277
|
-
aiSpecific: true,
|
|
44278
|
-
_v7Verdict: "DORMANT",
|
|
44279
|
-
_v7Lift: 1,
|
|
44280
|
-
_v7Recall: 0,
|
|
44281
|
-
_v7FpRate: 0,
|
|
44282
|
-
_v7Precision: 0,
|
|
44283
|
-
_v8Verdict: "DORMANT",
|
|
44284
|
-
_v8Lift: 1,
|
|
44285
|
-
_v9Verdict: "DORMANT",
|
|
44286
|
-
_v9Lift: 0.31,
|
|
44287
|
-
_v9Recall: 0.1093,
|
|
44288
|
-
_v9FpRate: 0.2041,
|
|
44289
|
-
_v9Precision: 0.0632,
|
|
44290
|
-
defaultOff: true
|
|
44291
|
-
},
|
|
44292
|
-
"java/string-concat-loop": {
|
|
44293
|
-
recall: 46e-4,
|
|
44294
|
-
fpRate: 0.0358,
|
|
44295
|
-
ratio: 0.13,
|
|
44296
|
-
precision: 0.0158,
|
|
44297
|
-
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
44298
|
-
verdict: "DORMANT",
|
|
44299
|
-
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=47, FP=2933, P=1.58%, FPR=3.58%, recall=0.46%, ratio=0.13, lift=P/FPR=0.44. Strongly INVERTED: string concatenation in a loop is 8x more common in legacy enterprise Java (neg) than in AI/ML/Java-modern pos repos. Modern Java training data and modern IDE warnings discourage the pattern. v0.20: new rule.",
|
|
44300
|
-
aiSpecific: true,
|
|
44301
|
-
_v7Verdict: "DORMANT",
|
|
44302
|
-
_v7Lift: 1,
|
|
44303
|
-
_v7Recall: 0,
|
|
44304
|
-
_v7FpRate: 0,
|
|
44305
|
-
_v7Precision: 0,
|
|
44306
|
-
_v8Verdict: "DORMANT",
|
|
44307
|
-
_v8Lift: 1,
|
|
44308
|
-
_v9Verdict: "DORMANT",
|
|
44309
|
-
_v9Lift: 0.44,
|
|
44310
|
-
_v9Recall: 46e-4,
|
|
44311
|
-
_v9FpRate: 0.0358,
|
|
44312
|
-
_v9Precision: 0.0158,
|
|
44313
|
-
defaultOff: true
|
|
44314
|
-
},
|
|
44315
|
-
"java/system-out-println": {
|
|
44316
|
-
recall: 0.1503,
|
|
44317
|
-
fpRate: 0.2533,
|
|
44318
|
-
ratio: 0.59,
|
|
44319
|
-
precision: 0.0695,
|
|
44320
|
-
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
44321
|
-
verdict: "DORMANT",
|
|
44322
|
-
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=1549, FP=20737, P=6.95%, FPR=25.32%, recall=15.03%, ratio=0.59, lift=P/FPR=0.27. INVERTED: System.out.println is more common in legacy enterprise Java (neg) than in modern AI/Java repos (pos). Modern Java defaults to SLF4J/Log4j, AI-generated Java starts with a logger import. The pattern is the opposite of the v0.20 design intent (which assumed println was the AI-fingerprint signal). v0.25.x: consider gating this rule by Java era (recent file mtime) or making it an age-fingerprint rather than AI-fingerprint. v0.20: new rule.",
|
|
44323
|
-
aiSpecific: true,
|
|
44324
|
-
_v7Verdict: "DORMANT",
|
|
44325
|
-
_v7Lift: 1,
|
|
44326
|
-
_v7Recall: 0,
|
|
44327
|
-
_v7FpRate: 0,
|
|
44328
|
-
_v7Precision: 0,
|
|
44329
|
-
_v8Verdict: "DORMANT",
|
|
44330
|
-
_v8Lift: 1,
|
|
44331
|
-
_v9Verdict: "DORMANT",
|
|
44332
|
-
_v9Lift: 0.27,
|
|
44333
|
-
_v9Recall: 0.1503,
|
|
44334
|
-
_v9FpRate: 0.2533,
|
|
44335
|
-
_v9Precision: 0.0695,
|
|
44336
|
-
defaultOff: true
|
|
44337
|
-
},
|
|
44338
43612
|
"layout/forced-layout": {
|
|
44339
43613
|
recall: 0,
|
|
44340
43614
|
fpRate: 0,
|
|
@@ -45579,13 +44853,13 @@ var signal_strength_default = {
|
|
|
45579
44853
|
defaultOff: true
|
|
45580
44854
|
},
|
|
45581
44855
|
"kotlin/data-class-defaults-overuse": {
|
|
45582
|
-
recall:
|
|
45583
|
-
fpRate:
|
|
45584
|
-
ratio:
|
|
45585
|
-
precision: 0,
|
|
45586
|
-
lastCalibratedAt: "2026-07-
|
|
44856
|
+
recall: 469e-5,
|
|
44857
|
+
fpRate: 222e-5,
|
|
44858
|
+
ratio: 2.11,
|
|
44859
|
+
precision: 0.1429,
|
|
44860
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45587
44861
|
verdict: "DORMANT",
|
|
45588
|
-
_calibrationNote: "v0.
|
|
44862
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=2.11 (\u22651.5) but precision=14.3% (<50%) \u2014 verdict DORMANT. Era-confounding: pre-2022 Kotlin is sparse; the rule fires on the rare .kt file with multiple default values regardless of authorship.",
|
|
45589
44863
|
aiSpecific: true,
|
|
45590
44864
|
_v7Verdict: "DORMANT",
|
|
45591
44865
|
_v7Lift: 1,
|
|
@@ -45594,16 +44868,21 @@ var signal_strength_default = {
|
|
|
45594
44868
|
_v7Precision: 0,
|
|
45595
44869
|
_v8Verdict: "DORMANT",
|
|
45596
44870
|
_v8Lift: 1,
|
|
44871
|
+
_v9Verdict: "DORMANT",
|
|
44872
|
+
_v9Lift: 2.11,
|
|
44873
|
+
_v9Recall: 469e-5,
|
|
44874
|
+
_v9FpRate: 222e-5,
|
|
44875
|
+
_v9Precision: 0.1429,
|
|
45597
44876
|
defaultOff: true
|
|
45598
44877
|
},
|
|
45599
44878
|
"kotlin/coroutine-global-scope": {
|
|
45600
|
-
recall:
|
|
45601
|
-
fpRate: 0,
|
|
45602
|
-
ratio:
|
|
45603
|
-
precision:
|
|
45604
|
-
lastCalibratedAt: "2026-07-
|
|
44879
|
+
recall: 469e-5,
|
|
44880
|
+
fpRate: 0.05004,
|
|
44881
|
+
ratio: 0.09,
|
|
44882
|
+
precision: 735e-5,
|
|
44883
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45605
44884
|
verdict: "DORMANT",
|
|
45606
|
-
_calibrationNote: "v0.
|
|
44885
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.09 (FAR <1.0) \u2014 fires 135x more on neg than pos. Classic era-confound: pre-2022 Kotlin coroutines had GlobalScope as the default; modern Kotlin uses viewModelScope / lifecycleScope. The rule detects era, not AI authorship.",
|
|
45607
44886
|
aiSpecific: true,
|
|
45608
44887
|
_v7Verdict: "DORMANT",
|
|
45609
44888
|
_v7Lift: 1,
|
|
@@ -45612,16 +44891,21 @@ var signal_strength_default = {
|
|
|
45612
44891
|
_v7Precision: 0,
|
|
45613
44892
|
_v8Verdict: "DORMANT",
|
|
45614
44893
|
_v8Lift: 1,
|
|
44894
|
+
_v9Verdict: "DORMANT",
|
|
44895
|
+
_v9Lift: 0.09,
|
|
44896
|
+
_v9Recall: 469e-5,
|
|
44897
|
+
_v9FpRate: 0.05004,
|
|
44898
|
+
_v9Precision: 735e-5,
|
|
45615
44899
|
defaultOff: true
|
|
45616
44900
|
},
|
|
45617
44901
|
"kotlin/println-debug": {
|
|
45618
|
-
recall: 0,
|
|
45619
|
-
fpRate: 0,
|
|
45620
|
-
ratio:
|
|
45621
|
-
precision: 0,
|
|
45622
|
-
lastCalibratedAt: "2026-07-
|
|
44902
|
+
recall: 0.07042,
|
|
44903
|
+
fpRate: 0.11379,
|
|
44904
|
+
ratio: 0.62,
|
|
44905
|
+
precision: 0.04659,
|
|
44906
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45623
44907
|
verdict: "DORMANT",
|
|
45624
|
-
_calibrationNote: "v0.
|
|
44908
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.62 (\u226A1.0) \u2014 fires 20x more on neg than pos. Classic era-confound: pre-2022 Kotlin code uses println() for debug; modern Kotlin uses Timber / android.util.Log / kermit.",
|
|
45625
44909
|
aiSpecific: true,
|
|
45626
44910
|
_v7Verdict: "DORMANT",
|
|
45627
44911
|
_v7Lift: 1,
|
|
@@ -45630,16 +44914,21 @@ var signal_strength_default = {
|
|
|
45630
44914
|
_v7Precision: 0,
|
|
45631
44915
|
_v8Verdict: "DORMANT",
|
|
45632
44916
|
_v8Lift: 1,
|
|
44917
|
+
_v9Verdict: "DORMANT",
|
|
44918
|
+
_v9Lift: 0.62,
|
|
44919
|
+
_v9Recall: 0.07042,
|
|
44920
|
+
_v9FpRate: 0.11379,
|
|
44921
|
+
_v9Precision: 0.04659,
|
|
45633
44922
|
defaultOff: true
|
|
45634
44923
|
},
|
|
45635
44924
|
"kotlin/object-singleton-misuse": {
|
|
45636
|
-
recall:
|
|
45637
|
-
fpRate:
|
|
45638
|
-
ratio: 1,
|
|
45639
|
-
precision: 0,
|
|
45640
|
-
lastCalibratedAt: "2026-07-
|
|
44925
|
+
recall: 469e-5,
|
|
44926
|
+
fpRate: 371e-5,
|
|
44927
|
+
ratio: 1.27,
|
|
44928
|
+
precision: 0.09091,
|
|
44929
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45641
44930
|
verdict: "DORMANT",
|
|
45642
|
-
_calibrationNote: "v0.
|
|
44931
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.27 (\u22651.0) but <1.5; precision=9.1% \u2014 verdict DORMANT. INSUFFICIENT_DATA: pos arm only 213 files (under 10k minimum).",
|
|
45643
44932
|
aiSpecific: true,
|
|
45644
44933
|
_v7Verdict: "DORMANT",
|
|
45645
44934
|
_v7Lift: 1,
|
|
@@ -45648,16 +44937,21 @@ var signal_strength_default = {
|
|
|
45648
44937
|
_v7Precision: 0,
|
|
45649
44938
|
_v8Verdict: "DORMANT",
|
|
45650
44939
|
_v8Lift: 1,
|
|
44940
|
+
_v9Verdict: "DORMANT",
|
|
44941
|
+
_v9Lift: 1.27,
|
|
44942
|
+
_v9Recall: 469e-5,
|
|
44943
|
+
_v9FpRate: 371e-5,
|
|
44944
|
+
_v9Precision: 0.09091,
|
|
45651
44945
|
defaultOff: true
|
|
45652
44946
|
},
|
|
45653
44947
|
"kotlin/string-concat-loop": {
|
|
45654
|
-
recall:
|
|
45655
|
-
fpRate:
|
|
45656
|
-
ratio: 1,
|
|
45657
|
-
precision: 0,
|
|
45658
|
-
lastCalibratedAt: "2026-07-
|
|
44948
|
+
recall: 939e-5,
|
|
44949
|
+
fpRate: 519e-5,
|
|
44950
|
+
ratio: 1.81,
|
|
44951
|
+
precision: 0.125,
|
|
44952
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45659
44953
|
verdict: "DORMANT",
|
|
45660
|
-
_calibrationNote: "v0.
|
|
44954
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.81 (\u22651.5) but precision=12.5% (<50%) \u2014 verdict DORMANT. INSUFFICIENT_DATA: pos arm only 213 files.",
|
|
45661
44955
|
aiSpecific: true,
|
|
45662
44956
|
_v7Verdict: "DORMANT",
|
|
45663
44957
|
_v7Lift: 1,
|
|
@@ -45666,6 +44960,11 @@ var signal_strength_default = {
|
|
|
45666
44960
|
_v7Precision: 0,
|
|
45667
44961
|
_v8Verdict: "DORMANT",
|
|
45668
44962
|
_v8Lift: 1,
|
|
44963
|
+
_v9Verdict: "DORMANT",
|
|
44964
|
+
_v9Lift: 1.81,
|
|
44965
|
+
_v9Recall: 939e-5,
|
|
44966
|
+
_v9FpRate: 519e-5,
|
|
44967
|
+
_v9Precision: 0.125,
|
|
45669
44968
|
defaultOff: true
|
|
45670
44969
|
},
|
|
45671
44970
|
"swift/force-unwrap": {
|
|
@@ -45898,8 +45197,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
|
45898
45197
|
const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
|
|
45899
45198
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
45900
45199
|
".swift",
|
|
45901
|
-
".kt",
|
|
45902
|
-
".kts",
|
|
45903
45200
|
".dart",
|
|
45904
45201
|
".cpp",
|
|
45905
45202
|
".cc",
|