code-gauge 1.14.0 → 3.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.
Files changed (54) hide show
  1. package/README.md +97 -108
  2. package/dist/cli.cjs +3 -3
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/cli.js +3 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/cliConfig.cjs +1 -1
  7. package/dist/cliConfig.cjs.map +1 -1
  8. package/dist/cliConfig.d.ts +16 -84
  9. package/dist/cliConfig.js +1 -1
  10. package/dist/cliConfig.js.map +1 -1
  11. package/dist/crossFileDuplication.cjs +1 -1
  12. package/dist/crossFileDuplication.cjs.map +1 -1
  13. package/dist/crossFileDuplication.d.ts +24 -10
  14. package/dist/crossFileDuplication.js +1 -1
  15. package/dist/crossFileDuplication.js.map +1 -1
  16. package/dist/duplication.cjs +1 -1
  17. package/dist/duplication.cjs.map +1 -1
  18. package/dist/duplication.d.ts +153 -4
  19. package/dist/duplication.js +1 -1
  20. package/dist/duplication.js.map +1 -1
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.js +1 -1
  24. package/dist/languages.cjs +1 -1
  25. package/dist/languages.cjs.map +1 -1
  26. package/dist/languages.js +1 -1
  27. package/dist/languages.js.map +1 -1
  28. package/dist/metrics.cjs +1 -2
  29. package/dist/metrics.cjs.map +1 -1
  30. package/dist/metrics.d.ts +9 -4
  31. package/dist/metrics.js +1 -2
  32. package/dist/metrics.js.map +1 -1
  33. package/dist/nativeMetrics.cjs +1 -1
  34. package/dist/nativeMetrics.cjs.map +1 -1
  35. package/dist/nativeMetrics.d.ts +2 -2
  36. package/dist/nativeMetrics.js +1 -1
  37. package/dist/nativeMetrics.js.map +1 -1
  38. package/dist/ncss.cjs +1 -1
  39. package/dist/ncss.cjs.map +1 -1
  40. package/dist/ncss.d.ts +11 -5
  41. package/dist/ncss.js +1 -1
  42. package/dist/ncss.js.map +1 -1
  43. package/dist/types.d.ts +14 -79
  44. package/package.json +7 -5
  45. package/dist/architectureMetrics.cjs +0 -2
  46. package/dist/architectureMetrics.cjs.map +0 -1
  47. package/dist/architectureMetrics.d.ts +0 -41
  48. package/dist/architectureMetrics.js +0 -2
  49. package/dist/architectureMetrics.js.map +0 -1
  50. package/dist/typescriptProject.cjs +0 -3
  51. package/dist/typescriptProject.cjs.map +0 -1
  52. package/dist/typescriptProject.d.ts +0 -24
  53. package/dist/typescriptProject.js +0 -3
  54. package/dist/typescriptProject.js.map +0 -1
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./languages.cjs");let t=require("node:module");const n=new Map(e.defaultLanguages.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{i=(0,t.createRequire)(require("url").pathToFileURL(__filename).href)(`../native/code-gauge.node`)}catch{}return i}exports.isNativeBackendAvailable=o,exports.measureWithNativeBackend=a;
1
+ "use strict";const e=require("./languages.cjs");let t=require("node:module");const n=new Map(e.defaultLanguages.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{let e=(0,t.createRequire)(require("url").pathToFileURL(__filename).href)(`../native/code-gauge.node`);e.payloadVersion?.()===2&&(i=e)}catch{}return i}exports.isNativeBackendAvailable=o,exports.measureWithNativeBackend=a;
2
2
  //# sourceMappingURL=nativeMetrics.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"nativeMetrics.cjs","names":["defaultLanguages","createRequire"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, difficulty, ...) are\n * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and\n * the native backend must be bit-identical to the TypeScript one.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'maintainabilityIndex' | 'syntaxTree'> {\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\ninterface NativeBinding {\n measureCodeNative(code: string, language: string, includeSyntaxTree: boolean): string;\n}\n\nconst defaultLanguageByName = new Map(defaultLanguages.map((language) => [language.name, language]));\n\nlet bindingLoadAttempted = false;\nlet cachedBinding: NativeBinding | undefined;\n\n/**\n * Measures via the Rust addon when it is built and applicable, or returns undefined so the caller\n * falls back to the TypeScript implementation. Custom-registered languages always fall back: the\n * addon only embeds the built-in grammars.\n */\nexport function measureWithNativeBackend(\n code: string,\n language: LanguageDefinition,\n includeSyntaxTree: boolean\n): NativeMetricsPayload | undefined {\n if (!isNativeBackendEnabled() || defaultLanguageByName.get(language.name) !== language) {\n return undefined;\n }\n\n // Lone surrogates cannot cross the N-API boundary losslessly (they become U+FFFD), so\n // ill-formed strings measure through the TypeScript backend, which sees them as-is.\n if (!code.isWellFormed()) {\n return undefined;\n }\n\n const binding = loadBinding();\n if (!binding) {\n return undefined;\n }\n\n try {\n return JSON.parse(binding.measureCodeNative(code, language.name, includeSyntaxTree)) as NativeMetricsPayload;\n } catch (error) {\n // Parity tests set the strict flag: without it, a binding that starts throwing would silently\n // degrade the \"native\" side of every comparison into a TypeScript-vs-TypeScript check.\n if (process.env.CODE_GAUGE_NATIVE_STRICT === '1') {\n throw error;\n }\n // A native failure (e.g. the tree-depth guard on pathological input) falls back to the\n // TypeScript backend instead of turning measureCode into a throwing API.\n return undefined;\n }\n}\n\n/** Whether measureCode currently uses the native backend for built-in languages. */\nexport function isNativeBackendAvailable(): boolean {\n return isNativeBackendEnabled() && loadBinding() !== undefined;\n}\n\n/** Checked per call (not cached) so tests can flip backends within one process. */\nfunction isNativeBackendEnabled(): boolean {\n return process.env.CODE_GAUGE_NATIVE !== '0';\n}\n\nfunction loadBinding(): NativeBinding | undefined {\n if (bindingLoadAttempted) {\n return cachedBinding;\n }\n bindingLoadAttempted = true;\n\n try {\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find native/.\n const requireNative = createRequire(import.meta.url);\n cachedBinding = requireNative('../native/code-gauge.node') as NativeBinding;\n } catch {\n // The addon has not been built (or this platform/module format cannot load it).\n }\n return cachedBinding;\n}\n"],"mappings":"6EAyBA,MAAM,EAAwB,IAAI,IAAIA,EAAAA,iBAAiB,IAAK,GAAa,CAAC,EAAS,KAAM,CAAQ,CAAC,CAAC,EAEnG,IAAI,EAAuB,GACvB,EAOJ,SAAgB,EACd,EACA,EACA,EACkC,CAOlC,GANI,CAAC,EAAuB,GAAK,EAAsB,IAAI,EAAS,IAAI,IAAM,GAM1E,CAAC,EAAK,aAAa,EACrB,OAGF,IAAM,EAAU,EAAY,EACvB,KAIL,GAAI,CACF,OAAO,KAAK,MAAM,EAAQ,kBAAkB,EAAM,EAAS,KAAM,CAAiB,CAAC,CACrF,OAAS,EAAO,CAGd,GAAI,QAAQ,IAAI,2BAA6B,IAC3C,MAAM,EAIR,MACF,CACF,CAGA,SAAgB,GAAoC,CAClD,OAAO,EAAuB,GAAK,EAAY,IAAM,IAAA,EACvD,CAGA,SAAS,GAAkC,CACzC,OAAO,QAAQ,IAAI,oBAAsB,GAC3C,CAEA,SAAS,GAAyC,CAChD,GAAI,EACF,OAAO,EAET,EAAuB,GAEvB,GAAI,CAGF,GAAA,EADsBC,EAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IACM,CAAC,CAAC,2BAA2B,CAC3D,MAAQ,CAER,CACA,OAAO,CACT"}
