@wbern/obscene 1.5.0 → 2.0.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/README.md +18 -2
- package/dist/cli.js +65 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -252,9 +252,25 @@ Docs: https://github.com/wbern/obscene#metrics
|
|
|
252
252
|
|
|
253
253
|
Any language [scc supports](https://github.com/boyter/scc#features) — 200+ languages including C, C++, Go, Java, JavaScript, TypeScript, Python, Rust, Ruby, PHP, Swift, Kotlin, and many more. No configuration needed; scc auto-detects languages from file extensions.
|
|
254
254
|
|
|
255
|
-
##
|
|
255
|
+
## Exclusions
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
All exclusions are opt-in. Run `obscene init` to generate a `.obsignore` file with recommended patterns for your project:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
obscene init
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
This creates a `.obsignore` containing:
|
|
264
|
+
- **Universal exclusions** — test files (`*.test.*`, `*.spec.*`, `__tests__/`, etc.), lock files (`package-lock.json`, `pnpm-lock.yaml`, etc.), and package manifests (`package.json`)
|
|
265
|
+
- **Detected project patterns** — CI directories (`.github/`), config files (`*.config.*`), vendored code, etc., based on your project structure
|
|
266
|
+
|
|
267
|
+
If no `.obsignore` or `.obsceneignore` exists, obscene prints a hint to stderr:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
hint: no .obsignore found — run `obscene init` to generate one with recommended exclusions
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
scc also skips generated files by default (`--no-gen`).
|
|
258
274
|
|
|
259
275
|
## Ignore files
|
|
260
276
|
|
package/dist/cli.js
CHANGED
|
@@ -22,22 +22,32 @@ function readIgnoreFile() {
|
|
|
22
22
|
}
|
|
23
23
|
return [];
|
|
24
24
|
}
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
var UNIVERSAL_IGNORE_GROUPS = [
|
|
26
|
+
{
|
|
27
|
+
title: "Test files and test infrastructure",
|
|
28
|
+
patterns: [
|
|
29
|
+
{ pattern: "*.test.*", comment: "Unit test files" },
|
|
30
|
+
{ pattern: "*.spec.*", comment: "Spec test files" },
|
|
31
|
+
{ pattern: "*.integration.test.*", comment: "Integration tests" },
|
|
32
|
+
{ pattern: "test-setup.*", comment: "Test setup files" },
|
|
33
|
+
{ pattern: "test-utils.*", comment: "Test utility files" },
|
|
34
|
+
{ pattern: "test-helpers.*", comment: "Test helper files" },
|
|
35
|
+
{ pattern: "__tests__/**", comment: "Test directories" },
|
|
36
|
+
{ pattern: "__mocks__/**", comment: "Mock directories" },
|
|
37
|
+
{ pattern: "*.stories.*", comment: "Storybook stories" },
|
|
38
|
+
{ pattern: "*.d.ts", comment: "TypeScript declaration files" }
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: "Lock files and package manifests",
|
|
43
|
+
patterns: [
|
|
44
|
+
{ pattern: "package.json", comment: "npm package manifest" },
|
|
45
|
+
{ pattern: "package-lock.json", comment: "npm lock file" },
|
|
46
|
+
{ pattern: "pnpm-lock.yaml", comment: "pnpm lock file" },
|
|
47
|
+
{ pattern: "yarn.lock", comment: "Yarn lock file" },
|
|
48
|
+
{ pattern: "bun.lock", comment: "Bun lock file" }
|
|
49
|
+
]
|
|
50
|
+
}
|
|
41
51
|
];
|
|
42
52
|
var HOT_CUMULATIVE = 0.5;
|
|
43
53
|
var WARM_CUMULATIVE = 0.8;
|
|
@@ -55,7 +65,7 @@ function normalizePath(p) {
|
|
|
55
65
|
return forwardSlash.startsWith("./") ? forwardSlash.slice(2) : forwardSlash;
|
|
56
66
|
}
|
|
57
67
|
function runScc(excludes = []) {
|
|
58
|
-
const patterns =
|
|
68
|
+
const patterns = excludes.map(globToRegex);
|
|
59
69
|
let raw;
|
|
60
70
|
try {
|
|
61
71
|
raw = execSync("scc --by-file --format json --no-cocomo --no-gen", {
|
|
@@ -154,7 +164,7 @@ function getAuthors(months) {
|
|
|
154
164
|
}
|
|
155
165
|
var MAX_FILES_PER_COMMIT = 20;
|
|
156
166
|
function getCoChanges(months, excludes = []) {
|
|
157
|
-
const patterns =
|
|
167
|
+
const patterns = excludes.map(globToRegex);
|
|
158
168
|
let raw;
|
|
159
169
|
try {
|
|
160
170
|
raw = execSync(
|
|
@@ -465,7 +475,7 @@ function detectIgnorePatterns() {
|
|
|
465
475
|
}
|
|
466
476
|
return patterns;
|
|
467
477
|
}
|
|
468
|
-
function formatIgnoreFile(
|
|
478
|
+
function formatIgnoreFile(detectedPatterns, universalGroups = UNIVERSAL_IGNORE_GROUPS) {
|
|
469
479
|
const lines = [
|
|
470
480
|
"# Generated by obscene init",
|
|
471
481
|
"# Edit this file to customize which files are excluded from analysis.",
|
|
@@ -473,16 +483,20 @@ function formatIgnoreFile(patterns) {
|
|
|
473
483
|
"# See: https://github.com/wbern/obscene#ignore-files",
|
|
474
484
|
""
|
|
475
485
|
];
|
|
476
|
-
|
|
477
|
-
lines.push(
|
|
478
|
-
|
|
486
|
+
for (const group of universalGroups) {
|
|
487
|
+
lines.push(`# ${group.title}`);
|
|
488
|
+
for (const p of group.patterns) {
|
|
489
|
+
lines.push(p.pattern);
|
|
490
|
+
}
|
|
479
491
|
lines.push("");
|
|
480
|
-
}
|
|
481
|
-
|
|
492
|
+
}
|
|
493
|
+
if (detectedPatterns.length > 0) {
|
|
494
|
+
lines.push("# Project-specific patterns");
|
|
495
|
+
for (const p of detectedPatterns) {
|
|
482
496
|
lines.push(`# ${p.comment}`);
|
|
483
497
|
lines.push(p.pattern);
|
|
484
|
-
lines.push("");
|
|
485
498
|
}
|
|
499
|
+
lines.push("");
|
|
486
500
|
}
|
|
487
501
|
return lines.join("\n");
|
|
488
502
|
}
|
|
@@ -857,7 +871,7 @@ function formatCompositeTable(output) {
|
|
|
857
871
|
|
|
858
872
|
// src/cli.ts
|
|
859
873
|
var program = new Command();
|
|
860
|
-
program.name("obscene").description("Identify hotspot files \u2014 complex code that changes frequently").version("
|
|
874
|
+
program.name("obscene").description("Identify hotspot files \u2014 complex code that changes frequently").version("2.0.0");
|
|
861
875
|
var REPORT_GUIDE = {
|
|
862
876
|
complexity: "Cyclomatic complexity (branch/loop count). NOT a quality judgment \u2014 a 500-line parser will naturally score high. Compare density, not raw values.",
|
|
863
877
|
complexityDensity: "Complexity per line of code. Normalizes for file size. >0.25 suggests dense logic worth reviewing; <0.10 is typical for straightforward code.",
|
|
@@ -923,7 +937,15 @@ program.command("init").description("generate a starter .obsignore based on proj
|
|
|
923
937
|
function resolveExcludes(cliExcludes) {
|
|
924
938
|
return [...readIgnoreFile(), ...cliExcludes ?? []];
|
|
925
939
|
}
|
|
940
|
+
function warnIfNoIgnoreFile() {
|
|
941
|
+
if (!existsSync(".obsignore") && !existsSync(".obsceneignore")) {
|
|
942
|
+
process.stderr.write(
|
|
943
|
+
"hint: no .obsignore found \u2014 run `obscene init` to generate one with recommended exclusions\n"
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
926
947
|
function runReport(opts) {
|
|
948
|
+
warnIfNoIgnoreFile();
|
|
927
949
|
const top = parseInt(opts.top, 10);
|
|
928
950
|
const allExcludes = resolveExcludes(opts.exclude);
|
|
929
951
|
const files = runScc(allExcludes);
|
|
@@ -956,6 +978,7 @@ function runReport(opts) {
|
|
|
956
978
|
}
|
|
957
979
|
}
|
|
958
980
|
function runHotspots(opts) {
|
|
981
|
+
warnIfNoIgnoreFile();
|
|
959
982
|
const top = parseInt(opts.top, 10);
|
|
960
983
|
const months = parseInt(opts.months, 10);
|
|
961
984
|
const allExcludes = resolveExcludes(opts.exclude);
|
|
@@ -995,6 +1018,7 @@ ${formatCompositeTable(composite)}
|
|
|
995
1018
|
}
|
|
996
1019
|
}
|
|
997
1020
|
function runCoupling(opts) {
|
|
1021
|
+
warnIfNoIgnoreFile();
|
|
998
1022
|
const top = parseInt(opts.top, 10);
|
|
999
1023
|
const months = parseInt(opts.months, 10);
|
|
1000
1024
|
const minCochanges = parseInt(opts.minCochanges, 10);
|
|
@@ -1048,22 +1072,25 @@ function runInit() {
|
|
|
1048
1072
|
".obsceneignore already exists. Remove it first to regenerate."
|
|
1049
1073
|
);
|
|
1050
1074
|
}
|
|
1051
|
-
const
|
|
1052
|
-
const content = formatIgnoreFile(
|
|
1075
|
+
const detected = detectIgnorePatterns();
|
|
1076
|
+
const content = formatIgnoreFile(detected);
|
|
1053
1077
|
writeFileSync(".obsignore", content);
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1078
|
+
const universalCount = UNIVERSAL_IGNORE_GROUPS.reduce(
|
|
1079
|
+
(sum, g) => sum + g.patterns.length,
|
|
1080
|
+
0
|
|
1081
|
+
);
|
|
1082
|
+
process.stderr.write(
|
|
1083
|
+
`Created .obsignore with ${universalCount} universal exclusions`
|
|
1084
|
+
);
|
|
1085
|
+
if (detected.length > 0) {
|
|
1086
|
+
process.stderr.write(` + ${detected.length} detected patterns:
|
|
1087
|
+
`);
|
|
1088
|
+
for (const p of detected) {
|
|
1064
1089
|
process.stderr.write(` ${p.pattern.padEnd(20)} ${p.comment}
|
|
1065
1090
|
`);
|
|
1066
1091
|
}
|
|
1092
|
+
} else {
|
|
1093
|
+
process.stderr.write(" (no project-specific patterns detected)\n");
|
|
1067
1094
|
}
|
|
1068
1095
|
}
|
|
1069
1096
|
function exitWithError(err) {
|