code-gauge 6.0.0 → 8.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 +113 -74
- package/dist/checkCommand.cjs +2 -2
- package/dist/checkCommand.cjs.map +1 -1
- package/dist/checkCommand.d.ts +4 -4
- package/dist/checkCommand.js +2 -2
- package/dist/checkCommand.js.map +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/cliConfig.cjs +1 -1
- package/dist/cliConfig.cjs.map +1 -1
- package/dist/cliConfig.js +1 -1
- package/dist/cliConfig.js.map +1 -1
- package/dist/nativeMetrics.cjs +1 -1
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.js +1 -1
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/thresholdCheck.cjs +1 -1
- package/dist/thresholdCheck.cjs.map +1 -1
- package/dist/thresholdCheck.d.ts +8 -4
- package/dist/thresholdCheck.js +1 -1
- package/dist/thresholdCheck.js.map +1 -1
- package/dist/thresholds.cjs +1 -1
- package/dist/thresholds.cjs.map +1 -1
- package/dist/thresholds.d.ts +20 -9
- package/dist/thresholds.js +1 -1
- package/dist/thresholds.js.map +1 -1
- package/package.json +8 -11
- package/scripts/installNative.mjs +0 -96
package/dist/thresholds.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thresholds.js","names":[],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/**\n * One
|
|
1
|
+
{"version":3,"file":"thresholds.js","names":[],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** The severities of a violation, mildest first. */\nexport const levels = ['warning', 'error'] as const;\nexport type Level = (typeof levels)[number];\n\n/**\n * One threshold of `code-gauge check`. Its `key` is the config setting, the per-language override\n * setting, and (kebab-cased after a level prefix) the command-line options, so a new entry here is\n * all a new thresholded metric needs.\n */\nexport interface Threshold<Subject = never> {\n key: string;\n /** How the report names the measured value. */\n label: string;\n /** `Infinity` leaves the level unchecked unless the config or the command line sets a limit. */\n defaultLimits: Readonly<Record<Level, number>>;\n /** What to change in the code when the limit is exceeded. */\n hint: string;\n measure: (subject: Subject) => number;\n /**\n * The value of a function is at least that of each function nested in it, whose code it counts\n * too. Not so for Halstead difficulty and effort: a ratio can be higher for the nested part alone.\n */\n includesNestedFunctions?: true;\n}\n\n/** Limits by threshold key; `Infinity` disables a limit. */\nexport type Limits = Readonly<Record<string, number>>;\nexport type LimitsByLevel = Readonly<Record<Level, Limits>>;\n\nexport const functionThresholds: readonly Threshold<FunctionMetrics>[] = [\n {\n key: 'maxFunctionCognitiveComplexity',\n label: 'cognitive complexity',\n defaultLimits: { warning: 15, error: 30 },\n hint: 'flatten nested branching with early returns and extract nested blocks into named functions.',\n measure: (fn) => fn.cognitiveComplexity,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionCyclomaticComplexity',\n label: 'cyclomatic complexity',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function has too many independent paths; split it by decision or replace condition chains with a lookup table.',\n measure: (fn) => fn.cyclomaticComplexity,\n },\n {\n key: 'maxFunctionNcss',\n label: 'NCSS',\n defaultLimits: { warning: 60, error: 100 },\n hint: 'split the function into smaller functions that each do one step.',\n measure: (fn) => fn.ncss,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionNestingDepth',\n label: 'nesting depth',\n defaultLimits: { warning: 4, error: 5 },\n hint: 'replace nested conditions with guard clauses or move inner blocks into functions.',\n measure: (fn) => fn.nestingDepth,\n },\n {\n key: 'maxFunctionParameterCount',\n label: 'parameters',\n defaultLimits: { warning: 7, error: Infinity },\n hint: 'group related parameters into one object or split the function.',\n measure: (fn) => fn.parameterCount,\n },\n {\n key: 'maxFunctionHalsteadVolume',\n label: 'Halstead volume',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function holds too many operators and operands; move cohesive parts into helper functions.',\n measure: (fn) => fn.halstead.volume,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionHalsteadDifficulty',\n label: 'Halstead difficulty',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function reuses the same operands many times; split it so each part handles fewer values.',\n measure: (fn) => fn.halstead.difficulty,\n },\n {\n key: 'maxFunctionHalsteadEffort',\n label: 'Halstead effort',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function is both large and dense; split it into smaller functions with fewer values each.',\n measure: (fn) => fn.halstead.effort,\n },\n {\n key: 'maxFunctionDepDegree',\n label: 'DepDegree',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'too many values flow between the variables of the function; split it so each part works on fewer variables.',\n measure: (fn) => fn.depDegree,\n includesNestedFunctions: true,\n },\n];\n\nexport const fileThresholds: readonly Threshold<CodeMetrics>[] = [\n {\n key: 'maxFileNcss',\n label: 'file NCSS',\n defaultLimits: { warning: 500, error: 1000 },\n hint: 'split the file into smaller modules with one responsibility each.',\n measure: (file) => file.ncssCount,\n },\n];\n\n/** A duplicated block violates when it spans at least this many lines. */\nexport const duplicationThreshold: Threshold<{ startLine: number; endLine: number }> = {\n key: 'minDuplicateLines',\n label: 'duplicated lines',\n defaultLimits: { warning: 10, error: 20 },\n hint: 'extract the repeated code into one shared function or module and call it from every location.',\n measure: (block) => block.endLine - block.startLine + 1,\n};\n\nexport const thresholds: readonly Threshold[] = [...functionThresholds, ...fileThresholds, duplicationThreshold];\n\n/** The name a report gives a threshold's measured value: its key without the `max`/`min` prefix. */\nexport function metricNameOf(threshold: Threshold): string {\n const name = threshold.key.slice(3);\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** One level of the `thresholds` config section; a missing key falls back to the less specific layer. */\nexport interface LevelThresholdsConfig {\n limits: Limits;\n /** Overrides for the files of one language, keyed by language name. */\n languages: Readonly<Record<string, Limits>>;\n}\n\nexport type ThresholdsConfig = Readonly<Partial<Record<Level, LevelThresholdsConfig>>>;\n\n/**\n * The limits of each level applying to files of each language, with precedence command-line\n * options > the config's per-language overrides > the config's limits > built-in defaults.\n */\nexport function resolveLimits(\n cliLimits: LimitsByLevel,\n config: ThresholdsConfig | undefined\n): (language: string) => LimitsByLevel {\n const resolveLevel = (level: Level, language: string): Limits => {\n const levelConfig = config?.[level];\n return {\n ...Object.fromEntries(thresholds.map(({ key, defaultLimits }) => [key, defaultLimits[level]])),\n ...levelConfig?.limits,\n // Object.hasOwn: a language named like an Object.prototype member must not read an inherited value.\n ...(levelConfig && Object.hasOwn(levelConfig.languages, language) ? levelConfig.languages[language] : undefined),\n ...cliLimits[level],\n };\n };\n return (language) => ({ warning: resolveLevel('warning', language), error: resolveLevel('error', language) });\n}\n"],"mappings":"AAGA,MAAa,EAAS,CAAC,UAAW,OAAO,EA4B5B,EAA4D,CACvE,CACE,IAAK,iCACL,MAAO,uBACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,8FACN,QAAU,GAAO,EAAG,oBACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,kCACL,MAAO,wBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,qHACN,QAAU,GAAO,EAAG,oBACtB,EACA,CACE,IAAK,kBACL,MAAO,OACP,cAAe,CAAE,QAAS,GAAI,MAAO,GAAI,EACzC,KAAM,mEACN,QAAU,GAAO,EAAG,KACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,0BACL,MAAO,gBACP,cAAe,CAAE,QAAS,EAAG,MAAO,CAAE,EACtC,KAAM,oFACN,QAAU,GAAO,EAAG,YACtB,EACA,CACE,IAAK,4BACL,MAAO,aACP,cAAe,CAAE,QAAS,EAAG,MAAO,GAAS,EAC7C,KAAM,kEACN,QAAU,GAAO,EAAG,cACtB,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,iGACN,QAAU,GAAO,EAAG,SAAS,OAC7B,wBAAyB,EAC3B,EACA,CACE,IAAK,gCACL,MAAO,sBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAO,EAAG,SAAS,UAC/B,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAO,EAAG,SAAS,MAC/B,EACA,CACE,IAAK,uBACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,8GACN,QAAU,GAAO,EAAG,UACpB,wBAAyB,EAC3B,CACF,EAEa,EAAoD,CAC/D,CACE,IAAK,cACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAK,MAAO,GAAK,EAC3C,KAAM,oEACN,QAAU,GAAS,EAAK,SAC1B,CACF,EAGa,EAA0E,CACrF,IAAK,oBACL,MAAO,mBACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,gGACN,QAAU,GAAU,EAAM,QAAU,EAAM,UAAY,CACxD,EAEa,EAAmC,CAAC,GAAG,EAAoB,GAAG,EAAgB,CAAoB,EAG/G,SAAgB,EAAa,EAA8B,CACzD,IAAM,EAAO,EAAU,IAAI,MAAM,CAAC,EAClC,OAAO,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,EAAI,EAAK,MAAM,CAAC,CACpD,CAeA,SAAgB,EACd,EACA,EACqC,CACrC,IAAM,GAAgB,EAAc,IAA6B,CAC/D,IAAM,EAAc,IAAS,GAC7B,MAAO,CACL,GAAG,OAAO,YAAY,EAAW,KAAK,CAAE,MAAK,mBAAoB,CAAC,EAAK,EAAc,EAAM,CAAC,CAAC,EAC7F,GAAG,GAAa,OAEhB,GAAI,GAAe,OAAO,OAAO,EAAY,UAAW,CAAQ,EAAI,EAAY,UAAU,GAAY,IAAA,GACtG,GAAG,EAAU,EACf,CACF,EACA,MAAQ,KAAc,CAAE,QAAS,EAAa,UAAW,CAAQ,EAAG,MAAO,EAAa,QAAS,CAAQ,CAAE,EAC7G"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-gauge",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Measure code metrics with tree-sitter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
"native/code-gauge.wasm",
|
|
42
42
|
"native/src/",
|
|
43
43
|
"scripts/buildNative.mjs",
|
|
44
|
-
"scripts/installNative.mjs",
|
|
45
44
|
"THIRD-PARTY-NOTICES.txt"
|
|
46
45
|
],
|
|
47
46
|
"scripts": {
|
|
@@ -51,10 +50,8 @@
|
|
|
51
50
|
"build-wasm": "node scripts/buildWasm.mjs",
|
|
52
51
|
"cleanup": "bun wb lint --fix --format",
|
|
53
52
|
"format": "bun wb lint --format",
|
|
54
|
-
"postinstall": "node scripts/installNative.mjs",
|
|
55
53
|
"lint": "bun wb lint",
|
|
56
54
|
"lint-fix": "bun wb lint --fix",
|
|
57
|
-
"prepare": "(bun lefthook install || true) && (test ! -f .lefthook/normalize-bun-lockfile.sh || bash .lefthook/normalize-bun-lockfile.sh)",
|
|
58
55
|
"test": "bun wb test",
|
|
59
56
|
"test/ci": "bun wb test-on-ci",
|
|
60
57
|
"typecheck": "bun wb typecheck",
|
|
@@ -95,12 +92,12 @@
|
|
|
95
92
|
"registry": "https://registry.npmjs.org/"
|
|
96
93
|
},
|
|
97
94
|
"optionalDependencies": {
|
|
98
|
-
"code-gauge-linux-x64-gnu": "
|
|
99
|
-
"code-gauge-linux-arm64-gnu": "
|
|
100
|
-
"code-gauge-linux-x64-musl": "
|
|
101
|
-
"code-gauge-linux-arm64-musl": "
|
|
102
|
-
"code-gauge-darwin-x64": "
|
|
103
|
-
"code-gauge-darwin-arm64": "
|
|
104
|
-
"code-gauge-win32-x64-msvc": "
|
|
95
|
+
"code-gauge-linux-x64-gnu": "8.0.0",
|
|
96
|
+
"code-gauge-linux-arm64-gnu": "8.0.0",
|
|
97
|
+
"code-gauge-linux-x64-musl": "8.0.0",
|
|
98
|
+
"code-gauge-linux-arm64-musl": "8.0.0",
|
|
99
|
+
"code-gauge-darwin-x64": "8.0.0",
|
|
100
|
+
"code-gauge-darwin-arm64": "8.0.0",
|
|
101
|
+
"code-gauge-win32-x64-msvc": "8.0.0"
|
|
105
102
|
}
|
|
106
103
|
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Postinstall hook ensuring the native Rust addon is available: a prebuilt platform package or an
|
|
3
|
-
// already-built native/code-gauge.node is kept when it serves the payload version this checkout
|
|
4
|
-
// expects (a stale addon surviving a `git pull` must not be kept — the runtime loader would
|
|
5
|
-
// reject it), otherwise the addon is built from the bundled sources when a Rust toolchain is
|
|
6
|
-
// available. Never fails the install — the whole body is guarded and the process always exits 0
|
|
7
|
-
// (npm runs lifecycle scripts through cmd.exe on Windows, where a `|| true` suffix would not
|
|
8
|
-
// work) — and the runtime loader raises a descriptive error on first use if no addon could be
|
|
9
|
-
// provided.
|
|
10
|
-
|
|
11
|
-
import { execFileSync } from 'node:child_process';
|
|
12
|
-
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
13
|
-
import path from 'node:path';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
15
|
-
|
|
16
|
-
const packageRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
|
-
|
|
18
|
-
// No explicit process.exit: nothing here keeps the event loop alive, so the process ends with
|
|
19
|
-
// code 0 on its own once the synchronous work returns — and exit() could truncate the warning's
|
|
20
|
-
// asynchronous stderr write (stderr is a pipe under npm, which is asynchronous on Windows).
|
|
21
|
-
try {
|
|
22
|
-
installNativeAddon();
|
|
23
|
-
} catch (error) {
|
|
24
|
-
warnBuildFailure(error);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function installNativeAddon() {
|
|
28
|
-
// The authoritative payload version lives in the bundled Rust source; a parse failure yields
|
|
29
|
-
// undefined, which no addon reports, so validation then always falls through to a fresh build.
|
|
30
|
-
const expectedPayloadVersion = /pub fn payload_version\(\) -> u32 \{\s*(\d+)/.exec(
|
|
31
|
-
readFileSync(path.join(packageRoot, 'native', 'src', 'lib.rs'), 'utf8')
|
|
32
|
-
)?.[1];
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
servesExpectedPayload(`code-gauge-${platformTriplet()}`, expectedPayloadVersion) ||
|
|
36
|
-
servesExpectedPayload(path.join(packageRoot, 'native', 'code-gauge.node'), expectedPayloadVersion)
|
|
37
|
-
) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
execFileSync(process.execPath, [path.join(packageRoot, 'scripts', 'buildNative.mjs')], { stdio: 'inherit' });
|
|
43
|
-
} catch (error) {
|
|
44
|
-
warnBuildFailure(error);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
// In an installed package the cargo build tree (hundreds of MB) buys nothing once the addon is
|
|
48
|
-
// copied out; a repository checkout (recognized by its sources) keeps it as the build cache.
|
|
49
|
-
// Guarded separately: a cleanup failure (e.g. a held Windows file handle) leaves a usable addon
|
|
50
|
-
// behind and must not be reported as a build failure.
|
|
51
|
-
if (!existsSync(path.join(packageRoot, 'src'))) {
|
|
52
|
-
try {
|
|
53
|
-
rmSync(path.join(packageRoot, 'native', 'target'), { recursive: true, force: true });
|
|
54
|
-
} catch {
|
|
55
|
-
// The leftover build tree only costs disk space.
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// The platform-package suffix in the napi-rs naming convention: Linux is qualified by libc ABI
|
|
61
|
-
// (a glibc-linked addon cannot load on Alpine/musl) and Windows by toolchain ABI. Must match
|
|
62
|
-
// platformTriplet in src/nativeMetrics.ts.
|
|
63
|
-
function platformTriplet() {
|
|
64
|
-
const base = `${process.platform}-${process.arch}`;
|
|
65
|
-
if (process.platform === 'win32') {
|
|
66
|
-
return `${base}-msvc`;
|
|
67
|
-
}
|
|
68
|
-
if (process.platform !== 'linux') {
|
|
69
|
-
return base;
|
|
70
|
-
}
|
|
71
|
-
return process.report?.getReport()?.header?.glibcVersionRuntime ? `${base}-gnu` : `${base}-musl`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Probed in a child process: loading the addon here would keep its library mapped for this
|
|
75
|
-
// process's lifetime, and overwriting a mapped DLL is refused on Windows — exactly what the
|
|
76
|
-
// stale-addon rebuild below must be able to do.
|
|
77
|
-
function servesExpectedPayload(specifier, expectedPayloadVersion) {
|
|
78
|
-
try {
|
|
79
|
-
const reported = execFileSync(
|
|
80
|
-
process.execPath,
|
|
81
|
-
['-p', `String(require(${JSON.stringify(specifier)}).payloadVersion?.())`],
|
|
82
|
-
// stderr is discarded: a missing platform package is the normal case, not a diagnostic.
|
|
83
|
-
{ cwd: packageRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
84
|
-
).trim();
|
|
85
|
-
return expectedPayloadVersion !== undefined && reported === expectedPayloadVersion;
|
|
86
|
-
} catch {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function warnBuildFailure(error) {
|
|
92
|
-
console.warn(
|
|
93
|
-
`code-gauge: could not build the native addon (${error instanceof Error ? error.message : String(error)}). ` +
|
|
94
|
-
'Install a Rust toolchain and run `node scripts/buildNative.mjs` in the package directory to enable it.'
|
|
95
|
-
);
|
|
96
|
-
}
|