1
+ {"version":3,"file":"nativeMetrics.cjs","names":["defaultLanguages","createRequire"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, effort, ...) are\n * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and\n * the native backend must be bit-identical to the TypeScript one.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'syntaxTree'> {\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\ninterface NativeBinding {\n measureCodeNative(code: string, language: string, includeSyntaxTree: boolean): string;\n /** Absent on stale builds that predate payload versioning. */\n payloadVersion?(): number;\n}\n\n/**\n * Must equal `payload_version` in native/src/lib.rs. A previously built addon survives a\n * `git pull` untouched, so without this handshake it would silently return payloads missing\n * newer fields (e.g. duplication.duplicateLineNumbers) instead of falling back to the\n * TypeScript backend.\n */\nconst expectedPayloadVersion = 2;\n\nconst defaultLanguageByName = new Map(defaultLanguages.map((language) => [language.name, language]));\n\nlet bindingLoadAttempted = false;\nlet cachedBinding: NativeBinding | undefined;\n\n/**\n * Measures via the Rust addon when it is built and applicable, or returns undefined so the caller\n * falls back to the TypeScript implementation. Custom-registered languages always fall back: the\n * addon only embeds the built-in grammars.\n */\nexport function measureWithNativeBackend(\n code: string,\n language: LanguageDefinition,\n includeSyntaxTree: boolean\n): NativeMetricsPayload | undefined {\n if (!isNativeBackendEnabled() || defaultLanguageByName.get(language.name) !== language) {\n return undefined;\n }\n\n // Lone surrogates cannot cross the N-API boundary losslessly (they become U+FFFD), so\n // ill-formed strings measure through the TypeScript backend, which sees them as-is.\n if (!code.isWellFormed()) {\n return undefined;\n }\n\n const binding = loadBinding();\n if (!binding) {\n return undefined;\n }\n\n try {\n return JSON.parse(binding.measureCodeNative(code, language.name, includeSyntaxTree)) as NativeMetricsPayload;\n } catch (error) {\n // Parity tests set the strict flag: without it, a binding that starts throwing would silently\n // degrade the \"native\" side of every comparison into a TypeScript-vs-TypeScript check.\n if (process.env.CODE_GAUGE_NATIVE_STRICT === '1') {\n throw error;\n }\n // A native failure (e.g. the tree-depth guard on pathological input) falls back to the\n // TypeScript backend instead of turning measureCode into a throwing API.\n return undefined;\n }\n}\n\n/** Whether measureCode currently uses the native backend for built-in languages. */\nexport function isNativeBackendAvailable(): boolean {\n return isNativeBackendEnabled() && loadBinding() !== undefined;\n}\n\n/** Checked per call (not cached) so tests can flip backends within one process. */\nfunction isNativeBackendEnabled(): boolean {\n return process.env.CODE_GAUGE_NATIVE !== '0';\n}\n\nfunction loadBinding(): NativeBinding | undefined {\n if (bindingLoadAttempted) {\n return cachedBinding;\n }\n bindingLoadAttempted = true;\n\n try {\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find native/.\n const requireNative = createRequire(import.meta.url);\n const binding = requireNative('../native/code-gauge.node') as NativeBinding;\n if (binding.payloadVersion?.() === expectedPayloadVersion) {\n cachedBinding = binding;\n }\n // A version mismatch (or a build too old to report one) leaves the binding unused, like a\n // missing addon: rebuild with `yarn build-native` to re-enable the native backend.\n } catch {\n // The addon has not been built (or this platform/module format cannot load it).\n }\n return cachedBinding;\n}\n"],"mappings":"6EAiCA,MAEM,EAAwB,IAAI,IAAIA,EAAAA,iBAAiB,IAAK,GAAa,CAAC,EAAS,KAAM,CAAQ,CAAC,CAAC,EAEnG,IAAI,EAAuB,GACvB,EAOJ,SAAgB,EACd,EACA,EACA,EACkC,CAOlC,GANI,CAAC,EAAuB,GAAK,EAAsB,IAAI,EAAS,IAAI,IAAM,GAM1E,CAAC,EAAK,aAAa,EACrB,OAGF,IAAM,EAAU,EAAY,EACvB,KAIL,GAAI,CACF,OAAO,KAAK,MAAM,EAAQ,kBAAkB,EAAM,EAAS,KAAM,CAAiB,CAAC,CACrF,OAAS,EAAO,CAGd,GAAI,QAAQ,IAAI,2BAA6B,IAC3C,MAAM,EAIR,MACF,CACF,CAGA,SAAgB,GAAoC,CAClD,OAAO,EAAuB,GAAK,EAAY,IAAM,IAAA,EACvD,CAGA,SAAS,GAAkC,CACzC,OAAO,QAAQ,IAAI,oBAAsB,GAC3C,CAEA,SAAS,GAAyC,CAChD,GAAI,EACF,OAAO,EAET,EAAuB,GAEvB,GAAI,CAGF,IAAM,GAAA,EADgBC,EAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IACM,CAAC,CAAC,2BAA2B,EACrD,EAAQ,iBAAiB,IAAM,IACjC,EAAgB,EAIpB,MAAQ,CAER,CACA,OAAO,CACT"}
@@ -1,6 +1,6 @@
1
1
  import type { CodeMetrics, LanguageDefinition } from './types.js';
2
2
  /**
3
- * Halstead counts measured natively; the derived float metrics (volume, difficulty, ...) are
3
+ * Halstead counts measured natively; the derived float metrics (volume, effort, ...) are
4
4
  * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and
5
5
  * the native backend must be bit-identical to the TypeScript one.
6
6
  */
@@ -10,7 +10,7 @@ export interface NativeHalsteadCounts {
10
10
  totalOperators: number;
11
11
  totalOperands: number;
12
12
  }
13
- export interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'maintainabilityIndex' | 'syntaxTree'> {
13
+ export interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'syntaxTree'> {
14
14
  halsteadCounts: NativeHalsteadCounts;
15
15
  syntaxTree?: string;
16
16
  }
@@ -1,2 +1,2 @@
1
- import{defaultLanguages as e}from"./languages.js";import{createRequire as t}from"node:module";const n=new Map(e.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{i=t(import.meta.url)(`../native/code-gauge.node`)}catch{}return i}export{o as isNativeBackendAvailable,a as measureWithNativeBackend};
1
+ import{defaultLanguages as e}from"./languages.js";import{createRequire as t}from"node:module";const n=new Map(e.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{let e=t(import.meta.url)(`../native/code-gauge.node`);e.payloadVersion?.()===2&&(i=e)}catch{}return i}export{o as isNativeBackendAvailable,a as measureWithNativeBackend};
2
2
  //# sourceMappingURL=nativeMetrics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nativeMetrics.js","names":[],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, difficulty, ...) are\n * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and\n * the native backend must be bit-identical to the TypeScript one.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'maintainabilityIndex' | 'syntaxTree'> {\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\ninterface NativeBinding {\n measureCodeNative(code: string, language: string, includeSyntaxTree: boolean): string;\n}\n\nconst defaultLanguageByName = new Map(defaultLanguages.map((language) => [language.name, language]));\n\nlet bindingLoadAttempted = false;\nlet cachedBinding: NativeBinding | undefined;\n\n/**\n * Measures via the Rust addon when it is built and applicable, or returns undefined so the caller\n * falls back to the TypeScript implementation. Custom-registered languages always fall back: the\n * addon only embeds the built-in grammars.\n */\nexport function measureWithNativeBackend(\n code: string,\n language: LanguageDefinition,\n includeSyntaxTree: boolean\n): NativeMetricsPayload | undefined {\n if (!isNativeBackendEnabled() || defaultLanguageByName.get(language.name) !== language) {\n return undefined;\n }\n\n // Lone surrogates cannot cross the N-API boundary losslessly (they become U+FFFD), so\n // ill-formed strings measure through the TypeScript backend, which sees them as-is.\n if (!code.isWellFormed()) {\n return undefined;\n }\n\n const binding = loadBinding();\n if (!binding) {\n return undefined;\n }\n\n try {\n return JSON.parse(binding.measureCodeNative(code, language.name, includeSyntaxTree)) as NativeMetricsPayload;\n } catch (error) {\n // Parity tests set the strict flag: without it, a binding that starts throwing would silently\n // degrade the \"native\" side of every comparison into a TypeScript-vs-TypeScript check.\n if (process.env.CODE_GAUGE_NATIVE_STRICT === '1') {\n throw error;\n }\n // A native failure (e.g. the tree-depth guard on pathological input) falls back to the\n // TypeScript backend instead of turning measureCode into a throwing API.\n return undefined;\n }\n}\n\n/** Whether measureCode currently uses the native backend for built-in languages. */\nexport function isNativeBackendAvailable(): boolean {\n return isNativeBackendEnabled() && loadBinding() !== undefined;\n}\n\n/** Checked per call (not cached) so tests can flip backends within one process. */\nfunction isNativeBackendEnabled(): boolean {\n return process.env.CODE_GAUGE_NATIVE !== '0';\n}\n\nfunction loadBinding(): NativeBinding | undefined {\n if (bindingLoadAttempted) {\n return cachedBinding;\n }\n bindingLoadAttempted = true;\n\n try {\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find native/.\n const requireNative = createRequire(import.meta.url);\n cachedBinding = requireNative('../native/code-gauge.node') as NativeBinding;\n } catch {\n // The addon has not been built (or this platform/module format cannot load it).\n }\n return cachedBinding;\n}\n"],"mappings":"8FAyBA,MAAM,EAAwB,IAAI,IAAI,EAAiB,IAAK,GAAa,CAAC,EAAS,KAAM,CAAQ,CAAC,CAAC,EAEnG,IAAI,EAAuB,GACvB,EAOJ,SAAgB,EACd,EACA,EACA,EACkC,CAOlC,GANI,CAAC,EAAuB,GAAK,EAAsB,IAAI,EAAS,IAAI,IAAM,GAM1E,CAAC,EAAK,aAAa,EACrB,OAGF,IAAM,EAAU,EAAY,EACvB,KAIL,GAAI,CACF,OAAO,KAAK,MAAM,EAAQ,kBAAkB,EAAM,EAAS,KAAM,CAAiB,CAAC,CACrF,OAAS,EAAO,CAGd,GAAI,QAAQ,IAAI,2BAA6B,IAC3C,MAAM,EAIR,MACF,CACF,CAGA,SAAgB,GAAoC,CAClD,OAAO,EAAuB,GAAK,EAAY,IAAM,IAAA,EACvD,CAGA,SAAS,GAAkC,CACzC,OAAO,QAAQ,IAAI,oBAAsB,GAC3C,CAEA,SAAS,GAAyC,CAChD,GAAI,EACF,OAAO,EAET,EAAuB,GAEvB,GAAI,CAGF,EADsB,EAAc,YAAY,GACpB,CAAC,CAAC,2BAA2B,CAC3D,MAAQ,CAER,CACA,OAAO,CACT"}
1
+ {"version":3,"file":"nativeMetrics.js","names":[],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, effort, ...) are\n * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and\n * the native backend must be bit-identical to the TypeScript one.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'syntaxTree'> {\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\ninterface NativeBinding {\n measureCodeNative(code: string, language: string, includeSyntaxTree: boolean): string;\n /** Absent on stale builds that predate payload versioning. */\n payloadVersion?(): number;\n}\n\n/**\n * Must equal `payload_version` in native/src/lib.rs. A previously built addon survives a\n * `git pull` untouched, so without this handshake it would silently return payloads missing\n * newer fields (e.g. duplication.duplicateLineNumbers) instead of falling back to the\n * TypeScript backend.\n */\nconst expectedPayloadVersion = 2;\n\nconst defaultLanguageByName = new Map(defaultLanguages.map((language) => [language.name, language]));\n\nlet bindingLoadAttempted = false;\nlet cachedBinding: NativeBinding | undefined;\n\n/**\n * Measures via the Rust addon when it is built and applicable, or returns undefined so the caller\n * falls back to the TypeScript implementation. Custom-registered languages always fall back: the\n * addon only embeds the built-in grammars.\n */\nexport function measureWithNativeBackend(\n code: string,\n language: LanguageDefinition,\n includeSyntaxTree: boolean\n): NativeMetricsPayload | undefined {\n if (!isNativeBackendEnabled() || defaultLanguageByName.get(language.name) !== language) {\n return undefined;\n }\n\n // Lone surrogates cannot cross the N-API boundary losslessly (they become U+FFFD), so\n // ill-formed strings measure through the TypeScript backend, which sees them as-is.\n if (!code.isWellFormed()) {\n return undefined;\n }\n\n const binding = loadBinding();\n if (!binding) {\n return undefined;\n }\n\n try {\n return JSON.parse(binding.measureCodeNative(code, language.name, includeSyntaxTree)) as NativeMetricsPayload;\n } catch (error) {\n // Parity tests set the strict flag: without it, a binding that starts throwing would silently\n // degrade the \"native\" side of every comparison into a TypeScript-vs-TypeScript check.\n if (process.env.CODE_GAUGE_NATIVE_STRICT === '1') {\n throw error;\n }\n // A native failure (e.g. the tree-depth guard on pathological input) falls back to the\n // TypeScript backend instead of turning measureCode into a throwing API.\n return undefined;\n }\n}\n\n/** Whether measureCode currently uses the native backend for built-in languages. */\nexport function isNativeBackendAvailable(): boolean {\n return isNativeBackendEnabled() && loadBinding() !== undefined;\n}\n\n/** Checked per call (not cached) so tests can flip backends within one process. */\nfunction isNativeBackendEnabled(): boolean {\n return process.env.CODE_GAUGE_NATIVE !== '0';\n}\n\nfunction loadBinding(): NativeBinding | undefined {\n if (bindingLoadAttempted) {\n return cachedBinding;\n }\n bindingLoadAttempted = true;\n\n try {\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find native/.\n const requireNative = createRequire(import.meta.url);\n const binding = requireNative('../native/code-gauge.node') as NativeBinding;\n if (binding.payloadVersion?.() === expectedPayloadVersion) {\n cachedBinding = binding;\n }\n // A version mismatch (or a build too old to report one) leaves the binding unused, like a\n // missing addon: rebuild with `yarn build-native` to re-enable the native backend.\n } catch {\n // The addon has not been built (or this platform/module format cannot load it).\n }\n return cachedBinding;\n}\n"],"mappings":"8FAiCA,MAEM,EAAwB,IAAI,IAAI,EAAiB,IAAK,GAAa,CAAC,EAAS,KAAM,CAAQ,CAAC,CAAC,EAEnG,IAAI,EAAuB,GACvB,EAOJ,SAAgB,EACd,EACA,EACA,EACkC,CAOlC,GANI,CAAC,EAAuB,GAAK,EAAsB,IAAI,EAAS,IAAI,IAAM,GAM1E,CAAC,EAAK,aAAa,EACrB,OAGF,IAAM,EAAU,EAAY,EACvB,KAIL,GAAI,CACF,OAAO,KAAK,MAAM,EAAQ,kBAAkB,EAAM,EAAS,KAAM,CAAiB,CAAC,CACrF,OAAS,EAAO,CAGd,GAAI,QAAQ,IAAI,2BAA6B,IAC3C,MAAM,EAIR,MACF,CACF,CAGA,SAAgB,GAAoC,CAClD,OAAO,EAAuB,GAAK,EAAY,IAAM,IAAA,EACvD,CAGA,SAAS,GAAkC,CACzC,OAAO,QAAQ,IAAI,oBAAsB,GAC3C,CAEA,SAAS,GAAyC,CAChD,GAAI,EACF,OAAO,EAET,EAAuB,GAEvB,GAAI,CAGF,IAAM,EADgB,EAAc,YAAY,GACpB,CAAC,CAAC,2BAA2B,EACrD,EAAQ,iBAAiB,IAAM,IACjC,EAAgB,EAIpB,MAAQ,CAER,CACA,OAAO,CACT"}
package/dist/ncss.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";const e=new Set([`comment`,`line_comment`,`block_comment`]),t=new Set([`attribute_item`,`inner_attribute_item`,`empty_statement`,`heredoc_body`,`parenthesized_statements`]),n=new Set([`property_signature`,`method_signature`,`index_signature`,`construct_signature`,`call_signature`]),r=new Set([`else_clause`,`elif_clause`,`else`,`elsif`]),i=new Set([`if_statement`,`if_expression`]),a=new Set([`struct_specifier`,`enum_specifier`,`union_specifier`,`class_specifier`]),o=new WeakMap;function s(e){o.delete(e)}function c(e){let t=o.get(e);return t||(t={countable:new Set(e.ncssNodeTypes),containers:new Set(e.ncssContainerNodeTypes)},o.set(e,t)),t}function l(e,t){let{countable:n,containers:r}=c(t),i=0;function a(e){i+=d(e,n,r);for(let t of e.children)a(t)}return a(e),i}function u(e,t){let{countable:n,containers:r}=c(t),i=d(e,n,r);return l(e,t)+(i>0?0:1)}function d(n,r,a){if(!n.isNamed||e.has(n.type)||g(n))return 0;let o=0,s=p(n,a)&&!a.has(n.type)&&!t.has(n.type);return(f(n,r)||s||_(n))&&!m(n,r)&&(o+=1),i.has(n.type)&&(o+=b(n).length),o}function f(e,t){return t.has(e.type)?a.has(e.type)?e.childForFieldName(`body`)!==null:e.type!==`resource`||e.childForFieldName(`name`)!==null:!1}function p(e,t){let n=e.parent;for(;n&&n.type===`parenthesized_statements`;)n=n.parent;return n!==null&&t.has(n.type)}function m(e,t){if(e.type!==`export_statement`)return!1;let n=e.childForFieldName(`declaration`);return n!==null&&(t.has(n.type)||n.type===`internal_module`||n.type===`ambient_declaration`)}const h=new Set([`init`,`initializer`,`condition`,`update`,`increment`]);function g(e){let t=e.parent;if(!t)return!1;if(t.type===`init_statement`&&t.parent?.type===`for_range_loop`)return!0;if(t.type!==`for_statement`&&t.type!==`for_clause`&&t.type!==`for_range_loop`)return!1;for(let n=0;n<t.childCount;n+=1)if(t.child(n)?.id===e.id)return h.has(t.fieldNameForChild(n)??``);return!1}function _(e){let t=e.parent?.type;return e.type===`block`&&t===`class_body`||n.has(e.type)&&t===`interface_body`||t===`match_arm`&&e.type!==`block`&&y(e,`value`)||e.type===`method_signature`&&t===`class_body`||e.type===`internal_module`&&t!==`expression_statement`||(t===`method`||t===`singleton_method`)&&e.type!==`body_statement`&&y(e,`body`)?!0:e.type===`friend_declaration`?!e.namedChildren.some(e=>e.type===`declaration`||e.type===`function_definition`):e.type===`macro_invocation`&&(t===`source_file`||t===`declaration_list`)?!0:e.type===`field_declaration`&&t===`field_declaration_list`?v(e.parent?.parent,`struct_type`):e.type===`method_elem`||e.type===`method_spec`||e.type===`type_elem`?v(e.parent,`interface_type`):!1}function v(e,t){return e?.type===t?e.parent?.type===`type_spec`||e.parent?.type===`type_alias`:!1}function y(e,t){let n=e.parent;if(!n)return!1;for(let r=0;r<n.childCount;r+=1)if(n.child(r)?.id===e.id)return n.fieldNameForChild(r)===t;return!1}function b(e){let t=[];for(let n=0;n<e.childCount;n+=1){let i=e.child(n);i&&e.fieldNameForChild(n)===`alternative`&&!r.has(i.type)&&t.push(i)}return t}exports.commentNodeTypes=e,exports.countFunctionNcss=u,exports.countNcss=l,exports.invalidateNcssSetsCache=s;
1
+ "use strict";const e=new Set([`comment`,`line_comment`,`block_comment`]),t=new Set([`attribute_item`,`inner_attribute_item`,`empty_statement`,`heredoc_body`,`parenthesized_statements`]),n=new Set([`property_signature`,`method_signature`,`index_signature`,`construct_signature`,`call_signature`]),r=new Set([`else_clause`,`elif_clause`,`else`,`elsif`]),i=new Set([`if_statement`,`if_expression`]),a=new Set([`struct_specifier`,`enum_specifier`,`union_specifier`,`class_specifier`]),o=new WeakMap;function s(e){o.delete(e)}function c(e){let t=o.get(e);return t||(t={countable:new Set(e.ncssNodeTypes),containers:new Set(e.ncssContainerNodeTypes)},o.set(e,t)),t}function l(e,t){let{countable:n,containers:r}=c(t),i=0;function a(e){i+=u(e,n,r);for(let t of e.children)a(t)}return a(e),i}function u(n,r,a){if(!n.isNamed||e.has(n.type)||h(n))return 0;let o=0,s=f(n,a)&&!a.has(n.type)&&!t.has(n.type);return(d(n,r)||s||g(n))&&!p(n,r)&&(o+=1),i.has(n.type)&&(o+=y(n).length),o}function d(e,t){return t.has(e.type)?a.has(e.type)?e.childForFieldName(`body`)!==null:e.type!==`resource`||e.childForFieldName(`name`)!==null:!1}function f(e,t){let n=e.parent;for(;n&&n.type===`parenthesized_statements`;)n=n.parent;return n!==null&&t.has(n.type)}function p(e,t){if(e.type!==`export_statement`)return!1;let n=e.childForFieldName(`declaration`);return n!==null&&(t.has(n.type)||n.type===`internal_module`||n.type===`ambient_declaration`)}const m=new Set([`init`,`initializer`,`condition`,`update`,`increment`]);function h(e){let t=e.parent;if(!t)return!1;if(t.type===`init_statement`&&t.parent?.type===`for_range_loop`)return!0;if(t.type!==`for_statement`&&t.type!==`for_clause`&&t.type!==`for_range_loop`)return!1;for(let n=0;n<t.childCount;n+=1)if(t.child(n)?.id===e.id)return m.has(t.fieldNameForChild(n)??``);return!1}function g(e){let t=e.parent?.type;return e.type===`block`&&t===`class_body`||n.has(e.type)&&t===`interface_body`||t===`match_arm`&&e.type!==`block`&&v(e,`value`)||e.type===`method_signature`&&t===`class_body`||e.type===`internal_module`&&t!==`expression_statement`||(t===`method`||t===`singleton_method`)&&e.type!==`body_statement`&&v(e,`body`)?!0:e.type===`friend_declaration`?!e.namedChildren.some(e=>e.type===`declaration`||e.type===`function_definition`):e.type===`macro_invocation`&&(t===`source_file`||t===`declaration_list`)?!0:e.type===`field_declaration`&&t===`field_declaration_list`?_(e.parent?.parent,`struct_type`):e.type===`method_elem`||e.type===`method_spec`||e.type===`type_elem`?_(e.parent,`interface_type`):!1}function _(e,t){return e?.type===t?e.parent?.type===`type_spec`||e.parent?.type===`type_alias`:!1}function v(e,t){let n=e.parent;if(!n)return!1;for(let r=0;r<n.childCount;r+=1)if(n.child(r)?.id===e.id)return n.fieldNameForChild(r)===t;return!1}function y(e){let t=[];for(let n=0;n<e.childCount;n+=1){let i=e.child(n);i&&e.fieldNameForChild(n)===`alternative`&&!r.has(i.type)&&t.push(i)}return t}exports.commentNodeTypes=e,exports.countNcss=l,exports.getNcssSets=c,exports.invalidateNcssSetsCache=s,exports.ncssContribution=u;
2
2
  //# sourceMappingURL=ncss.cjs.map
package/dist/ncss.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ncss.cjs","names":[],"sources":["../src/ncss.ts"],"sourcesContent":["import type Parser from 'tree-sitter';\nimport type { LanguageDefinition } from './types.js';\n\nexport const commentNodeTypes = new Set(['comment', 'line_comment', 'block_comment']);\n\n// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby\n// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),\n// and Ruby statement parentheses (transparent wrappers whose children count instead).\nconst positionalExclusionTypes = new Set([\n 'attribute_item',\n 'inner_attribute_item',\n 'empty_statement',\n 'heredoc_body',\n 'parenthesized_statements',\n]);\n\n// TypeScript interface members count like Java interface members, but the same node types appear\n// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so\n// they only count directly under an interface body.\nconst interfaceMemberNodeTypes = new Set([\n 'property_signature',\n 'method_signature',\n 'index_signature',\n 'construct_signature',\n 'call_signature',\n]);\n\n// An if-branch wrapped in one of these already counts through ncssNodeTypes; a bare `alternative`\n// (Java/Go put the else branch directly in the field) needs the extra `else` count instead.\nconst elseClauseNodeTypes = new Set(['else_clause', 'elif_clause', 'else', 'elsif']);\n\nconst ifNodeTypes = new Set(['if_statement', 'if_expression']);\n\n// C/C++ type specifiers only declare something when they carry a body (`struct S { ... }`);\n// without one they are mere type references inside other declarations.\nconst bodylessNcssSpecifierTypes = new Set([\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'class_specifier',\n]);\n\n/**\n * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,\n * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources\n * resource); `try` itself, braces, blank lines, and comments count 0.\n */\ninterface NcssSets {\n countable: Set<string>;\n containers: Set<string>;\n}\n\n// Cached per language: countNcss runs once per function plus once per file, so per-call Set\n// construction would add a measurable constant factor on large files.\nconst ncssSetsCache = new WeakMap<LanguageDefinition, NcssSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nexport function invalidateNcssSetsCache(language: LanguageDefinition): void {\n ncssSetsCache.delete(language);\n}\n\nfunction getNcssSets(language: LanguageDefinition): NcssSets {\n let sets = ncssSetsCache.get(language);\n if (!sets) {\n sets = { countable: new Set(language.ncssNodeTypes), containers: new Set(language.ncssContainerNodeTypes) };\n ncssSetsCache.set(language, sets);\n }\n return sets;\n}\n\nexport function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n let count = 0;\n\n function visit(current: Parser.SyntaxNode): void {\n count += ncssContribution(current, countable, containers);\n for (const child of current.children) {\n visit(child);\n }\n }\n\n visit(node);\n return count;\n}\n\n/**\n * Per-function NCSS: the function's whole subtree plus 1 for the declaration itself when the\n * function node carries no countable declaration of its own (arrow functions, lambdas, blocks).\n */\nexport function countFunctionNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n const selfContribution = ncssContribution(node, countable, containers);\n return countNcss(node, language) + (selfContribution > 0 ? 0 : 1);\n}\n\nfunction ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number {\n if (!node.isNamed || commentNodeTypes.has(node.type) || isForHeaderNode(node)) {\n return 0;\n }\n\n let contribution = 0;\n const positional =\n isInContainerPosition(node, containers) && !containers.has(node.type) && !positionalExclusionTypes.has(node.type);\n if (\n (countsThroughNodeType(node, countable) || positional || countsContextually(node)) &&\n !isDeclarationWrapper(node, countable)\n ) {\n contribution += 1;\n }\n\n // A bare else branch (Java/Go `alternative:` without an else-clause wrapper) counts 1 like the\n // `else` keyword does in PMD; an `else if` chain charges the nested if separately on top.\n if (ifNodeTypes.has(node.type)) {\n contribution += findBareAlternatives(node).length;\n }\n\n return contribution;\n}\n\nfunction countsThroughNodeType(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (!countable.has(node.type)) {\n return false;\n }\n if (bodylessNcssSpecifierTypes.has(node.type)) {\n return node.childForFieldName('body') !== null;\n }\n // A try-with-resources `resource` counts only when it declares a variable; `try (r)` reuses an\n // existing one and adds no statement (matching PMD).\n if (node.type === 'resource') {\n return node.childForFieldName('name') !== null;\n }\n return true;\n}\n\n/**\n * Direct container children count positionally; Ruby's `(foo; bar)` statement parentheses are\n * transparent (through arbitrary nesting), so their children count when the parentheses\n * themselves sit in a container.\n */\nfunction isInContainerPosition(node: Parser.SyntaxNode, containers: Set<string>): boolean {\n let ancestor = node.parent;\n while (ancestor && ancestor.type === 'parenthesized_statements') {\n ancestor = ancestor.parent;\n }\n return ancestor !== null && containers.has(ancestor.type);\n}\n\n/**\n * `export const x = 1` nests a countable declaration inside `export_statement`; only the inner\n * declaration counts, mirroring how PMD counts one statement per declared entity. The nested\n * declaration may also count contextually (`export namespace N {}` → internal_module) or sit one\n * level deeper (`export declare function f(): void;` → ambient_declaration > function_signature).\n */\nfunction isDeclarationWrapper(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (node.type !== 'export_statement') {\n return false;\n }\n const declaration = node.childForFieldName('declaration');\n return (\n declaration !== null &&\n (countable.has(declaration.type) ||\n declaration.type === 'internal_module' ||\n declaration.type === 'ambient_declaration')\n );\n}\n\nconst forHeaderFieldNames = new Set(['init', 'initializer', 'condition', 'update', 'increment']);\n\n/**\n * Statement-shaped nodes in a `for` header (`for (int i = 0; i < n; i++)`) are part of the loop\n * statement, which already counts; PMD does not count them separately. JavaScript parses the\n * condition as an `expression_statement` and Go parses the update as an `inc_statement`, so all\n * header fields must be excluded, not just the initializer.\n */\nfunction isForHeaderNode(node: Parser.SyntaxNode): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // C++20 range-for initializers nest one level deeper: for_range_loop > init_statement > node.\n if (parent.type === 'init_statement' && parent.parent?.type === 'for_range_loop') {\n return true;\n }\n if (parent.type !== 'for_statement' && parent.type !== 'for_clause' && parent.type !== 'for_range_loop') {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return forHeaderFieldNames.has(parent.fieldNameForChild(index) ?? '');\n }\n }\n return false;\n}\n\n/** Statements only countable by their position: constructs without a dedicated statement node. */\nfunction countsContextually(node: Parser.SyntaxNode): boolean {\n const parentType = node.parent?.type;\n // A Java instance initializer is a bare `block` in the class body; PMD counts it like the\n // `static_initializer` declaration it parallels.\n if (node.type === 'block' && parentType === 'class_body') {\n return true;\n }\n // TypeScript interface members (see interfaceMemberNodeTypes).\n if (interfaceMemberNodeTypes.has(node.type) && parentType === 'interface_body') {\n return true;\n }\n // A braceless Rust match-arm body (`1 => foo()`) has no expression_statement wrapper; count the\n // value expression so braced and unbraced arms measure alike.\n if (parentType === 'match_arm' && node.type !== 'block' && isFieldOfParent(node, 'value')) {\n return true;\n }\n // A TypeScript class-body method overload signature declares a member like its interface twin.\n if (node.type === 'method_signature' && parentType === 'class_body') {\n return true;\n }\n // An ambient `declare namespace M { ... }` is a bare `internal_module`; the non-ambient\n // `namespace N { ... }` is wrapped in an `expression_statement`, which already counts.\n if (node.type === 'internal_module' && parentType !== 'expression_statement') {\n return true;\n }\n // A Ruby endless method (`def f(x) = expr`) stores its single-statement body directly in the\n // `body` field instead of a positional `body_statement` container.\n if (\n (parentType === 'method' || parentType === 'singleton_method') &&\n node.type !== 'body_statement' &&\n isFieldOfParent(node, 'body')\n ) {\n return true;\n }\n // C++ `friend class X;` declares on its own; `friend void g() { ... }` merely wraps a counted\n // definition.\n if (node.type === 'friend_declaration') {\n return !node.namedChildren.some((child) => child.type === 'declaration' || child.type === 'function_definition');\n }\n // A Rust item-position macro invocation (`foo! {}` at module level) has no expression_statement\n // wrapper; the semicolon form does and already counts through it.\n if (node.type === 'macro_invocation' && (parentType === 'source_file' || parentType === 'declaration_list')) {\n return true;\n }\n // Go struct fields and interface members count like other languages' member declarations, but\n // only inside a named type declaration; inline anonymous types (`var x struct{ ... }`,\n // `func f(h interface{ ... })`) are part of one declaration.\n if (node.type === 'field_declaration' && parentType === 'field_declaration_list') {\n return isGoDeclaredTypeBody(node.parent?.parent, 'struct_type');\n }\n if (node.type === 'method_elem' || node.type === 'method_spec' || node.type === 'type_elem') {\n return isGoDeclaredTypeBody(node.parent, 'interface_type');\n }\n return false;\n}\n\nfunction isGoDeclaredTypeBody(typeNode: Parser.SyntaxNode | null | undefined, expectedType: string): boolean {\n if (typeNode?.type !== expectedType) {\n return false;\n }\n return typeNode.parent?.type === 'type_spec' || typeNode.parent?.type === 'type_alias';\n}\n\nfunction isFieldOfParent(node: Parser.SyntaxNode, fieldName: string): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return parent.fieldNameForChild(index) === fieldName;\n }\n }\n return false;\n}\n\nfunction findBareAlternatives(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const alternatives: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === 'alternative' && !elseClauseNodeTypes.has(child.type)) {\n alternatives.push(child);\n }\n }\n return alternatives;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ncss.cjs","names":[],"sources":["../src/ncss.ts"],"sourcesContent":["import type Parser from 'tree-sitter';\nimport type { LanguageDefinition } from './types.js';\n\nexport const commentNodeTypes = new Set(['comment', 'line_comment', 'block_comment']);\n\n// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby\n// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),\n// and Ruby statement parentheses (transparent wrappers whose children count instead).\nconst positionalExclusionTypes = new Set([\n 'attribute_item',\n 'inner_attribute_item',\n 'empty_statement',\n 'heredoc_body',\n 'parenthesized_statements',\n]);\n\n// TypeScript interface members count like Java interface members, but the same node types appear\n// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so\n// they only count directly under an interface body.\nconst interfaceMemberNodeTypes = new Set([\n 'property_signature',\n 'method_signature',\n 'index_signature',\n 'construct_signature',\n 'call_signature',\n]);\n\n// An if-branch wrapped in one of these already counts through ncssNodeTypes; a bare `alternative`\n// (Java/Go put the else branch directly in the field) needs the extra `else` count instead.\nconst elseClauseNodeTypes = new Set(['else_clause', 'elif_clause', 'else', 'elsif']);\n\nconst ifNodeTypes = new Set(['if_statement', 'if_expression']);\n\n// C/C++ type specifiers only declare something when they carry a body (`struct S { ... }`);\n// without one they are mere type references inside other declarations.\nconst bodylessNcssSpecifierTypes = new Set([\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'class_specifier',\n]);\n\n/**\n * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,\n * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources\n * resource); `try` itself, braces, blank lines, and comments count 0.\n */\nexport interface NcssSets {\n countable: Set<string>;\n containers: Set<string>;\n}\n\n// Cached per language: countNcss runs once per function plus once per file, so per-call Set\n// construction would add a measurable constant factor on large files.\nconst ncssSetsCache = new WeakMap<LanguageDefinition, NcssSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nexport function invalidateNcssSetsCache(language: LanguageDefinition): void {\n ncssSetsCache.delete(language);\n}\n\nexport function getNcssSets(language: LanguageDefinition): NcssSets {\n let sets = ncssSetsCache.get(language);\n if (!sets) {\n sets = { countable: new Set(language.ncssNodeTypes), containers: new Set(language.ncssContainerNodeTypes) };\n ncssSetsCache.set(language, sets);\n }\n return sets;\n}\n\nexport function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n let count = 0;\n\n function visit(current: Parser.SyntaxNode): void {\n count += ncssContribution(current, countable, containers);\n for (const child of current.children) {\n visit(child);\n }\n }\n\n visit(node);\n return count;\n}\n\nexport function ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number {\n if (!node.isNamed || commentNodeTypes.has(node.type) || isForHeaderNode(node)) {\n return 0;\n }\n\n let contribution = 0;\n const positional =\n isInContainerPosition(node, containers) && !containers.has(node.type) && !positionalExclusionTypes.has(node.type);\n if (\n (countsThroughNodeType(node, countable) || positional || countsContextually(node)) &&\n !isDeclarationWrapper(node, countable)\n ) {\n contribution += 1;\n }\n\n // A bare else branch (Java/Go `alternative:` without an else-clause wrapper) counts 1 like the\n // `else` keyword does in PMD; an `else if` chain charges the nested if separately on top.\n if (ifNodeTypes.has(node.type)) {\n contribution += findBareAlternatives(node).length;\n }\n\n return contribution;\n}\n\nfunction countsThroughNodeType(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (!countable.has(node.type)) {\n return false;\n }\n if (bodylessNcssSpecifierTypes.has(node.type)) {\n return node.childForFieldName('body') !== null;\n }\n // A try-with-resources `resource` counts only when it declares a variable; `try (r)` reuses an\n // existing one and adds no statement (matching PMD).\n if (node.type === 'resource') {\n return node.childForFieldName('name') !== null;\n }\n return true;\n}\n\n/**\n * Direct container children count positionally; Ruby's `(foo; bar)` statement parentheses are\n * transparent (through arbitrary nesting), so their children count when the parentheses\n * themselves sit in a container.\n */\nfunction isInContainerPosition(node: Parser.SyntaxNode, containers: Set<string>): boolean {\n let ancestor = node.parent;\n while (ancestor && ancestor.type === 'parenthesized_statements') {\n ancestor = ancestor.parent;\n }\n return ancestor !== null && containers.has(ancestor.type);\n}\n\n/**\n * `export const x = 1` nests a countable declaration inside `export_statement`; only the inner\n * declaration counts, mirroring how PMD counts one statement per declared entity. The nested\n * declaration may also count contextually (`export namespace N {}` → internal_module) or sit one\n * level deeper (`export declare function f(): void;` → ambient_declaration > function_signature).\n */\nfunction isDeclarationWrapper(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (node.type !== 'export_statement') {\n return false;\n }\n const declaration = node.childForFieldName('declaration');\n return (\n declaration !== null &&\n (countable.has(declaration.type) ||\n declaration.type === 'internal_module' ||\n declaration.type === 'ambient_declaration')\n );\n}\n\nconst forHeaderFieldNames = new Set(['init', 'initializer', 'condition', 'update', 'increment']);\n\n/**\n * Statement-shaped nodes in a `for` header (`for (int i = 0; i < n; i++)`) are part of the loop\n * statement, which already counts; PMD does not count them separately. JavaScript parses the\n * condition as an `expression_statement` and Go parses the update as an `inc_statement`, so all\n * header fields must be excluded, not just the initializer.\n */\nfunction isForHeaderNode(node: Parser.SyntaxNode): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // C++20 range-for initializers nest one level deeper: for_range_loop > init_statement > node.\n if (parent.type === 'init_statement' && parent.parent?.type === 'for_range_loop') {\n return true;\n }\n if (parent.type !== 'for_statement' && parent.type !== 'for_clause' && parent.type !== 'for_range_loop') {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return forHeaderFieldNames.has(parent.fieldNameForChild(index) ?? '');\n }\n }\n return false;\n}\n\n/** Statements only countable by their position: constructs without a dedicated statement node. */\nfunction countsContextually(node: Parser.SyntaxNode): boolean {\n const parentType = node.parent?.type;\n // A Java instance initializer is a bare `block` in the class body; PMD counts it like the\n // `static_initializer` declaration it parallels.\n if (node.type === 'block' && parentType === 'class_body') {\n return true;\n }\n // TypeScript interface members (see interfaceMemberNodeTypes).\n if (interfaceMemberNodeTypes.has(node.type) && parentType === 'interface_body') {\n return true;\n }\n // A braceless Rust match-arm body (`1 => foo()`) has no expression_statement wrapper; count the\n // value expression so braced and unbraced arms measure alike.\n if (parentType === 'match_arm' && node.type !== 'block' && isFieldOfParent(node, 'value')) {\n return true;\n }\n // A TypeScript class-body method overload signature declares a member like its interface twin.\n if (node.type === 'method_signature' && parentType === 'class_body') {\n return true;\n }\n // An ambient `declare namespace M { ... }` is a bare `internal_module`; the non-ambient\n // `namespace N { ... }` is wrapped in an `expression_statement`, which already counts.\n if (node.type === 'internal_module' && parentType !== 'expression_statement') {\n return true;\n }\n // A Ruby endless method (`def f(x) = expr`) stores its single-statement body directly in the\n // `body` field instead of a positional `body_statement` container.\n if (\n (parentType === 'method' || parentType === 'singleton_method') &&\n node.type !== 'body_statement' &&\n isFieldOfParent(node, 'body')\n ) {\n return true;\n }\n // C++ `friend class X;` declares on its own; `friend void g() { ... }` merely wraps a counted\n // definition.\n if (node.type === 'friend_declaration') {\n return !node.namedChildren.some((child) => child.type === 'declaration' || child.type === 'function_definition');\n }\n // A Rust item-position macro invocation (`foo! {}` at module level) has no expression_statement\n // wrapper; the semicolon form does and already counts through it.\n if (node.type === 'macro_invocation' && (parentType === 'source_file' || parentType === 'declaration_list')) {\n return true;\n }\n // Go struct fields and interface members count like other languages' member declarations, but\n // only inside a named type declaration; inline anonymous types (`var x struct{ ... }`,\n // `func f(h interface{ ... })`) are part of one declaration.\n if (node.type === 'field_declaration' && parentType === 'field_declaration_list') {\n return isGoDeclaredTypeBody(node.parent?.parent, 'struct_type');\n }\n if (node.type === 'method_elem' || node.type === 'method_spec' || node.type === 'type_elem') {\n return isGoDeclaredTypeBody(node.parent, 'interface_type');\n }\n return false;\n}\n\nfunction isGoDeclaredTypeBody(typeNode: Parser.SyntaxNode | null | undefined, expectedType: string): boolean {\n if (typeNode?.type !== expectedType) {\n return false;\n }\n return typeNode.parent?.type === 'type_spec' || typeNode.parent?.type === 'type_alias';\n}\n\nfunction isFieldOfParent(node: Parser.SyntaxNode, fieldName: string): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return parent.fieldNameForChild(index) === fieldName;\n }\n }\n return false;\n}\n\nfunction findBareAlternatives(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const alternatives: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === 'alternative' && !elseClauseNodeTypes.has(child.type)) {\n alternatives.push(child);\n }\n }\n return alternatives;\n}\n"],"mappings":"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"}
package/dist/ncss.d.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  import type Parser from 'tree-sitter';
2
2
  import type { LanguageDefinition } from './types.js';
3
3
  export declare const commentNodeTypes: Set<string>;
4
+ /**
5
+ * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,
6
+ * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources
7
+ * resource); `try` itself, braces, blank lines, and comments count 0.
8
+ */
9
+ export interface NcssSets {
10
+ countable: Set<string>;
11
+ containers: Set<string>;
12
+ }
4
13
  /** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */
5
14
  export declare function invalidateNcssSetsCache(language: LanguageDefinition): void;
15
+ export declare function getNcssSets(language: LanguageDefinition): NcssSets;
6
16
  export declare function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number;
7
- /**
8
- * Per-function NCSS: the function's whole subtree plus 1 for the declaration itself when the
9
- * function node carries no countable declaration of its own (arrow functions, lambdas, blocks).
10
- */
11
- export declare function countFunctionNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number;
17
+ export declare function ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number;
package/dist/ncss.js CHANGED
@@ -1,2 +1,2 @@
1
- const e=new Set([`comment`,`line_comment`,`block_comment`]),t=new Set([`attribute_item`,`inner_attribute_item`,`empty_statement`,`heredoc_body`,`parenthesized_statements`]),n=new Set([`property_signature`,`method_signature`,`index_signature`,`construct_signature`,`call_signature`]),r=new Set([`else_clause`,`elif_clause`,`else`,`elsif`]),i=new Set([`if_statement`,`if_expression`]),a=new Set([`struct_specifier`,`enum_specifier`,`union_specifier`,`class_specifier`]),o=new WeakMap;function s(e){o.delete(e)}function c(e){let t=o.get(e);return t||(t={countable:new Set(e.ncssNodeTypes),containers:new Set(e.ncssContainerNodeTypes)},o.set(e,t)),t}function l(e,t){let{countable:n,containers:r}=c(t),i=0;function a(e){i+=d(e,n,r);for(let t of e.children)a(t)}return a(e),i}function u(e,t){let{countable:n,containers:r}=c(t),i=d(e,n,r);return l(e,t)+(i>0?0:1)}function d(n,r,a){if(!n.isNamed||e.has(n.type)||g(n))return 0;let o=0,s=p(n,a)&&!a.has(n.type)&&!t.has(n.type);return(f(n,r)||s||_(n))&&!m(n,r)&&(o+=1),i.has(n.type)&&(o+=b(n).length),o}function f(e,t){return t.has(e.type)?a.has(e.type)?e.childForFieldName(`body`)!==null:e.type!==`resource`||e.childForFieldName(`name`)!==null:!1}function p(e,t){let n=e.parent;for(;n&&n.type===`parenthesized_statements`;)n=n.parent;return n!==null&&t.has(n.type)}function m(e,t){if(e.type!==`export_statement`)return!1;let n=e.childForFieldName(`declaration`);return n!==null&&(t.has(n.type)||n.type===`internal_module`||n.type===`ambient_declaration`)}const h=new Set([`init`,`initializer`,`condition`,`update`,`increment`]);function g(e){let t=e.parent;if(!t)return!1;if(t.type===`init_statement`&&t.parent?.type===`for_range_loop`)return!0;if(t.type!==`for_statement`&&t.type!==`for_clause`&&t.type!==`for_range_loop`)return!1;for(let n=0;n<t.childCount;n+=1)if(t.child(n)?.id===e.id)return h.has(t.fieldNameForChild(n)??``);return!1}function _(e){let t=e.parent?.type;return e.type===`block`&&t===`class_body`||n.has(e.type)&&t===`interface_body`||t===`match_arm`&&e.type!==`block`&&y(e,`value`)||e.type===`method_signature`&&t===`class_body`||e.type===`internal_module`&&t!==`expression_statement`||(t===`method`||t===`singleton_method`)&&e.type!==`body_statement`&&y(e,`body`)?!0:e.type===`friend_declaration`?!e.namedChildren.some(e=>e.type===`declaration`||e.type===`function_definition`):e.type===`macro_invocation`&&(t===`source_file`||t===`declaration_list`)?!0:e.type===`field_declaration`&&t===`field_declaration_list`?v(e.parent?.parent,`struct_type`):e.type===`method_elem`||e.type===`method_spec`||e.type===`type_elem`?v(e.parent,`interface_type`):!1}function v(e,t){return e?.type===t?e.parent?.type===`type_spec`||e.parent?.type===`type_alias`:!1}function y(e,t){let n=e.parent;if(!n)return!1;for(let r=0;r<n.childCount;r+=1)if(n.child(r)?.id===e.id)return n.fieldNameForChild(r)===t;return!1}function b(e){let t=[];for(let n=0;n<e.childCount;n+=1){let i=e.child(n);i&&e.fieldNameForChild(n)===`alternative`&&!r.has(i.type)&&t.push(i)}return t}export{e as commentNodeTypes,u as countFunctionNcss,l as countNcss,s as invalidateNcssSetsCache};
1
+ const e=new Set([`comment`,`line_comment`,`block_comment`]),t=new Set([`attribute_item`,`inner_attribute_item`,`empty_statement`,`heredoc_body`,`parenthesized_statements`]),n=new Set([`property_signature`,`method_signature`,`index_signature`,`construct_signature`,`call_signature`]),r=new Set([`else_clause`,`elif_clause`,`else`,`elsif`]),i=new Set([`if_statement`,`if_expression`]),a=new Set([`struct_specifier`,`enum_specifier`,`union_specifier`,`class_specifier`]),o=new WeakMap;function s(e){o.delete(e)}function c(e){let t=o.get(e);return t||(t={countable:new Set(e.ncssNodeTypes),containers:new Set(e.ncssContainerNodeTypes)},o.set(e,t)),t}function l(e,t){let{countable:n,containers:r}=c(t),i=0;function a(e){i+=u(e,n,r);for(let t of e.children)a(t)}return a(e),i}function u(n,r,a){if(!n.isNamed||e.has(n.type)||h(n))return 0;let o=0,s=f(n,a)&&!a.has(n.type)&&!t.has(n.type);return(d(n,r)||s||g(n))&&!p(n,r)&&(o+=1),i.has(n.type)&&(o+=y(n).length),o}function d(e,t){return t.has(e.type)?a.has(e.type)?e.childForFieldName(`body`)!==null:e.type!==`resource`||e.childForFieldName(`name`)!==null:!1}function f(e,t){let n=e.parent;for(;n&&n.type===`parenthesized_statements`;)n=n.parent;return n!==null&&t.has(n.type)}function p(e,t){if(e.type!==`export_statement`)return!1;let n=e.childForFieldName(`declaration`);return n!==null&&(t.has(n.type)||n.type===`internal_module`||n.type===`ambient_declaration`)}const m=new Set([`init`,`initializer`,`condition`,`update`,`increment`]);function h(e){let t=e.parent;if(!t)return!1;if(t.type===`init_statement`&&t.parent?.type===`for_range_loop`)return!0;if(t.type!==`for_statement`&&t.type!==`for_clause`&&t.type!==`for_range_loop`)return!1;for(let n=0;n<t.childCount;n+=1)if(t.child(n)?.id===e.id)return m.has(t.fieldNameForChild(n)??``);return!1}function g(e){let t=e.parent?.type;return e.type===`block`&&t===`class_body`||n.has(e.type)&&t===`interface_body`||t===`match_arm`&&e.type!==`block`&&v(e,`value`)||e.type===`method_signature`&&t===`class_body`||e.type===`internal_module`&&t!==`expression_statement`||(t===`method`||t===`singleton_method`)&&e.type!==`body_statement`&&v(e,`body`)?!0:e.type===`friend_declaration`?!e.namedChildren.some(e=>e.type===`declaration`||e.type===`function_definition`):e.type===`macro_invocation`&&(t===`source_file`||t===`declaration_list`)?!0:e.type===`field_declaration`&&t===`field_declaration_list`?_(e.parent?.parent,`struct_type`):e.type===`method_elem`||e.type===`method_spec`||e.type===`type_elem`?_(e.parent,`interface_type`):!1}function _(e,t){return e?.type===t?e.parent?.type===`type_spec`||e.parent?.type===`type_alias`:!1}function v(e,t){let n=e.parent;if(!n)return!1;for(let r=0;r<n.childCount;r+=1)if(n.child(r)?.id===e.id)return n.fieldNameForChild(r)===t;return!1}function y(e){let t=[];for(let n=0;n<e.childCount;n+=1){let i=e.child(n);i&&e.fieldNameForChild(n)===`alternative`&&!r.has(i.type)&&t.push(i)}return t}export{e as commentNodeTypes,l as countNcss,c as getNcssSets,s as invalidateNcssSetsCache,u as ncssContribution};
2
2
  //# sourceMappingURL=ncss.js.map
package/dist/ncss.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ncss.js","names":[],"sources":["../src/ncss.ts"],"sourcesContent":["import type Parser from 'tree-sitter';\nimport type { LanguageDefinition } from './types.js';\n\nexport const commentNodeTypes = new Set(['comment', 'line_comment', 'block_comment']);\n\n// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby\n// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),\n// and Ruby statement parentheses (transparent wrappers whose children count instead).\nconst positionalExclusionTypes = new Set([\n 'attribute_item',\n 'inner_attribute_item',\n 'empty_statement',\n 'heredoc_body',\n 'parenthesized_statements',\n]);\n\n// TypeScript interface members count like Java interface members, but the same node types appear\n// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so\n// they only count directly under an interface body.\nconst interfaceMemberNodeTypes = new Set([\n 'property_signature',\n 'method_signature',\n 'index_signature',\n 'construct_signature',\n 'call_signature',\n]);\n\n// An if-branch wrapped in one of these already counts through ncssNodeTypes; a bare `alternative`\n// (Java/Go put the else branch directly in the field) needs the extra `else` count instead.\nconst elseClauseNodeTypes = new Set(['else_clause', 'elif_clause', 'else', 'elsif']);\n\nconst ifNodeTypes = new Set(['if_statement', 'if_expression']);\n\n// C/C++ type specifiers only declare something when they carry a body (`struct S { ... }`);\n// without one they are mere type references inside other declarations.\nconst bodylessNcssSpecifierTypes = new Set([\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'class_specifier',\n]);\n\n/**\n * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,\n * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources\n * resource); `try` itself, braces, blank lines, and comments count 0.\n */\ninterface NcssSets {\n countable: Set<string>;\n containers: Set<string>;\n}\n\n// Cached per language: countNcss runs once per function plus once per file, so per-call Set\n// construction would add a measurable constant factor on large files.\nconst ncssSetsCache = new WeakMap<LanguageDefinition, NcssSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nexport function invalidateNcssSetsCache(language: LanguageDefinition): void {\n ncssSetsCache.delete(language);\n}\n\nfunction getNcssSets(language: LanguageDefinition): NcssSets {\n let sets = ncssSetsCache.get(language);\n if (!sets) {\n sets = { countable: new Set(language.ncssNodeTypes), containers: new Set(language.ncssContainerNodeTypes) };\n ncssSetsCache.set(language, sets);\n }\n return sets;\n}\n\nexport function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n let count = 0;\n\n function visit(current: Parser.SyntaxNode): void {\n count += ncssContribution(current, countable, containers);\n for (const child of current.children) {\n visit(child);\n }\n }\n\n visit(node);\n return count;\n}\n\n/**\n * Per-function NCSS: the function's whole subtree plus 1 for the declaration itself when the\n * function node carries no countable declaration of its own (arrow functions, lambdas, blocks).\n */\nexport function countFunctionNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n const selfContribution = ncssContribution(node, countable, containers);\n return countNcss(node, language) + (selfContribution > 0 ? 0 : 1);\n}\n\nfunction ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number {\n if (!node.isNamed || commentNodeTypes.has(node.type) || isForHeaderNode(node)) {\n return 0;\n }\n\n let contribution = 0;\n const positional =\n isInContainerPosition(node, containers) && !containers.has(node.type) && !positionalExclusionTypes.has(node.type);\n if (\n (countsThroughNodeType(node, countable) || positional || countsContextually(node)) &&\n !isDeclarationWrapper(node, countable)\n ) {\n contribution += 1;\n }\n\n // A bare else branch (Java/Go `alternative:` without an else-clause wrapper) counts 1 like the\n // `else` keyword does in PMD; an `else if` chain charges the nested if separately on top.\n if (ifNodeTypes.has(node.type)) {\n contribution += findBareAlternatives(node).length;\n }\n\n return contribution;\n}\n\nfunction countsThroughNodeType(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (!countable.has(node.type)) {\n return false;\n }\n if (bodylessNcssSpecifierTypes.has(node.type)) {\n return node.childForFieldName('body') !== null;\n }\n // A try-with-resources `resource` counts only when it declares a variable; `try (r)` reuses an\n // existing one and adds no statement (matching PMD).\n if (node.type === 'resource') {\n return node.childForFieldName('name') !== null;\n }\n return true;\n}\n\n/**\n * Direct container children count positionally; Ruby's `(foo; bar)` statement parentheses are\n * transparent (through arbitrary nesting), so their children count when the parentheses\n * themselves sit in a container.\n */\nfunction isInContainerPosition(node: Parser.SyntaxNode, containers: Set<string>): boolean {\n let ancestor = node.parent;\n while (ancestor && ancestor.type === 'parenthesized_statements') {\n ancestor = ancestor.parent;\n }\n return ancestor !== null && containers.has(ancestor.type);\n}\n\n/**\n * `export const x = 1` nests a countable declaration inside `export_statement`; only the inner\n * declaration counts, mirroring how PMD counts one statement per declared entity. The nested\n * declaration may also count contextually (`export namespace N {}` → internal_module) or sit one\n * level deeper (`export declare function f(): void;` → ambient_declaration > function_signature).\n */\nfunction isDeclarationWrapper(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (node.type !== 'export_statement') {\n return false;\n }\n const declaration = node.childForFieldName('declaration');\n return (\n declaration !== null &&\n (countable.has(declaration.type) ||\n declaration.type === 'internal_module' ||\n declaration.type === 'ambient_declaration')\n );\n}\n\nconst forHeaderFieldNames = new Set(['init', 'initializer', 'condition', 'update', 'increment']);\n\n/**\n * Statement-shaped nodes in a `for` header (`for (int i = 0; i < n; i++)`) are part of the loop\n * statement, which already counts; PMD does not count them separately. JavaScript parses the\n * condition as an `expression_statement` and Go parses the update as an `inc_statement`, so all\n * header fields must be excluded, not just the initializer.\n */\nfunction isForHeaderNode(node: Parser.SyntaxNode): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // C++20 range-for initializers nest one level deeper: for_range_loop > init_statement > node.\n if (parent.type === 'init_statement' && parent.parent?.type === 'for_range_loop') {\n return true;\n }\n if (parent.type !== 'for_statement' && parent.type !== 'for_clause' && parent.type !== 'for_range_loop') {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return forHeaderFieldNames.has(parent.fieldNameForChild(index) ?? '');\n }\n }\n return false;\n}\n\n/** Statements only countable by their position: constructs without a dedicated statement node. */\nfunction countsContextually(node: Parser.SyntaxNode): boolean {\n const parentType = node.parent?.type;\n // A Java instance initializer is a bare `block` in the class body; PMD counts it like the\n // `static_initializer` declaration it parallels.\n if (node.type === 'block' && parentType === 'class_body') {\n return true;\n }\n // TypeScript interface members (see interfaceMemberNodeTypes).\n if (interfaceMemberNodeTypes.has(node.type) && parentType === 'interface_body') {\n return true;\n }\n // A braceless Rust match-arm body (`1 => foo()`) has no expression_statement wrapper; count the\n // value expression so braced and unbraced arms measure alike.\n if (parentType === 'match_arm' && node.type !== 'block' && isFieldOfParent(node, 'value')) {\n return true;\n }\n // A TypeScript class-body method overload signature declares a member like its interface twin.\n if (node.type === 'method_signature' && parentType === 'class_body') {\n return true;\n }\n // An ambient `declare namespace M { ... }` is a bare `internal_module`; the non-ambient\n // `namespace N { ... }` is wrapped in an `expression_statement`, which already counts.\n if (node.type === 'internal_module' && parentType !== 'expression_statement') {\n return true;\n }\n // A Ruby endless method (`def f(x) = expr`) stores its single-statement body directly in the\n // `body` field instead of a positional `body_statement` container.\n if (\n (parentType === 'method' || parentType === 'singleton_method') &&\n node.type !== 'body_statement' &&\n isFieldOfParent(node, 'body')\n ) {\n return true;\n }\n // C++ `friend class X;` declares on its own; `friend void g() { ... }` merely wraps a counted\n // definition.\n if (node.type === 'friend_declaration') {\n return !node.namedChildren.some((child) => child.type === 'declaration' || child.type === 'function_definition');\n }\n // A Rust item-position macro invocation (`foo! {}` at module level) has no expression_statement\n // wrapper; the semicolon form does and already counts through it.\n if (node.type === 'macro_invocation' && (parentType === 'source_file' || parentType === 'declaration_list')) {\n return true;\n }\n // Go struct fields and interface members count like other languages' member declarations, but\n // only inside a named type declaration; inline anonymous types (`var x struct{ ... }`,\n // `func f(h interface{ ... })`) are part of one declaration.\n if (node.type === 'field_declaration' && parentType === 'field_declaration_list') {\n return isGoDeclaredTypeBody(node.parent?.parent, 'struct_type');\n }\n if (node.type === 'method_elem' || node.type === 'method_spec' || node.type === 'type_elem') {\n return isGoDeclaredTypeBody(node.parent, 'interface_type');\n }\n return false;\n}\n\nfunction isGoDeclaredTypeBody(typeNode: Parser.SyntaxNode | null | undefined, expectedType: string): boolean {\n if (typeNode?.type !== expectedType) {\n return false;\n }\n return typeNode.parent?.type === 'type_spec' || typeNode.parent?.type === 'type_alias';\n}\n\nfunction isFieldOfParent(node: Parser.SyntaxNode, fieldName: string): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return parent.fieldNameForChild(index) === fieldName;\n }\n }\n return false;\n}\n\nfunction findBareAlternatives(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const alternatives: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === 'alternative' && !elseClauseNodeTypes.has(child.type)) {\n alternatives.push(child);\n }\n }\n return alternatives;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ncss.js","names":[],"sources":["../src/ncss.ts"],"sourcesContent":["import type Parser from 'tree-sitter';\nimport type { LanguageDefinition } from './types.js';\n\nexport const commentNodeTypes = new Set(['comment', 'line_comment', 'block_comment']);\n\n// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby\n// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),\n// and Ruby statement parentheses (transparent wrappers whose children count instead).\nconst positionalExclusionTypes = new Set([\n 'attribute_item',\n 'inner_attribute_item',\n 'empty_statement',\n 'heredoc_body',\n 'parenthesized_statements',\n]);\n\n// TypeScript interface members count like Java interface members, but the same node types appear\n// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so\n// they only count directly under an interface body.\nconst interfaceMemberNodeTypes = new Set([\n 'property_signature',\n 'method_signature',\n 'index_signature',\n 'construct_signature',\n 'call_signature',\n]);\n\n// An if-branch wrapped in one of these already counts through ncssNodeTypes; a bare `alternative`\n// (Java/Go put the else branch directly in the field) needs the extra `else` count instead.\nconst elseClauseNodeTypes = new Set(['else_clause', 'elif_clause', 'else', 'elsif']);\n\nconst ifNodeTypes = new Set(['if_statement', 'if_expression']);\n\n// C/C++ type specifiers only declare something when they carry a body (`struct S { ... }`);\n// without one they are mere type references inside other declarations.\nconst bodylessNcssSpecifierTypes = new Set([\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'class_specifier',\n]);\n\n/**\n * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,\n * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources\n * resource); `try` itself, braces, blank lines, and comments count 0.\n */\nexport interface NcssSets {\n countable: Set<string>;\n containers: Set<string>;\n}\n\n// Cached per language: countNcss runs once per function plus once per file, so per-call Set\n// construction would add a measurable constant factor on large files.\nconst ncssSetsCache = new WeakMap<LanguageDefinition, NcssSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nexport function invalidateNcssSetsCache(language: LanguageDefinition): void {\n ncssSetsCache.delete(language);\n}\n\nexport function getNcssSets(language: LanguageDefinition): NcssSets {\n let sets = ncssSetsCache.get(language);\n if (!sets) {\n sets = { countable: new Set(language.ncssNodeTypes), containers: new Set(language.ncssContainerNodeTypes) };\n ncssSetsCache.set(language, sets);\n }\n return sets;\n}\n\nexport function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n let count = 0;\n\n function visit(current: Parser.SyntaxNode): void {\n count += ncssContribution(current, countable, containers);\n for (const child of current.children) {\n visit(child);\n }\n }\n\n visit(node);\n return count;\n}\n\nexport function ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number {\n if (!node.isNamed || commentNodeTypes.has(node.type) || isForHeaderNode(node)) {\n return 0;\n }\n\n let contribution = 0;\n const positional =\n isInContainerPosition(node, containers) && !containers.has(node.type) && !positionalExclusionTypes.has(node.type);\n if (\n (countsThroughNodeType(node, countable) || positional || countsContextually(node)) &&\n !isDeclarationWrapper(node, countable)\n ) {\n contribution += 1;\n }\n\n // A bare else branch (Java/Go `alternative:` without an else-clause wrapper) counts 1 like the\n // `else` keyword does in PMD; an `else if` chain charges the nested if separately on top.\n if (ifNodeTypes.has(node.type)) {\n contribution += findBareAlternatives(node).length;\n }\n\n return contribution;\n}\n\nfunction countsThroughNodeType(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (!countable.has(node.type)) {\n return false;\n }\n if (bodylessNcssSpecifierTypes.has(node.type)) {\n return node.childForFieldName('body') !== null;\n }\n // A try-with-resources `resource` counts only when it declares a variable; `try (r)` reuses an\n // existing one and adds no statement (matching PMD).\n if (node.type === 'resource') {\n return node.childForFieldName('name') !== null;\n }\n return true;\n}\n\n/**\n * Direct container children count positionally; Ruby's `(foo; bar)` statement parentheses are\n * transparent (through arbitrary nesting), so their children count when the parentheses\n * themselves sit in a container.\n */\nfunction isInContainerPosition(node: Parser.SyntaxNode, containers: Set<string>): boolean {\n let ancestor = node.parent;\n while (ancestor && ancestor.type === 'parenthesized_statements') {\n ancestor = ancestor.parent;\n }\n return ancestor !== null && containers.has(ancestor.type);\n}\n\n/**\n * `export const x = 1` nests a countable declaration inside `export_statement`; only the inner\n * declaration counts, mirroring how PMD counts one statement per declared entity. The nested\n * declaration may also count contextually (`export namespace N {}` → internal_module) or sit one\n * level deeper (`export declare function f(): void;` → ambient_declaration > function_signature).\n */\nfunction isDeclarationWrapper(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (node.type !== 'export_statement') {\n return false;\n }\n const declaration = node.childForFieldName('declaration');\n return (\n declaration !== null &&\n (countable.has(declaration.type) ||\n declaration.type === 'internal_module' ||\n declaration.type === 'ambient_declaration')\n );\n}\n\nconst forHeaderFieldNames = new Set(['init', 'initializer', 'condition', 'update', 'increment']);\n\n/**\n * Statement-shaped nodes in a `for` header (`for (int i = 0; i < n; i++)`) are part of the loop\n * statement, which already counts; PMD does not count them separately. JavaScript parses the\n * condition as an `expression_statement` and Go parses the update as an `inc_statement`, so all\n * header fields must be excluded, not just the initializer.\n */\nfunction isForHeaderNode(node: Parser.SyntaxNode): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // C++20 range-for initializers nest one level deeper: for_range_loop > init_statement > node.\n if (parent.type === 'init_statement' && parent.parent?.type === 'for_range_loop') {\n return true;\n }\n if (parent.type !== 'for_statement' && parent.type !== 'for_clause' && parent.type !== 'for_range_loop') {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return forHeaderFieldNames.has(parent.fieldNameForChild(index) ?? '');\n }\n }\n return false;\n}\n\n/** Statements only countable by their position: constructs without a dedicated statement node. */\nfunction countsContextually(node: Parser.SyntaxNode): boolean {\n const parentType = node.parent?.type;\n // A Java instance initializer is a bare `block` in the class body; PMD counts it like the\n // `static_initializer` declaration it parallels.\n if (node.type === 'block' && parentType === 'class_body') {\n return true;\n }\n // TypeScript interface members (see interfaceMemberNodeTypes).\n if (interfaceMemberNodeTypes.has(node.type) && parentType === 'interface_body') {\n return true;\n }\n // A braceless Rust match-arm body (`1 => foo()`) has no expression_statement wrapper; count the\n // value expression so braced and unbraced arms measure alike.\n if (parentType === 'match_arm' && node.type !== 'block' && isFieldOfParent(node, 'value')) {\n return true;\n }\n // A TypeScript class-body method overload signature declares a member like its interface twin.\n if (node.type === 'method_signature' && parentType === 'class_body') {\n return true;\n }\n // An ambient `declare namespace M { ... }` is a bare `internal_module`; the non-ambient\n // `namespace N { ... }` is wrapped in an `expression_statement`, which already counts.\n if (node.type === 'internal_module' && parentType !== 'expression_statement') {\n return true;\n }\n // A Ruby endless method (`def f(x) = expr`) stores its single-statement body directly in the\n // `body` field instead of a positional `body_statement` container.\n if (\n (parentType === 'method' || parentType === 'singleton_method') &&\n node.type !== 'body_statement' &&\n isFieldOfParent(node, 'body')\n ) {\n return true;\n }\n // C++ `friend class X;` declares on its own; `friend void g() { ... }` merely wraps a counted\n // definition.\n if (node.type === 'friend_declaration') {\n return !node.namedChildren.some((child) => child.type === 'declaration' || child.type === 'function_definition');\n }\n // A Rust item-position macro invocation (`foo! {}` at module level) has no expression_statement\n // wrapper; the semicolon form does and already counts through it.\n if (node.type === 'macro_invocation' && (parentType === 'source_file' || parentType === 'declaration_list')) {\n return true;\n }\n // Go struct fields and interface members count like other languages' member declarations, but\n // only inside a named type declaration; inline anonymous types (`var x struct{ ... }`,\n // `func f(h interface{ ... })`) are part of one declaration.\n if (node.type === 'field_declaration' && parentType === 'field_declaration_list') {\n return isGoDeclaredTypeBody(node.parent?.parent, 'struct_type');\n }\n if (node.type === 'method_elem' || node.type === 'method_spec' || node.type === 'type_elem') {\n return isGoDeclaredTypeBody(node.parent, 'interface_type');\n }\n return false;\n}\n\nfunction isGoDeclaredTypeBody(typeNode: Parser.SyntaxNode | null | undefined, expectedType: string): boolean {\n if (typeNode?.type !== expectedType) {\n return false;\n }\n return typeNode.parent?.type === 'type_spec' || typeNode.parent?.type === 'type_alias';\n}\n\nfunction isFieldOfParent(node: Parser.SyntaxNode, fieldName: string): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return parent.fieldNameForChild(index) === fieldName;\n }\n }\n return false;\n}\n\nfunction findBareAlternatives(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const alternatives: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === 'alternative' && !elseClauseNodeTypes.has(child.type)) {\n alternatives.push(child);\n }\n }\n return alternatives;\n}\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -6,7 +6,6 @@ export interface LanguageDefinition {
6
6
  parserLanguage: ParserLanguage;
7
7
  aliases?: readonly string[];
8
8
  functionNodeTypes?: readonly string[];
9
- classNodeTypes?: readonly string[];
10
9
  decisionNodeTypes?: readonly string[];
11
10
  nestingNodeTypes?: readonly string[];
12
11
  /** Node types that each count as one non-commenting source statement (NCSS). */
@@ -23,8 +22,9 @@ export interface DuplicationOptions {
23
22
  minTokens?: number;
24
23
  /**
25
24
  * Maximum normalized-token gap between two adjacent duplicate groups merged into one gapped
26
- * (Type-3) clone group (default 30). 0 disables merging. Applies to within-file detection only:
27
- * cross-file matching compares whole candidates and does not merge across gaps yet.
25
+ * (Type-3) clone group (default 30). 0 disables merging. Applies to within-file and cross-file
26
+ * detection: two near-identical regions in separate files that differ by one edited statement
27
+ * merge into one cross-file group when each file's fragments are gap-adjacent.
28
28
  */
29
29
  maxGapTokens?: number;
30
30
  /**
@@ -55,10 +55,7 @@ export interface HalsteadMetrics {
55
55
  vocabulary: number;
56
56
  length: number;
57
57
  volume: number;
58
- difficulty: number;
59
58
  effort: number;
60
- time: number;
61
- bugs: number;
62
59
  }
63
60
  export interface FunctionMetrics {
64
61
  name?: string;
@@ -72,8 +69,6 @@ export interface FunctionMetrics {
72
69
  startLine: number;
73
70
  startColumn: number;
74
71
  endLine: number;
75
- returnsJsx: boolean;
76
- cyclomaticComplexity: number;
77
72
  cognitiveComplexity: number;
78
73
  nestingDepth: number;
79
74
  /**
@@ -82,63 +77,19 @@ export interface FunctionMetrics {
82
77
  * class bodies are included, so summing over functions counts shared regions more than once.
83
78
  */
84
79
  ncss: number;
85
- callCount: number;
86
- uniqueCalleeCount: number;
87
- fanIn: number;
88
- fanOut: number;
89
80
  parameterCount: number;
90
- recursive: boolean;
91
- }
92
- export interface CallGraphMetrics {
93
- callCount: number;
94
- uniqueCalleeCount: number;
95
- internalCallCount: number;
96
- internalEdgeCount: number;
97
- recursiveFunctionCount: number;
98
- maxFanIn: number;
99
- maxFanOut: number;
100
- maxCallDepth: number;
101
- }
102
- export interface CouplingMetrics {
103
- importCount: number;
104
- importSourceCount: number;
105
- relativeImportCount: number;
106
- externalImportCount: number;
107
- exportCount: number;
108
- }
109
- export interface DeclarationMetrics {
110
- exported: boolean;
111
- name: string;
112
- startLine: number;
113
- }
114
- export interface ModuleMetrics {
115
- declarations: DeclarationMetrics[];
116
- importSources: string[];
117
- }
118
- export interface CohesionMetrics {
119
- averageFunctionIdentifierOverlap: number;
120
- sharedIdentifierCount: number;
121
- uniqueIdentifierCount: number;
122
- }
123
- export interface SyntaxFeatureMetrics {
124
- assignmentCount: number;
125
- awaitExpressionCount: number;
126
- loopStatementCount: number;
127
- mutableBindingCount: number;
128
- returnStatementCount: number;
129
- throwStatementCount: number;
130
- tryStatementCount: number;
131
81
  }
132
82
  /**
133
83
  * Within-file structural duplication: copy-pasted regions whose normalized token sequence repeats.
134
84
  * Identifiers are anonymized consistently by first-occurrence order and literals by kind, so
135
- * consistently renamed copies match. Distinct from cross-file duplicate symbol names.
85
+ * consistently renamed copies match.
136
86
  */
137
87
  export interface DuplicationMetrics {
138
88
  /**
139
89
  * Number of redundant (extra) duplicated regions: sum over groups of (groupSize - 1) times the
140
- * matched fragments per occurrence, so a gapped (merged) clone counts like its unmerged
141
- * fragments and threshold semantics do not shift with merging.
90
+ * matched fragments per occurrence, so merging a gapped clone's fragments into one group does
91
+ * not halve duplicateBlockCount — an edited two-fragment pair still counts 2, exactly as its
92
+ * unmerged fragments did.
142
93
  */
143
94
  duplicateBlockCount: number;
144
95
  /** Number of distinct normalized token sequences that appear more than once. */
@@ -147,6 +98,13 @@ export interface DuplicationMetrics {
147
98
  duplicateBlockGroups: DuplicateBlockOccurrence[][];
148
99
  /** Number of distinct lines covered by any counted duplicate occurrence (originals included). */
149
100
  duplicateLineCount: number;
101
+ /**
102
+ * The 1-based lines behind duplicateLineCount, sorted ascending: code lines carrying matched
103
+ * tokens. Exposed so consumers combining within-file and cross-file coverage can union exact
104
+ * line sets instead of over-counting from block bounding ranges (which include the unmatched gap
105
+ * of a merged clone and comment/blank lines).
106
+ */
107
+ duplicateLineNumbers: number[];
150
108
  /**
151
109
  * duplicateLineCount / code lines (0 when the file has no code). Code lines are the denominator
152
110
  * because duplicated lines are counted from matched tokens, which only ever land on code lines;
@@ -160,40 +118,17 @@ export interface DuplicateBlockOccurrence {
160
118
  endLine: number;
161
119
  startLine: number;
162
120
  }
163
- export interface TypeComplexityMetrics {
164
- typeAnnotationCount: number;
165
- typeAliasCount: number;
166
- interfaceCount: number;
167
- genericParameterCount: number;
168
- unionTypeCount: number;
169
- intersectionTypeCount: number;
170
- conditionalTypeCount: number;
171
- typeAssertionCount: number;
172
- nonNullAssertionCount: number;
173
- satisfiesExpressionCount: number;
174
- }
175
121
  export interface CodeMetrics {
176
122
  language: LanguageName;
177
123
  bytes: number;
178
124
  lines: LineMetrics;
179
125
  functions: FunctionMetrics[];
180
- classCount: number;
181
- functionCount: number;
182
- cyclomaticComplexity: number;
183
- maxCyclomaticComplexity: number;
184
126
  cognitiveComplexity: number;
185
127
  maxCognitiveComplexity: number;
186
128
  nestingDepth: number;
187
129
  /** Non-commenting source statements in the whole file; every statement counts exactly once. */
188
130
  ncssCount: number;
189
- callGraph: CallGraphMetrics;
190
- coupling: CouplingMetrics;
191
- module: ModuleMetrics;
192
- cohesion: CohesionMetrics;
193
- syntaxFeatures: SyntaxFeatureMetrics;
194
- typeComplexity: TypeComplexityMetrics;
195
131
  duplication: DuplicationMetrics;
196
132
  halstead: HalsteadMetrics;
197
- maintainabilityIndex: number;
198
133
  syntaxTree?: string;
199
134
  }
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "code-gauge",
3
- "version": "1.14.0",
3
+ "version": "3.0.0",
4
4
  "description": "Measure code metrics with tree-sitter.",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "tree-sitter",
8
8
  "metrics",
9
9
  "loc",
10
- "cyclomatic-complexity",
11
- "cognitive-complexity"
10
+ "cognitive-complexity",
11
+ "ncss",
12
+ "duplication",
13
+ "refactoring"
12
14
  ],
13
15
  "repository": {
14
16
  "type": "git",
@@ -57,8 +59,7 @@
57
59
  "tree-sitter-python": "^0.21.0",
58
60
  "tree-sitter-ruby": "^0.21.0",
59
61
  "tree-sitter-rust": "^0.21.0",
60
- "tree-sitter-typescript": "^0.21.2",
61
- "typescript": "7.0.2"
62
+ "tree-sitter-typescript": "^0.21.2"
62
63
  },
63
64
  "devDependencies": {
64
65
  "@tsconfig/bun": "1.0.10",
@@ -77,6 +78,7 @@
77
78
  "oxlint-tsgolint": "7.0.2001",
78
79
  "semantic-release": "25.0.5",
79
80
  "sort-package-json": "4.0.0",
81
+ "typescript": "7.0.2",
80
82
  "vitest": "4.1.9"
81
83
  },
82
84
  "engines": {
@@ -1,2 +0,0 @@
1
- "use strict";const e=require("./_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);function n(e,n){let i=e.map(e=>({...e,relativeFile:t.default.relative(n,e.file)||t.default.basename(e.file)})),a=r(i),o=b(i),s=w(o),c=i.map(e=>{let t=a.dependenciesByFile.get(e.relativeFile)??new Set,n=a.nonRelativeLocalImportCountByFile.get(e.relativeFile)??0,r=T(e.metrics),i=E(e.metrics,t.size,n);return{file:e.relativeFile,directLocalDependencyCount:t.size,duplicateSymbolGroupCount:s.get(e.relativeFile)??0,structuralBreadthScore:i.length,structuralCoordination:r,structuralFeatureGroups:i,transitiveLocalDependencyCount:y(e.relativeFile,a.dependenciesByFile)}});return{duplicateSymbolGroups:o,files:c,maxDirectLocalDependencyCount:k(c,`directLocalDependencyCount`),maxDuplicateSymbolGroupCount:k(c,`duplicateSymbolGroupCount`),maxStructuralBreadthScore:k(c,`structuralBreadthScore`),maxStructuralCoordinationScore:Math.max(0,...c.map(e=>e.structuralCoordination.score)),maxTransitiveLocalDependencyCount:k(c,`transitiveLocalDependencyCount`)}}function r(e){let t=new Set(e.map(e=>e.relativeFile)),n=m(e),r=new Map,i=new Map;for(let a of e){let e=new Set,o=0;for(let r of a.metrics.module.importSources){let i=s(a.relativeFile,r,t,n);i&&i!==a.relativeFile&&(e.add(i),g(a.relativeFile)===`.java`&&!r.startsWith(`.`)&&(o+=1))}r.set(a.relativeFile,e),i.set(a.relativeFile,o)}return{dependenciesByFile:r,nonRelativeLocalImportCountByFile:i}}const i=new Set([`.js`,`.jsx`,`.ts`,`.tsx`,`.mjs`,`.cjs`,`.mts`,`.cts`]),a=new Set([`.c`,`.c++`,`.cc`,`.cp`,`.cpp`,`.cxx`,`.h`,`.hh`,`.hpp`,`.hxx`,`.tcc`]),o=new Map([[`.js`,[`.ts`,`.tsx`]],[`.jsx`,[`.tsx`]],[`.mjs`,[`.mts`]],[`.cjs`,[`.cts`]]]);function s(e,n,r,a){let s=g(e);if(!n.startsWith(`.`))return s===`.rs`&&/^(?:crate|self|super)(?:::|$)/u.test(n)?u(e,n,r):s===`.java`?h(n,a):void 0;let l=_(e,n);if(i.has(s))for(let e of l){let n=o.get(t.default.extname(e));if(!n)continue;let i=e.slice(0,-t.default.extname(e).length);for(let e of n)if(r.has(`${i}${e}`))return`${i}${e}`}for(let e of l)if(r.has(e))return e;let{extensions:d,indexFiles:f}=c(e);for(let e of l){for(let t of d)if(r.has(`${e}${t}`))return`${e}${t}`;for(let n of f){let i=t.default.join(e,n);if(r.has(i))return i}}}function c(e){let t=g(e);return t===`.rb`?{extensions:[`.rb`],indexFiles:[]}:t===`.py`?{extensions:[`.py`],indexFiles:[`__init__.py`]}:a.has(t)?{extensions:[],indexFiles:[]}:i.has(t)?{extensions:[`.ts`,`.tsx`,`.js`,`.jsx`,`.mts`,`.cts`,`.mjs`,`.cjs`],indexFiles:[`index.ts`,`index.tsx`,`index.js`]}:{extensions:[`.ts`,`.tsx`,`.js`,`.jsx`,`.py`,`.rb`,`.h`,`.hpp`,`.hh`,`.hxx`,`.c`,`.cpp`,`.cc`,`.cxx`],indexFiles:[`index.ts`,`index.tsx`,`index.js`,`__init__.py`]}}const l=new Set([`mod.rs`,`lib.rs`,`main.rs`]);function u(e,n,r){let i=n.split(`::`),a=i.shift(),o=0;for(;a===`super`&&i[0]===`super`;)i.shift(),o+=1;let s=i.join(`/`);if(!s)return;let c=p(t.default.dirname(e)),u=l.has(t.default.basename(e)),m=t.default.join(c,t.default.basename(e,`.rs`)),h;h=a===`self`?u?[c]:[m,c]:a===`super`?(u?[p(t.default.dirname(c)),c]:[c,p(t.default.dirname(c))]).map(e=>f(e,o)):d(c,r);let g=[s,i.slice(0,-1).join(`/`)].filter(Boolean);for(let n of h)for(let i of g)for(let a of[t.default.join(n,`${i}.rs`),t.default.join(n,i,`mod.rs`)])if(a!==e&&r.has(a))return a}function d(e,n){let r=[],i=e;for(;r.push(i),i!==``;)i=p(t.default.dirname(i));let a=r.filter(e=>n.has(t.default.join(e,`lib.rs`))||n.has(t.default.join(e,`main.rs`)));return a.length>0?a:r}function f(e,n){let r=e;for(let e=0;e<n&&r!==``;e+=1)r=p(t.default.dirname(r));return r}function p(e){return e===`.`?``:e}function m(e){let n=new Map;for(let{relativeFile:r,metrics:i}of e){if(g(r)!==`.java`)continue;let e=t.default.basename(r,t.default.extname(r)),a=(e,t)=>{let i=n.get(e)??[];i.some(e=>e.file===r&&e.qualifiedPath===t)||(i.push({file:r,qualifiedPath:t}),n.set(e,i))},o=!1;for(let t of i.module.declarations){let n=t.name.lastIndexOf(`::`),r=n===-1?t.name:t.name.slice(n+2),i=n===-1?void 0:t.name.slice(0,n).replaceAll(`.`,`/`);a(r,i?`${i}/${r}`:void 0),o||=r===e}o||a(e,void 0)}for(let e of n.values())e.sort((e,t)=>e.file.localeCompare(t.file));return n}function h(e,n){if(e.endsWith(`.*`))return;let r=e.split(`.`);for(let e=r.length;e>=1;--e){let i=r.slice(0,e),a=i.at(-1);if(!a)continue;let o=i.join(`/`),s=n.get(a)??[],c=s.find(e=>e.qualifiedPath===o);if(c)return c.file;let l=s.find(e=>{if(e.qualifiedPath)return!1;let n=e.file.replaceAll(`\\`,`/`),r=n.slice(0,n.length-t.default.extname(n).length);return r===o||r.endsWith(`/${o}`)});if(l)return l.file}}function g(e){return t.default.extname(e).toLowerCase()}function _(e,n){let r=t.default.dirname(e);if(v(n))return[t.default.normalize(t.default.join(r,n))];let i=/^(?<dots>\.+)(?<module>.*)$/u.exec(n),a=i?.groups?.dots,o=i?.groups?.module;if(!a||o===void 0)return[];let s=r;for(let e=1;e<a.length;e+=1)s=t.default.dirname(s);let c=o.replaceAll(`.`,t.default.sep);return[t.default.normalize(t.default.join(s,c))]}function v(e){return e.startsWith(`./`)||e.startsWith(`../`)||e.includes(`/`)}function y(e,t){let n=new Set([e]),r=[...t.get(e)??[]];for(;r.length>0;){let e=r.pop();!e||n.has(e)||(n.add(e),r.push(...t.get(e)??[]))}return n.size-1}function b(e){let t=new Map;for(let n of e)for(let e of n.metrics.module.declarations){if(!x(e))continue;let r=t.get(e.name)??[];r.push(S(n.relativeFile,e)),t.set(e.name,r)}return[...t.entries()].flatMap(([e,t])=>{let n=[...new Set(t.map(e=>e.file))].toSorted();return n.length>1?[{declarations:t.toSorted(C),files:n,name:e}]:[]}).toSorted((e,t)=>t.files.length-e.files.length||e.name.localeCompare(t.name))}function x(e){return(e.name.split(/::|\./u).at(-1)??e.name).length>=5}function S(e,t){return{file:e,line:t.startLine}}function C(e,t){return e.file.localeCompare(t.file)||e.line-t.line}function w(e){let t=new Map;for(let n of e)for(let e of n.files)t.set(e,(t.get(e)??0)+1);return t}function T(e){let t=e.syntaxFeatures.awaitExpressionCount,n=e.syntaxFeatures.tryStatementCount+e.syntaxFeatures.throwStatementCount,r=e.syntaxFeatures.mutableBindingCount+e.syntaxFeatures.assignmentCount,i=e.cyclomaticComplexity+e.syntaxFeatures.loopStatementCount+e.syntaxFeatures.returnStatementCount,a=e.callGraph.callCount+e.callGraph.internalEdgeCount*2+e.callGraph.maxCallDepth*3;return{asyncBoundaryCount:t,branchingScore:i,exceptionHandlingCount:n,moduleInteractionScore:a,score:t*2+n*3+r+i+a,stateMutationScore:r}}function E(e,t,n){let r=new Set;return D(r,`class-shapes`,e.classCount>0),D(r,`control-flow`,e.cognitiveComplexity>0),D(r,`external-dependencies`,e.coupling.externalImportCount-n>0),D(r,`functions`,e.functionCount>0),D(r,`local-dependencies`,e.coupling.relativeImportCount>0||t>0),D(r,`module-api`,e.coupling.exportCount>0||e.module.declarations.length>0),D(r,`state-mutation`,e.syntaxFeatures.mutableBindingCount+e.syntaxFeatures.assignmentCount>0),D(r,`type-shapes`,O(e)),[...r].toSorted()}function D(e,t,n){n&&e.add(t)}function O(e){return e.typeComplexity.typeAnnotationCount+e.typeComplexity.typeAliasCount+e.typeComplexity.interfaceCount+e.typeComplexity.genericParameterCount+e.typeComplexity.unionTypeCount+e.typeComplexity.intersectionTypeCount+e.typeComplexity.conditionalTypeCount>0}function k(e,t){return Math.max(0,...e.map(e=>e[t]).filter(e=>typeof e==`number`))}exports.measureArchitecture=n;
2
- //# sourceMappingURL=architectureMetrics.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"architectureMetrics.cjs","names":["path"],"sources":["../src/architectureMetrics.ts"],"sourcesContent":["import path from 'node:path';\nimport type { CodeMetrics, DeclarationMetrics } from './types.js';\n\nexport interface ArchitectureSourceFile {\n file: string;\n metrics: CodeMetrics;\n}\n\nexport interface ArchitectureFileMetrics {\n directLocalDependencyCount: number;\n duplicateSymbolGroupCount: number;\n file: string;\n structuralBreadthScore: number;\n structuralCoordination: StructuralCoordinationMetrics;\n structuralFeatureGroups: string[];\n transitiveLocalDependencyCount: number;\n}\n\nexport interface ArchitectureMetrics {\n duplicateSymbolGroups: DuplicateSymbolGroup[];\n files: ArchitectureFileMetrics[];\n maxDirectLocalDependencyCount: number;\n maxDuplicateSymbolGroupCount: number;\n maxStructuralBreadthScore: number;\n maxStructuralCoordinationScore: number;\n maxTransitiveLocalDependencyCount: number;\n}\n\nexport interface DuplicateSymbolGroup {\n declarations: DuplicateSymbolDeclaration[];\n files: string[];\n name: string;\n}\n\nexport interface DuplicateSymbolDeclaration {\n file: string;\n line: number;\n}\n\nexport interface StructuralCoordinationMetrics {\n asyncBoundaryCount: number;\n branchingScore: number;\n exceptionHandlingCount: number;\n moduleInteractionScore: number;\n score: number;\n stateMutationScore: number;\n}\n\ninterface SourceFile {\n file: string;\n metrics: CodeMetrics;\n relativeFile: string;\n}\n\nconst duplicateSymbolNameLengthThreshold = 5;\n\nexport function measureArchitecture(\n files: readonly ArchitectureSourceFile[],\n displayRoot: string\n): ArchitectureMetrics {\n const sourceFiles = files.map((file) => ({\n ...file,\n relativeFile: path.relative(displayRoot, file.file) || path.basename(file.file),\n }));\n const dependencyGraph = measureDependencyGraph(sourceFiles);\n const duplicateSymbolGroups = measureDuplicateSymbolGroups(sourceFiles);\n const duplicateGroupCountByFile = measureDuplicateGroupCountByFile(duplicateSymbolGroups);\n const metrics = sourceFiles.map((sourceFile) => {\n const directDependencies = dependencyGraph.dependenciesByFile.get(sourceFile.relativeFile) ?? new Set<string>();\n const nonRelativeLocalImportCount =\n dependencyGraph.nonRelativeLocalImportCountByFile.get(sourceFile.relativeFile) ?? 0;\n const structuralCoordination = measureStructuralCoordination(sourceFile.metrics);\n const structuralFeatureGroups = measureStructuralFeatureGroups(\n sourceFile.metrics,\n directDependencies.size,\n nonRelativeLocalImportCount\n );\n return {\n file: sourceFile.relativeFile,\n directLocalDependencyCount: directDependencies.size,\n duplicateSymbolGroupCount: duplicateGroupCountByFile.get(sourceFile.relativeFile) ?? 0,\n structuralBreadthScore: structuralFeatureGroups.length,\n structuralCoordination,\n structuralFeatureGroups,\n transitiveLocalDependencyCount: measureTransitiveDependencyCount(\n sourceFile.relativeFile,\n dependencyGraph.dependenciesByFile\n ),\n };\n });\n\n return {\n duplicateSymbolGroups,\n files: metrics,\n maxDirectLocalDependencyCount: maxFileMetric(metrics, 'directLocalDependencyCount'),\n maxDuplicateSymbolGroupCount: maxFileMetric(metrics, 'duplicateSymbolGroupCount'),\n maxStructuralBreadthScore: maxFileMetric(metrics, 'structuralBreadthScore'),\n maxStructuralCoordinationScore: Math.max(0, ...metrics.map((file) => file.structuralCoordination.score)),\n maxTransitiveLocalDependencyCount: maxFileMetric(metrics, 'transitiveLocalDependencyCount'),\n };\n}\n\ninterface DependencyGraph {\n dependenciesByFile: Map<string, Set<string>>;\n /** Import sources without a relative prefix (Java dotted paths) that still resolved locally. */\n nonRelativeLocalImportCountByFile: Map<string, number>;\n}\n\nfunction measureDependencyGraph(files: SourceFile[]): DependencyGraph {\n const fileSet = new Set(files.map((file) => file.relativeFile));\n const javaFileIndex = buildJavaFileIndex(files);\n const dependenciesByFile = new Map<string, Set<string>>();\n const nonRelativeLocalImportCountByFile = new Map<string, number>();\n for (const file of files) {\n const dependencies = new Set<string>();\n let nonRelativeLocalImportCount = 0;\n for (const source of file.metrics.module.importSources) {\n const resolved = resolveLocalImport(file.relativeFile, source, fileSet, javaFileIndex);\n // A file importing itself (`import './a.js'` inside a.ts) is not a dependency.\n if (resolved && resolved !== file.relativeFile) {\n dependencies.add(resolved);\n // Only Java needs the external-count correction: its dotted imports classify as external\n // in coupling metrics. Rust crate::/self::/super:: sources already classify as relative,\n // so counting them would double-subtract from externalImportCount.\n if (fileExtension(file.relativeFile) === '.java' && !source.startsWith('.')) {\n nonRelativeLocalImportCount += 1;\n }\n }\n }\n dependenciesByFile.set(file.relativeFile, dependencies);\n nonRelativeLocalImportCountByFile.set(file.relativeFile, nonRelativeLocalImportCount);\n }\n return { dependenciesByFile, nonRelativeLocalImportCountByFile };\n}\n\nconst jsExtensions = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts']);\n// Must cover every extension the CLI's languageByExtension maps to C/C++ (uppercase `.C` arrives\n// here lowercased by fileExtension), or importProbes falls into the non-C catch-all branch.\nconst cExtensions = new Set(['.c', '.c++', '.cc', '.cp', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.hxx', '.tcc']);\n\n/**\n * TypeScript's ESM extension substitutions: a JS runtime extension in an import may name the\n * TypeScript source that compiles to it. `.ts`-family extensions are intentionally absent (they\n * resolve exactly), and `.mjs`/`.cjs` never remap to plain `.ts`.\n */\nconst jsImportExtensionSubstitutions = new Map<string, string[]>([\n ['.js', ['.ts', '.tsx']],\n ['.jsx', ['.tsx']],\n ['.mjs', ['.mts']],\n ['.cjs', ['.cts']],\n]);\n\nfunction resolveLocalImport(\n fromFile: string,\n source: string,\n fileSet: Set<string>,\n javaFileIndex: Map<string, JavaClassLocation[]>\n): string | undefined {\n const fromExtension = fileExtension(fromFile);\n if (!source.startsWith('.')) {\n if (fromExtension === '.rs' && /^(?:crate|self|super)(?:::|$)/u.test(source)) {\n return resolveRustLocalImport(fromFile, source, fileSet);\n }\n // Java imports are dotted class paths without a leading dot; other absolute sources are external.\n return fromExtension === '.java' ? resolveJavaImport(source, javaFileIndex) : undefined;\n }\n const bases = localImportBases(fromFile, source);\n\n // The JS-family ESM remap follows TypeScript's substitution table, which tries the TypeScript\n // source BEFORE the named JS file: `./foo.js` names `foo.ts` even when `foo.js` also exists,\n // and `./foo.mjs` may only name `foo.mts` (never an unrelated `foo.ts`), vice versa for `.cjs`.\n // Non-JS importers never remap, so `#include \"config.inc\"` cannot rebind to same-stem files.\n if (jsExtensions.has(fromExtension)) {\n for (const base of bases) {\n const substitutions = jsImportExtensionSubstitutions.get(path.extname(base));\n if (!substitutions) {\n continue;\n }\n const stem = base.slice(0, -path.extname(base).length);\n for (const substitution of substitutions) {\n if (fileSet.has(`${stem}${substitution}`)) {\n return `${stem}${substitution}`;\n }\n }\n }\n }\n\n // An explicit-path source (`#include \"b.hpp\"`, `require_relative \"./b.rb\"`, `./foo.js` with no\n // TypeScript counterpart) resolves exactly before extension probing, so a sibling `b.ts` cannot\n // shadow the named file.\n for (const base of bases) {\n if (fileSet.has(base)) {\n return base;\n }\n }\n\n const { extensions, indexFiles } = importProbes(fromFile);\n\n // Extension probing keeps each base's full name, so `./user.service` prefers `user.service.ts`.\n for (const stem of bases) {\n for (const extension of extensions) {\n if (fileSet.has(`${stem}${extension}`)) {\n return `${stem}${extension}`;\n }\n }\n for (const indexFile of indexFiles) {\n const candidate = path.join(stem, indexFile);\n if (fileSet.has(candidate)) {\n return candidate;\n }\n }\n }\n return undefined;\n}\n\n/** Extension-less sources probe only extensions the importing language can actually load. */\nfunction importProbes(fromFile: string): { extensions: string[]; indexFiles: string[] } {\n const fromExtension = fileExtension(fromFile);\n if (fromExtension === '.rb') {\n return { extensions: ['.rb'], indexFiles: [] };\n }\n if (fromExtension === '.py') {\n return { extensions: ['.py'], indexFiles: ['__init__.py'] };\n }\n if (cExtensions.has(fromExtension)) {\n // The C preprocessor searches for the exact requested filename and never appends extensions,\n // so `#include \"config\"` must not resolve to a same-stem `config.h`.\n return { extensions: [], indexFiles: [] };\n }\n if (jsExtensions.has(fromExtension)) {\n return {\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'],\n indexFiles: ['index.ts', 'index.tsx', 'index.js'],\n };\n }\n return {\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.py', '.rb', '.h', '.hpp', '.hh', '.hxx', '.c', '.cpp', '.cc', '.cxx'],\n indexFiles: ['index.ts', 'index.tsx', 'index.js', '__init__.py'],\n };\n}\n\n/** Rust module owner files whose child modules live as siblings in the same directory. */\nconst rustOwnerFileNames = new Set(['mod.rs', 'lib.rs', 'main.rs']);\n\n/**\n * Resolves a Rust in-crate module path (`crate::a::b`, `self::x`, `super::x`) to a scanned file by\n * probing `<path>.rs` and `<path>/mod.rs` against the appropriate base directories. `crate::` roots\n * are inferred from ancestor directories containing `lib.rs`/`main.rs`, falling back to every\n * ancestor so single-crate scans without a crate root file still resolve.\n */\nfunction resolveRustLocalImport(fromFile: string, source: string, fileSet: Set<string>): string | undefined {\n const segments = source.split('::');\n const head = segments.shift();\n // Rust allows repeated `super` segments to ascend several modules; each extra one climbs a\n // directory before the remaining path is probed.\n let extraSuperCount = 0;\n while (head === 'super' && segments[0] === 'super') {\n segments.shift();\n extraSuperCount += 1;\n }\n const modulePath = segments.join('/');\n if (!modulePath) {\n return undefined;\n }\n const fromDirectory = normalizeDirectory(path.dirname(fromFile));\n const isOwner = rustOwnerFileNames.has(path.basename(fromFile));\n const stemDirectory = path.join(fromDirectory, path.basename(fromFile, '.rs'));\n\n let baseDirectories: string[];\n if (head === 'self') {\n // Children of a mod.rs/lib.rs/main.rs module are siblings; children of `a.rs` live in `a/`.\n baseDirectories = isOwner ? [fromDirectory] : [stemDirectory, fromDirectory];\n } else if (head === 'super') {\n baseDirectories = (\n isOwner\n ? [normalizeDirectory(path.dirname(fromDirectory)), fromDirectory]\n : [fromDirectory, normalizeDirectory(path.dirname(fromDirectory))]\n ).map((directory) => ascendDirectory(directory, extraSuperCount));\n } else {\n baseDirectories = rustCrateRootCandidates(fromDirectory, fileSet);\n }\n\n // The leaf may be an item rather than a module (`use crate::b::helper` keeps `helper`), so the\n // parent module path is probed as a fallback for each base directory.\n const modulePaths = [modulePath, segments.slice(0, -1).join('/')].filter(Boolean);\n for (const base of baseDirectories) {\n for (const candidateModulePath of modulePaths) {\n for (const candidate of [\n path.join(base, `${candidateModulePath}.rs`),\n path.join(base, candidateModulePath, 'mod.rs'),\n ]) {\n if (candidate !== fromFile && fileSet.has(candidate)) {\n return candidate;\n }\n }\n }\n }\n return undefined;\n}\n\n/** Ancestor directories of `fromDirectory` holding a crate root file, else all ancestors (nearest first). */\nfunction rustCrateRootCandidates(fromDirectory: string, fileSet: Set<string>): string[] {\n const ancestors: string[] = [];\n let current = fromDirectory;\n for (;;) {\n ancestors.push(current);\n if (current === '') {\n break;\n }\n current = normalizeDirectory(path.dirname(current));\n }\n const withRootFile = ancestors.filter(\n (directory) => fileSet.has(path.join(directory, 'lib.rs')) || fileSet.has(path.join(directory, 'main.rs'))\n );\n return withRootFile.length > 0 ? withRootFile : ancestors;\n}\n\nfunction ascendDirectory(directory: string, levels: number): string {\n let current = directory;\n for (let index = 0; index < levels && current !== ''; index += 1) {\n current = normalizeDirectory(path.dirname(current));\n }\n return current;\n}\n\nfunction normalizeDirectory(directory: string): string {\n return directory === '.' ? '' : directory;\n}\n\ninterface JavaClassLocation {\n file: string;\n /** `<packagePath>/<ClassName>` from the file's declared package, when one was parsed. */\n qualifiedPath?: string;\n}\n\n/** Indexes scanned `.java` files by class name so dotted imports resolve without scanning all files. */\nfunction buildJavaFileIndex(files: SourceFile[]): Map<string, JavaClassLocation[]> {\n const index = new Map<string, JavaClassLocation[]>();\n for (const { relativeFile, metrics } of files) {\n if (fileExtension(relativeFile) !== '.java') {\n continue;\n }\n const fileClassName = path.basename(relativeFile, path.extname(relativeFile));\n const addLocation = (className: string, qualifiedPath: string | undefined): void => {\n const locations = index.get(className) ?? [];\n if (!locations.some((location) => location.file === relativeFile && location.qualifiedPath === qualifiedPath)) {\n locations.push({ file: relativeFile, qualifiedPath });\n index.set(className, locations);\n }\n };\n // Declarations carry the package as `com.alpha::ServiceThing`, giving each top-level type's\n // true qualified path regardless of directory layout — including package-private types whose\n // names differ from the file's.\n let sawFileClass = false;\n for (const declaration of metrics.module.declarations) {\n const separatorIndex = declaration.name.lastIndexOf('::');\n const className = separatorIndex === -1 ? declaration.name : declaration.name.slice(separatorIndex + 2);\n const packagePath =\n separatorIndex === -1 ? undefined : declaration.name.slice(0, separatorIndex).replaceAll('.', '/');\n addLocation(className, packagePath ? `${packagePath}/${className}` : undefined);\n sawFileClass ||= className === fileClassName;\n }\n // Filename fallback for files whose public type produced no declaration.\n if (!sawFileClass) {\n addLocation(fileClassName, undefined);\n }\n }\n for (const locations of index.values()) {\n locations.sort((left, right) => left.file.localeCompare(right.file));\n }\n return index;\n}\n\n/**\n * Resolves a Java dotted import (`com.example.Helper`) to a scanned file whose path ends with the\n * package path, tolerating source-root prefixes (`src/main/java/...`). Static imports fall back to\n * the enclosing class once the member segment fails to match; wildcard imports name a package, not\n * a file, and stay unresolved. Ambiguity across source roots resolves to the lexicographically\n * first path for determinism.\n */\nfunction resolveJavaImport(source: string, javaFileIndex: Map<string, JavaClassLocation[]>): string | undefined {\n if (source.endsWith('.*')) {\n return undefined;\n }\n // Trailing segments are dropped one by one so nested types and static members resolve to their\n // enclosing top-level class file (`com.example.Outer.Inner.CONST` -> `Outer.java`).\n const segments = source.split('.');\n for (let end = segments.length; end >= 1; end -= 1) {\n const candidateSegments = segments.slice(0, end);\n const className = candidateSegments.at(-1);\n if (!className) {\n continue;\n }\n const relativeStem = candidateSegments.join('/');\n const locations = javaFileIndex.get(className) ?? [];\n // The declared package is authoritative: matching it exactly stops `import java.util.List`\n // from binding to a project class in package `util` just because the Maven layout contains a\n // `java/` path segment.\n const packageMatch = locations.find((location) => location.qualifiedPath === relativeStem);\n if (packageMatch) {\n return packageMatch.file;\n }\n // Files without a parsed package fall back to path-suffix matching, tolerating source-root\n // prefixes (`src/main/java/...`). Indexed paths use the platform separator, so normalize.\n const suffixMatch = locations.find((location) => {\n if (location.qualifiedPath) {\n return false;\n }\n // Class names stay case-sensitive; only the extension is normalized (`B.JAVA` matches `B`).\n const normalized = location.file.replaceAll('\\\\', '/');\n const stem = normalized.slice(0, normalized.length - path.extname(normalized).length);\n return stem === relativeStem || stem.endsWith(`/${relativeStem}`);\n });\n if (suffixMatch) {\n return suffixMatch.file;\n }\n }\n return undefined;\n}\n\n/** Extensions are matched lowercased so `MAIN.CPP` or `.C` files use their language's rules. */\nfunction fileExtension(file: string): string {\n return path.extname(file).toLowerCase();\n}\n\nfunction localImportBases(fromFile: string, source: string): string[] {\n const fromDirectory = path.dirname(fromFile);\n if (isPathRelativeImport(source)) {\n return [path.normalize(path.join(fromDirectory, source))];\n }\n\n const match = /^(?<dots>\\.+)(?<module>.*)$/u.exec(source);\n const dots = match?.groups?.dots;\n const moduleName = match?.groups?.module;\n if (!dots || moduleName === undefined) {\n return [];\n }\n\n let baseDirectory = fromDirectory;\n for (let index = 1; index < dots.length; index += 1) {\n baseDirectory = path.dirname(baseDirectory);\n }\n\n const modulePath = moduleName.replaceAll('.', path.sep);\n return [path.normalize(path.join(baseDirectory, modulePath))];\n}\n\nfunction isPathRelativeImport(source: string): boolean {\n return source.startsWith('./') || source.startsWith('../') || source.includes('/');\n}\n\nfunction measureTransitiveDependencyCount(file: string, graph: Map<string, Set<string>>): number {\n // Seed with the starting file so dependency cycles do not count it as its own dependency.\n const visited = new Set<string>([file]);\n const pending = [...(graph.get(file) ?? [])];\n while (pending.length > 0) {\n const current = pending.pop();\n if (!current || visited.has(current)) {\n continue;\n }\n visited.add(current);\n pending.push(...(graph.get(current) ?? []));\n }\n return visited.size - 1;\n}\n\nfunction measureDuplicateSymbolGroups(files: SourceFile[]): DuplicateSymbolGroup[] {\n const declarationsBySymbol = new Map<string, DuplicateSymbolDeclaration[]>();\n for (const file of files) {\n for (const declaration of file.metrics.module.declarations) {\n if (!isDuplicateSymbolCandidate(declaration)) {\n continue;\n }\n const declarations = declarationsBySymbol.get(declaration.name) ?? [];\n declarations.push(toDuplicateSymbolDeclaration(file.relativeFile, declaration));\n declarationsBySymbol.set(declaration.name, declarations);\n }\n }\n return [...declarationsBySymbol.entries()]\n .flatMap(([name, declarations]) => {\n const duplicateFiles = [...new Set(declarations.map((declaration) => declaration.file))].toSorted();\n return duplicateFiles.length > 1\n ? [{ declarations: declarations.toSorted(compareDuplicateDeclarations), files: duplicateFiles, name }]\n : [];\n })\n .toSorted((left, right) => right.files.length - left.files.length || left.name.localeCompare(right.name));\n}\n\nfunction isDuplicateSymbolCandidate(declaration: DeclarationMetrics): boolean {\n // The threshold applies to the local name so qualification cannot let short names sneak past\n // it; the codebase emits both `::` (packages/namespaces/modules, C++ out-of-line names) and `.`\n // (Go `Receiver.Method`) separators.\n const localName = declaration.name.split(/::|\\./u).at(-1) ?? declaration.name;\n return localName.length >= duplicateSymbolNameLengthThreshold;\n}\n\nfunction toDuplicateSymbolDeclaration(file: string, declaration: DeclarationMetrics): DuplicateSymbolDeclaration {\n return { file, line: declaration.startLine };\n}\n\nfunction compareDuplicateDeclarations(left: DuplicateSymbolDeclaration, right: DuplicateSymbolDeclaration): number {\n return left.file.localeCompare(right.file) || left.line - right.line;\n}\n\nfunction measureDuplicateGroupCountByFile(groups: DuplicateSymbolGroup[]): Map<string, number> {\n const counts = new Map<string, number>();\n for (const group of groups) {\n for (const file of group.files) {\n counts.set(file, (counts.get(file) ?? 0) + 1);\n }\n }\n return counts;\n}\n\nfunction measureStructuralCoordination(metrics: CodeMetrics): StructuralCoordinationMetrics {\n const asyncBoundaryCount = metrics.syntaxFeatures.awaitExpressionCount;\n const exceptionHandlingCount = metrics.syntaxFeatures.tryStatementCount + metrics.syntaxFeatures.throwStatementCount;\n const stateMutationScore = metrics.syntaxFeatures.mutableBindingCount + metrics.syntaxFeatures.assignmentCount;\n const branchingScore =\n metrics.cyclomaticComplexity +\n metrics.syntaxFeatures.loopStatementCount +\n metrics.syntaxFeatures.returnStatementCount;\n const moduleInteractionScore =\n metrics.callGraph.callCount + metrics.callGraph.internalEdgeCount * 2 + metrics.callGraph.maxCallDepth * 3;\n\n return {\n asyncBoundaryCount,\n branchingScore,\n exceptionHandlingCount,\n moduleInteractionScore,\n score:\n asyncBoundaryCount * 2 +\n exceptionHandlingCount * 3 +\n stateMutationScore +\n branchingScore +\n moduleInteractionScore,\n stateMutationScore,\n };\n}\n\n/**\n * Java imports are dotted package paths, so per-file coupling classifies them as external even\n * when they resolve inside the scanned project; the dependency graph corrects both feature tags.\n */\nfunction measureStructuralFeatureGroups(\n metrics: CodeMetrics,\n directLocalDependencyCount: number,\n nonRelativeLocalImportCount: number\n): string[] {\n const groups = new Set<string>();\n addGroup(groups, 'class-shapes', metrics.classCount > 0);\n addGroup(groups, 'control-flow', metrics.cognitiveComplexity > 0);\n addGroup(groups, 'external-dependencies', metrics.coupling.externalImportCount - nonRelativeLocalImportCount > 0);\n addGroup(groups, 'functions', metrics.functionCount > 0);\n addGroup(groups, 'local-dependencies', metrics.coupling.relativeImportCount > 0 || directLocalDependencyCount > 0);\n addGroup(groups, 'module-api', metrics.coupling.exportCount > 0 || metrics.module.declarations.length > 0);\n addGroup(\n groups,\n 'state-mutation',\n metrics.syntaxFeatures.mutableBindingCount + metrics.syntaxFeatures.assignmentCount > 0\n );\n addGroup(groups, 'type-shapes', hasTypeShapeMetrics(metrics));\n return [...groups].toSorted();\n}\n\nfunction addGroup(groups: Set<string>, group: string, condition: boolean): void {\n if (condition) {\n groups.add(group);\n }\n}\n\nfunction hasTypeShapeMetrics(metrics: CodeMetrics): boolean {\n return (\n metrics.typeComplexity.typeAnnotationCount +\n metrics.typeComplexity.typeAliasCount +\n metrics.typeComplexity.interfaceCount +\n metrics.typeComplexity.genericParameterCount +\n metrics.typeComplexity.unionTypeCount +\n metrics.typeComplexity.intersectionTypeCount +\n metrics.typeComplexity.conditionalTypeCount >\n 0\n );\n}\n\nfunction maxFileMetric(metrics: ArchitectureFileMetrics[], key: keyof ArchitectureFileMetrics): number {\n return Math.max(\n 0,\n ...metrics.map((metric) => metric[key]).filter((value): value is number => typeof value === 'number')\n );\n}\n"],"mappings":"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"}