code-gauge 1.11.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -21
- package/dist/_virtual/_rolldown/runtime.cjs +1 -1
- package/dist/cli.cjs +3 -3
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/cliConfig.cjs +1 -1
- package/dist/cliConfig.cjs.map +1 -1
- package/dist/cliConfig.d.ts +9 -0
- package/dist/cliConfig.js +1 -1
- package/dist/cliConfig.js.map +1 -1
- package/dist/crossFileDuplication.cjs +2 -0
- package/dist/crossFileDuplication.cjs.map +1 -0
- package/dist/crossFileDuplication.d.ts +31 -0
- package/dist/crossFileDuplication.js +2 -0
- package/dist/crossFileDuplication.js.map +1 -0
- package/dist/duplicateSelection.cjs +2 -0
- package/dist/duplicateSelection.cjs.map +1 -0
- package/dist/duplicateSelection.d.ts +25 -0
- package/dist/duplicateSelection.js +2 -0
- package/dist/duplicateSelection.js.map +1 -0
- package/dist/duplication.cjs +1 -1
- package/dist/duplication.cjs.map +1 -1
- package/dist/duplication.d.ts +25 -5
- package/dist/duplication.js +1 -1
- package/dist/duplication.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -1
- package/dist/languages.cjs +1 -1
- package/dist/languages.cjs.map +1 -1
- package/dist/languages.js +1 -1
- package/dist/languages.js.map +1 -1
- package/dist/metrics.cjs +2 -2
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.ts +8 -0
- package/dist/metrics.js +2 -2
- package/dist/metrics.js.map +1 -1
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/ncss.cjs +2 -0
- package/dist/ncss.cjs.map +1 -0
- package/dist/ncss.d.ts +11 -0
- package/dist/ncss.js +2 -0
- package/dist/ncss.js.map +1 -0
- package/dist/types.d.ts +40 -1
- package/dist/typescriptProject.cjs.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nativeMetrics.cjs","names":["defaultLanguages"],"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,
|
|
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 +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,
|
|
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"}
|
package/dist/ncss.cjs
ADDED
|
@@ -0,0 +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;
|
|
2
|
+
//# sourceMappingURL=ncss.cjs.map
|
|
@@ -0,0 +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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"}
|
package/dist/ncss.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
import type { LanguageDefinition } from './types.js';
|
|
3
|
+
export declare const commentNodeTypes: Set<string>;
|
|
4
|
+
/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */
|
|
5
|
+
export declare function invalidateNcssSetsCache(language: LanguageDefinition): void;
|
|
6
|
+
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;
|
package/dist/ncss.js
ADDED
|
@@ -0,0 +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};
|
|
2
|
+
//# sourceMappingURL=ncss.js.map
|
package/dist/ncss.js.map
ADDED
|
@@ -0,0 +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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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -9,10 +9,30 @@ export interface LanguageDefinition {
|
|
|
9
9
|
classNodeTypes?: readonly string[];
|
|
10
10
|
decisionNodeTypes?: readonly string[];
|
|
11
11
|
nestingNodeTypes?: readonly string[];
|
|
12
|
+
/** Node types that each count as one non-commenting source statement (NCSS). */
|
|
13
|
+
ncssNodeTypes?: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* Node types whose direct named children count as statements even without a dedicated statement
|
|
16
|
+
* node type (expression-oriented grammars: Ruby bodies, Rust trailing block expressions).
|
|
17
|
+
*/
|
|
18
|
+
ncssContainerNodeTypes?: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/** Detection settings for within-file and cross-file duplication. */
|
|
21
|
+
export interface DuplicationOptions {
|
|
22
|
+
/** Minimum normalized token count for a region to be considered for duplication (default 40). */
|
|
23
|
+
minTokens?: number;
|
|
24
|
+
/**
|
|
25
|
+
* 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.
|
|
28
|
+
*/
|
|
29
|
+
maxGapTokens?: number;
|
|
12
30
|
}
|
|
13
31
|
export interface MeasureOptions {
|
|
14
32
|
language: LanguageName;
|
|
15
33
|
includeSyntaxTree?: boolean;
|
|
34
|
+
/** Duplication detection settings; non-default values disable the native backend for the call. */
|
|
35
|
+
duplication?: DuplicationOptions;
|
|
16
36
|
}
|
|
17
37
|
export interface LineMetrics {
|
|
18
38
|
total: number;
|
|
@@ -35,6 +55,13 @@ export interface HalsteadMetrics {
|
|
|
35
55
|
}
|
|
36
56
|
export interface FunctionMetrics {
|
|
37
57
|
name?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The tree-sitter node type of the function (e.g. `method_declaration`, `arrow_function`,
|
|
60
|
+
* `lambda_expression`), letting consumers distinguish declared methods from lambdas — e.g. to
|
|
61
|
+
* sum per-function metrics without double-counting lambda content already attributed to the
|
|
62
|
+
* enclosing function.
|
|
63
|
+
*/
|
|
64
|
+
nodeType: string;
|
|
38
65
|
startLine: number;
|
|
39
66
|
startColumn: number;
|
|
40
67
|
endLine: number;
|
|
@@ -42,6 +69,12 @@ export interface FunctionMetrics {
|
|
|
42
69
|
cyclomaticComplexity: number;
|
|
43
70
|
cognitiveComplexity: number;
|
|
44
71
|
nestingDepth: number;
|
|
72
|
+
/**
|
|
73
|
+
* Non-commenting source statements in the function, PMD-style: the declaration itself, each
|
|
74
|
+
* statement, and each `else`/`case` label/`catch`/`finally` clause count 1; nested function and
|
|
75
|
+
* class bodies are included, so summing over functions counts shared regions more than once.
|
|
76
|
+
*/
|
|
77
|
+
ncss: number;
|
|
45
78
|
callCount: number;
|
|
46
79
|
uniqueCalleeCount: number;
|
|
47
80
|
fanIn: number;
|
|
@@ -95,7 +128,11 @@ export interface SyntaxFeatureMetrics {
|
|
|
95
128
|
* consistently renamed copies match. Distinct from cross-file duplicate symbol names.
|
|
96
129
|
*/
|
|
97
130
|
export interface DuplicationMetrics {
|
|
98
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Number of redundant (extra) duplicated regions: sum over groups of (groupSize - 1) times the
|
|
133
|
+
* matched fragments per occurrence, so a gapped (merged) clone counts like its unmerged
|
|
134
|
+
* fragments and threshold semantics do not shift with merging.
|
|
135
|
+
*/
|
|
99
136
|
duplicateBlockCount: number;
|
|
100
137
|
/** Number of distinct normalized token sequences that appear more than once. */
|
|
101
138
|
duplicateBlockGroupCount: number;
|
|
@@ -140,6 +177,8 @@ export interface CodeMetrics {
|
|
|
140
177
|
cognitiveComplexity: number;
|
|
141
178
|
maxCognitiveComplexity: number;
|
|
142
179
|
nestingDepth: number;
|
|
180
|
+
/** Non-commenting source statements in the whole file; every statement counts exactly once. */
|
|
181
|
+
ncssCount: number;
|
|
143
182
|
callGraph: CallGraphMetrics;
|
|
144
183
|
coupling: CouplingMetrics;
|
|
145
184
|
module: ModuleMetrics;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescriptProject.cjs","names":["API","path","SignatureKind","SyntaxKind"],"sources":["../src/typescriptProject.ts"],"sourcesContent":["import { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport { API, SignatureKind, type Checker, type Type } from 'typescript/unstable/async';\nimport { getTokenPosOfNode, SyntaxKind, type Node, type SourceFile } from 'typescript/unstable/ast';\n\nexport interface ReactComponentFunctionMetric {\n file: string;\n name?: string;\n startLine: number;\n startColumn: number;\n}\n\nexport interface TypeScriptProjectMetrics {\n callExpressionCount: number;\n configDiagnosticCount: number;\n configFile: string;\n declarationDiagnosticCount: number;\n diagnosticFileCount: number;\n measuredRootFileCount: number;\n projectCount: number;\n reactComponentFunctions: ReactComponentFunctionMetric[];\n resolvedCallExpressionCount: number;\n resolvedCallExpressionRatio: number;\n rootFileCount: number;\n semanticDiagnosticCount: number;\n suggestionDiagnosticCount: number;\n syntacticDiagnosticCount: number;\n unresolvedCallExpressionCount: number;\n}\n\ninterface DiagnosticLike {\n fileName?: string;\n}\n\ninterface ReactComponentCandidateMetric extends Omit<ReactComponentFunctionMetric, 'file'> {\n implementationKey: string;\n}\n\nexport async function measureTypeScriptProject(\n configFile: string,\n measuredFiles: readonly string[] = []\n): Promise<TypeScriptProjectMetrics> {\n const api = new API({ cwd: process.cwd() });\n try {\n const snapshot = await api.updateSnapshot({ openProject: configFile });\n const projects = snapshot.getProjects();\n const measuredFileByCanonicalFile = await mapMeasuredFilesByCanonicalFile(measuredFiles);\n const measuredFileSet = new Set(measuredFileByCanonicalFile.keys());\n const diagnosticFiles = new Set<string>();\n const totals = {\n callExpressionCount: 0,\n configDiagnosticCount: 0,\n declarationDiagnosticCount: 0,\n measuredRootFileCount: 0,\n resolvedCallExpressionCount: 0,\n rootFileCount: 0,\n semanticDiagnosticCount: 0,\n suggestionDiagnosticCount: 0,\n syntacticDiagnosticCount: 0,\n };\n const reactComponentFunctions: ReactComponentFunctionMetric[] = [];\n const analyzedFiles = new Set<string>();\n\n for (const project of projects) {\n const projectRootFiles = await Promise.all(\n project.rootFiles.map(async (file) => ({ canonicalFile: await canonicalizeFile(file), file }))\n );\n totals.rootFileCount += projectRootFiles.length;\n totals.measuredRootFileCount += projectRootFiles.filter(({ canonicalFile }) =>\n measuredFileSet.has(canonicalFile)\n ).length;\n\n const syntacticDiagnostics = await project.program.getSyntacticDiagnostics();\n const semanticDiagnostics = await project.program.getSemanticDiagnostics();\n const suggestionDiagnostics = await project.program.getSuggestionDiagnostics();\n const declarationDiagnostics = await project.program.getDeclarationDiagnostics();\n const configDiagnostics = await project.program.getConfigFileParsingDiagnostics();\n totals.syntacticDiagnosticCount += syntacticDiagnostics.length;\n totals.semanticDiagnosticCount += semanticDiagnostics.length;\n totals.suggestionDiagnosticCount += suggestionDiagnostics.length;\n totals.declarationDiagnosticCount += declarationDiagnostics.length;\n totals.configDiagnosticCount += configDiagnostics.length;\n addDiagnosticFiles(diagnosticFiles, [\n ...syntacticDiagnostics,\n ...semanticDiagnostics,\n ...suggestionDiagnostics,\n ...declarationDiagnostics,\n ...configDiagnostics,\n ]);\n\n const projectAnalysisFiles =\n measuredFileSet.size === 0\n ? projectRootFiles\n : [...measuredFileByCanonicalFile.entries()].map(([canonicalFile, file]) => ({ canonicalFile, file }));\n const configDirectory = path.dirname(project.configFileName);\n const canonicalConfigDirectory = await canonicalizeFile(configDirectory);\n for (const { canonicalFile, file } of projectAnalysisFiles) {\n if (analyzedFiles.has(canonicalFile)) {\n continue;\n }\n const sourceFile = await getProjectSourceFile(\n project,\n file,\n canonicalFile,\n configDirectory,\n canonicalConfigDirectory\n );\n if (!sourceFile) {\n continue;\n }\n analyzedFiles.add(canonicalFile);\n const callExpressions = collectCallExpressions(sourceFile);\n totals.callExpressionCount += callExpressions.length;\n const fileReactComponentFunctions = await collectReactComponentFunctions(sourceFile, project.checker);\n reactComponentFunctions.push(\n ...fileReactComponentFunctions.map((component) => ({\n ...component,\n file: measuredFileByCanonicalFile.get(canonicalFile) ?? file,\n }))\n );\n for (const callExpression of callExpressions) {\n if (await project.checker.getResolvedSignature(callExpression)) {\n totals.resolvedCallExpressionCount += 1;\n }\n }\n }\n }\n\n const unresolvedCallExpressionCount = totals.callExpressionCount - totals.resolvedCallExpressionCount;\n return {\n ...totals,\n configFile,\n diagnosticFileCount: diagnosticFiles.size,\n projectCount: projects.length,\n reactComponentFunctions,\n unresolvedCallExpressionCount,\n resolvedCallExpressionRatio:\n totals.callExpressionCount === 0 ? 0 : totals.resolvedCallExpressionCount / totals.callExpressionCount,\n };\n } finally {\n await api.close();\n }\n}\n\nasync function getProjectSourceFile(\n project: { configFileName: string; program: { getSourceFile: (file: string) => Promise<SourceFile | undefined> } },\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): Promise<SourceFile | undefined> {\n for (const candidate of getProjectFileCandidates(file, canonicalFile, configDirectory, canonicalConfigDirectory)) {\n const sourceFile = await project.program.getSourceFile(candidate);\n if (sourceFile) {\n return sourceFile;\n }\n }\n return undefined;\n}\n\nfunction getProjectFileCandidates(\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): string[] {\n const candidates = new Set([file, canonicalFile]);\n if (canonicalFile.startsWith(`${canonicalConfigDirectory}${path.sep}`)) {\n candidates.add(path.join(configDirectory, path.relative(canonicalConfigDirectory, canonicalFile)));\n }\n return [...candidates];\n}\n\nasync function mapMeasuredFilesByCanonicalFile(files: readonly string[]): Promise<Map<string, string>> {\n const measuredFileByCanonicalFile = new Map<string, string>();\n for (const file of files) {\n measuredFileByCanonicalFile.set(await canonicalizeFile(file), file);\n }\n return measuredFileByCanonicalFile;\n}\n\nasync function canonicalizeFile(file: string): Promise<string> {\n try {\n return await realpath(file);\n } catch {\n return file;\n }\n}\n\nasync function collectReactComponentFunctions(\n sourceFile: SourceFile,\n checker: Checker\n): Promise<Omit<ReactComponentFunctionMetric, 'file'>[]> {\n const components: ReactComponentCandidateMetric[] = [];\n const candidates = collectReactComponentCandidates(sourceFile);\n const hasReactReference = containsReactModuleReference(sourceFile.text);\n for (const candidate of candidates) {\n if (!(await isReactComponentCandidate(candidate, checker, hasReactReference))) {\n continue;\n }\n const name = findNameNode(candidate);\n const functionNode = findComponentFunctionNode(candidate);\n const startOffset = findFunctionStartPosition(sourceFile.text, functionNode, name, sourceFile);\n const startPosition = positionToLineColumn(sourceFile.text, startOffset);\n components.push({\n implementationKey: `${functionNode.pos}:${functionNode.end}`,\n name: name ? findCandidateName(name) : undefined,\n startColumn: startPosition.column,\n startLine: startPosition.line,\n });\n }\n return dedupeReactComponentFunctions(components);\n}\n\nfunction collectReactComponentCandidates(root: Node): Node[] {\n const candidates: Node[] = [];\n visitNode(root, (node) => {\n if (isFunctionLikeCandidate(node) || isVariableFunctionCandidate(node)) {\n candidates.push(node);\n }\n });\n return candidates;\n}\n\nasync function isReactComponentCandidate(node: Node, checker: Checker, hasReactReference: boolean): Promise<boolean> {\n const type = await checker.getTypeAtLocation(node);\n if (!type) {\n return false;\n }\n\n const name = findNameNode(node);\n const componentName = name ? findCandidateName(name) : undefined;\n if (hasReactReference && (await hasReactComponentTypeName(type, node, checker))) {\n return true;\n }\n\n const namedType = name ? await checker.getTypeAtLocation(name) : undefined;\n if (hasReactReference && namedType && (await hasReactComponentTypeName(namedType, node, checker))) {\n return true;\n }\n\n if (!isUppercaseComponentName(componentName)) {\n return false;\n }\n\n return (\n (await hasReactRenderableReturnType(type, node, checker)) ||\n Boolean(namedType && (await hasReactRenderableReturnType(namedType, node, checker)))\n );\n}\n\nfunction containsReactModuleReference(text: string): boolean {\n return /\\bfrom\\s+['\"]react['\"]|\\bimport\\s+['\"]react['\"]|\\brequire\\(\\s*['\"]react['\"]\\s*\\)/u.test(text);\n}\n\nasync function hasReactRenderableReturnType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const signatures = await checker.getSignaturesOfType(type, SignatureKind.Call);\n for (const signature of signatures) {\n const returnType = await checker.getReturnTypeOfSignature(signature);\n if (returnType && (await isReactRenderableType(returnType, node, checker))) {\n return true;\n }\n }\n return false;\n}\n\nasync function hasReactComponentTypeName(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\\b/u.test(\n typeName\n );\n}\n\nasync function isReactRenderableType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:JSX\\.Element|React\\.JSX\\.Element|ReactElement|ReactNode)\\b/u.test(typeName);\n}\n\nfunction isFunctionLikeCandidate(node: Node): boolean {\n if (node.kind === SyntaxKind.FunctionDeclaration && !getNodeProperty(node, 'body')) {\n return false;\n }\n\n return (\n node.kind === SyntaxKind.FunctionDeclaration ||\n node.kind === SyntaxKind.FunctionExpression ||\n node.kind === SyntaxKind.ArrowFunction\n );\n}\n\nfunction isVariableFunctionCandidate(node: Node): boolean {\n if (node.kind !== SyntaxKind.VariableDeclaration) {\n return false;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n return unwrappedInitializer\n ? isFunctionLikeCandidate(unwrappedInitializer) || unwrappedInitializer.kind === SyntaxKind.CallExpression\n : false;\n}\n\nfunction findComponentFunctionNode(node: Node): Node {\n if (isFunctionLikeCandidate(node)) {\n return node;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n if (!unwrappedInitializer) {\n return node;\n }\n if (isFunctionLikeCandidate(unwrappedInitializer)) {\n return unwrappedInitializer;\n }\n if (isComponentImplementationWrapperCall(unwrappedInitializer)) {\n return findComponentImplementationWrapperFunction(unwrappedInitializer) ?? node;\n }\n return node;\n}\n\nfunction isComponentImplementationWrapperCall(node: Node): boolean {\n if (node.kind !== SyntaxKind.CallExpression) {\n return false;\n }\n\n const expression = getNodeProperty(node, 'expression');\n const expressionName = expression ? findCallExpressionName(expression) : undefined;\n return expressionName === 'memo' || expressionName === 'forwardRef';\n}\n\nfunction findCallExpressionName(expression: Node): string | undefined {\n const name = getNodeProperty(expression, 'name');\n return name ? findCandidateName(name) : findCandidateName(expression);\n}\n\nfunction findComponentImplementationWrapperFunction(node: Node): Node | undefined {\n let functionNode: Node | undefined;\n node.forEachChild((child) => {\n const unwrappedChild = unwrapExpression(child);\n if (isFunctionLikeCandidate(unwrappedChild)) {\n functionNode = unwrappedChild;\n } else if (isComponentImplementationWrapperCall(unwrappedChild)) {\n functionNode = findComponentImplementationWrapperFunction(unwrappedChild);\n }\n return functionNode;\n });\n return functionNode;\n}\n\nfunction findFunctionStartPosition(\n text: string,\n functionNode: Node,\n name: Node | undefined,\n sourceFile: SourceFile\n): number {\n const tokenPosition = getTokenPosOfNode(functionNode, sourceFile);\n if (functionNode.kind !== SyntaxKind.FunctionDeclaration || !name) {\n return tokenPosition;\n }\n\n const functionName = findCandidateName(name);\n const namePosition = functionName ? text.indexOf(functionName, tokenPosition) : -1;\n const functionPosition = namePosition >= 0 ? text.lastIndexOf('function', namePosition) : -1;\n if (functionPosition < tokenPosition) {\n return tokenPosition;\n }\n\n const modifierText = text.slice(tokenPosition, functionPosition);\n const asyncMatch = /\\basync\\s*$/u.exec(modifierText);\n return asyncMatch ? tokenPosition + asyncMatch.index : functionPosition;\n}\n\nfunction dedupeReactComponentFunctions(\n components: readonly ReactComponentCandidateMetric[]\n): Omit<ReactComponentFunctionMetric, 'file'>[] {\n const componentByKey = new Map<string, ReactComponentCandidateMetric>();\n for (const component of components) {\n const existingComponent = componentByKey.get(component.implementationKey);\n if (!existingComponent || (!existingComponent.name && component.name)) {\n componentByKey.set(component.implementationKey, component);\n }\n }\n return [...componentByKey.values()].map((component) => ({\n name: component.name,\n startColumn: component.startColumn,\n startLine: component.startLine,\n }));\n}\n\nfunction findCandidateName(name: Node): string | undefined {\n if ('escapedText' in name) {\n return String(name.escapedText);\n }\n if ('text' in name && typeof name.text === 'string') {\n return name.text;\n }\n return undefined;\n}\n\nfunction isUppercaseComponentName(name: string | undefined): boolean {\n return name !== undefined && /^[A-Z]/u.test(name);\n}\n\nfunction findNameNode(node: Node): Node | undefined {\n const name = getNodeProperty(node, 'name');\n if (name) {\n return name;\n }\n\n let currentNode: Node | undefined = node;\n while (currentNode) {\n const parent = getNodeProperty(currentNode, 'parent');\n if (!parent) {\n return undefined;\n }\n if (parent.kind === SyntaxKind.VariableDeclaration) {\n return getNodeProperty(parent, 'name');\n }\n if (!isExpressionWrapperNode(parent)) {\n return undefined;\n }\n currentNode = parent;\n }\n return undefined;\n}\n\nfunction unwrapExpression(node: Node): Node {\n let currentNode = node;\n while (isExpressionWrapperNode(currentNode)) {\n const expression = getNodeProperty(currentNode, 'expression');\n if (!expression) {\n return currentNode;\n }\n currentNode = expression;\n }\n return currentNode;\n}\n\nfunction isExpressionWrapperNode(node: Node): boolean {\n return (\n node.kind === SyntaxKind.ParenthesizedExpression ||\n node.kind === SyntaxKind.AsExpression ||\n node.kind === SyntaxKind.TypeAssertionExpression ||\n node.kind === SyntaxKind.SatisfiesExpression\n );\n}\n\nfunction getNodeProperty(\n node: Node,\n property: 'body' | 'expression' | 'initializer' | 'name' | 'parent'\n): Node | undefined {\n const value = (node as Partial<Record<typeof property, Node>>)[property];\n return isNode(value) ? value : undefined;\n}\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && 'kind' in value && 'pos' in value && 'end' in value;\n}\n\nfunction positionToLineColumn(text: string, position: number): { column: number; line: number } {\n const lines = text.slice(0, position).split('\\n');\n return { column: lines.at(-1)?.length ?? 0, line: lines.length };\n}\n\nfunction collectCallExpressions(root: Node): Node[] {\n const calls: Node[] = [];\n visitNode(root, (node) => {\n if (node.kind === SyntaxKind.CallExpression) {\n calls.push(node);\n }\n });\n return calls;\n}\n\nfunction visitNode(node: Node, visitor: (node: Node) => void): void {\n visitor(node);\n node.forEachChild((child) => {\n visitNode(child, visitor);\n return;\n });\n}\n\nfunction addDiagnosticFiles(files: Set<string>, diagnostics: readonly DiagnosticLike[]): void {\n for (const diagnostic of diagnostics) {\n if (diagnostic.fileName) {\n files.add(diagnostic.fileName);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"typescriptProject.cjs","names":["API","path","realpath","SignatureKind","SyntaxKind","getTokenPosOfNode"],"sources":["../src/typescriptProject.ts"],"sourcesContent":["import { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport { API, SignatureKind, type Checker, type Type } from 'typescript/unstable/async';\nimport { getTokenPosOfNode, SyntaxKind, type Node, type SourceFile } from 'typescript/unstable/ast';\n\nexport interface ReactComponentFunctionMetric {\n file: string;\n name?: string;\n startLine: number;\n startColumn: number;\n}\n\nexport interface TypeScriptProjectMetrics {\n callExpressionCount: number;\n configDiagnosticCount: number;\n configFile: string;\n declarationDiagnosticCount: number;\n diagnosticFileCount: number;\n measuredRootFileCount: number;\n projectCount: number;\n reactComponentFunctions: ReactComponentFunctionMetric[];\n resolvedCallExpressionCount: number;\n resolvedCallExpressionRatio: number;\n rootFileCount: number;\n semanticDiagnosticCount: number;\n suggestionDiagnosticCount: number;\n syntacticDiagnosticCount: number;\n unresolvedCallExpressionCount: number;\n}\n\ninterface DiagnosticLike {\n fileName?: string;\n}\n\ninterface ReactComponentCandidateMetric extends Omit<ReactComponentFunctionMetric, 'file'> {\n implementationKey: string;\n}\n\nexport async function measureTypeScriptProject(\n configFile: string,\n measuredFiles: readonly string[] = []\n): Promise<TypeScriptProjectMetrics> {\n const api = new API({ cwd: process.cwd() });\n try {\n const snapshot = await api.updateSnapshot({ openProject: configFile });\n const projects = snapshot.getProjects();\n const measuredFileByCanonicalFile = await mapMeasuredFilesByCanonicalFile(measuredFiles);\n const measuredFileSet = new Set(measuredFileByCanonicalFile.keys());\n const diagnosticFiles = new Set<string>();\n const totals = {\n callExpressionCount: 0,\n configDiagnosticCount: 0,\n declarationDiagnosticCount: 0,\n measuredRootFileCount: 0,\n resolvedCallExpressionCount: 0,\n rootFileCount: 0,\n semanticDiagnosticCount: 0,\n suggestionDiagnosticCount: 0,\n syntacticDiagnosticCount: 0,\n };\n const reactComponentFunctions: ReactComponentFunctionMetric[] = [];\n const analyzedFiles = new Set<string>();\n\n for (const project of projects) {\n const projectRootFiles = await Promise.all(\n project.rootFiles.map(async (file) => ({ canonicalFile: await canonicalizeFile(file), file }))\n );\n totals.rootFileCount += projectRootFiles.length;\n totals.measuredRootFileCount += projectRootFiles.filter(({ canonicalFile }) =>\n measuredFileSet.has(canonicalFile)\n ).length;\n\n const syntacticDiagnostics = await project.program.getSyntacticDiagnostics();\n const semanticDiagnostics = await project.program.getSemanticDiagnostics();\n const suggestionDiagnostics = await project.program.getSuggestionDiagnostics();\n const declarationDiagnostics = await project.program.getDeclarationDiagnostics();\n const configDiagnostics = await project.program.getConfigFileParsingDiagnostics();\n totals.syntacticDiagnosticCount += syntacticDiagnostics.length;\n totals.semanticDiagnosticCount += semanticDiagnostics.length;\n totals.suggestionDiagnosticCount += suggestionDiagnostics.length;\n totals.declarationDiagnosticCount += declarationDiagnostics.length;\n totals.configDiagnosticCount += configDiagnostics.length;\n addDiagnosticFiles(diagnosticFiles, [\n ...syntacticDiagnostics,\n ...semanticDiagnostics,\n ...suggestionDiagnostics,\n ...declarationDiagnostics,\n ...configDiagnostics,\n ]);\n\n const projectAnalysisFiles =\n measuredFileSet.size === 0\n ? projectRootFiles\n : [...measuredFileByCanonicalFile.entries()].map(([canonicalFile, file]) => ({ canonicalFile, file }));\n const configDirectory = path.dirname(project.configFileName);\n const canonicalConfigDirectory = await canonicalizeFile(configDirectory);\n for (const { canonicalFile, file } of projectAnalysisFiles) {\n if (analyzedFiles.has(canonicalFile)) {\n continue;\n }\n const sourceFile = await getProjectSourceFile(\n project,\n file,\n canonicalFile,\n configDirectory,\n canonicalConfigDirectory\n );\n if (!sourceFile) {\n continue;\n }\n analyzedFiles.add(canonicalFile);\n const callExpressions = collectCallExpressions(sourceFile);\n totals.callExpressionCount += callExpressions.length;\n const fileReactComponentFunctions = await collectReactComponentFunctions(sourceFile, project.checker);\n reactComponentFunctions.push(\n ...fileReactComponentFunctions.map((component) => ({\n ...component,\n file: measuredFileByCanonicalFile.get(canonicalFile) ?? file,\n }))\n );\n for (const callExpression of callExpressions) {\n if (await project.checker.getResolvedSignature(callExpression)) {\n totals.resolvedCallExpressionCount += 1;\n }\n }\n }\n }\n\n const unresolvedCallExpressionCount = totals.callExpressionCount - totals.resolvedCallExpressionCount;\n return {\n ...totals,\n configFile,\n diagnosticFileCount: diagnosticFiles.size,\n projectCount: projects.length,\n reactComponentFunctions,\n unresolvedCallExpressionCount,\n resolvedCallExpressionRatio:\n totals.callExpressionCount === 0 ? 0 : totals.resolvedCallExpressionCount / totals.callExpressionCount,\n };\n } finally {\n await api.close();\n }\n}\n\nasync function getProjectSourceFile(\n project: { configFileName: string; program: { getSourceFile: (file: string) => Promise<SourceFile | undefined> } },\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): Promise<SourceFile | undefined> {\n for (const candidate of getProjectFileCandidates(file, canonicalFile, configDirectory, canonicalConfigDirectory)) {\n const sourceFile = await project.program.getSourceFile(candidate);\n if (sourceFile) {\n return sourceFile;\n }\n }\n return undefined;\n}\n\nfunction getProjectFileCandidates(\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): string[] {\n const candidates = new Set([file, canonicalFile]);\n if (canonicalFile.startsWith(`${canonicalConfigDirectory}${path.sep}`)) {\n candidates.add(path.join(configDirectory, path.relative(canonicalConfigDirectory, canonicalFile)));\n }\n return [...candidates];\n}\n\nasync function mapMeasuredFilesByCanonicalFile(files: readonly string[]): Promise<Map<string, string>> {\n const measuredFileByCanonicalFile = new Map<string, string>();\n for (const file of files) {\n measuredFileByCanonicalFile.set(await canonicalizeFile(file), file);\n }\n return measuredFileByCanonicalFile;\n}\n\nasync function canonicalizeFile(file: string): Promise<string> {\n try {\n return await realpath(file);\n } catch {\n return file;\n }\n}\n\nasync function collectReactComponentFunctions(\n sourceFile: SourceFile,\n checker: Checker\n): Promise<Omit<ReactComponentFunctionMetric, 'file'>[]> {\n const components: ReactComponentCandidateMetric[] = [];\n const candidates = collectReactComponentCandidates(sourceFile);\n const hasReactReference = containsReactModuleReference(sourceFile.text);\n for (const candidate of candidates) {\n if (!(await isReactComponentCandidate(candidate, checker, hasReactReference))) {\n continue;\n }\n const name = findNameNode(candidate);\n const functionNode = findComponentFunctionNode(candidate);\n const startOffset = findFunctionStartPosition(sourceFile.text, functionNode, name, sourceFile);\n const startPosition = positionToLineColumn(sourceFile.text, startOffset);\n components.push({\n implementationKey: `${functionNode.pos}:${functionNode.end}`,\n name: name ? findCandidateName(name) : undefined,\n startColumn: startPosition.column,\n startLine: startPosition.line,\n });\n }\n return dedupeReactComponentFunctions(components);\n}\n\nfunction collectReactComponentCandidates(root: Node): Node[] {\n const candidates: Node[] = [];\n visitNode(root, (node) => {\n if (isFunctionLikeCandidate(node) || isVariableFunctionCandidate(node)) {\n candidates.push(node);\n }\n });\n return candidates;\n}\n\nasync function isReactComponentCandidate(node: Node, checker: Checker, hasReactReference: boolean): Promise<boolean> {\n const type = await checker.getTypeAtLocation(node);\n if (!type) {\n return false;\n }\n\n const name = findNameNode(node);\n const componentName = name ? findCandidateName(name) : undefined;\n if (hasReactReference && (await hasReactComponentTypeName(type, node, checker))) {\n return true;\n }\n\n const namedType = name ? await checker.getTypeAtLocation(name) : undefined;\n if (hasReactReference && namedType && (await hasReactComponentTypeName(namedType, node, checker))) {\n return true;\n }\n\n if (!isUppercaseComponentName(componentName)) {\n return false;\n }\n\n return (\n (await hasReactRenderableReturnType(type, node, checker)) ||\n Boolean(namedType && (await hasReactRenderableReturnType(namedType, node, checker)))\n );\n}\n\nfunction containsReactModuleReference(text: string): boolean {\n return /\\bfrom\\s+['\"]react['\"]|\\bimport\\s+['\"]react['\"]|\\brequire\\(\\s*['\"]react['\"]\\s*\\)/u.test(text);\n}\n\nasync function hasReactRenderableReturnType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const signatures = await checker.getSignaturesOfType(type, SignatureKind.Call);\n for (const signature of signatures) {\n const returnType = await checker.getReturnTypeOfSignature(signature);\n if (returnType && (await isReactRenderableType(returnType, node, checker))) {\n return true;\n }\n }\n return false;\n}\n\nasync function hasReactComponentTypeName(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\\b/u.test(\n typeName\n );\n}\n\nasync function isReactRenderableType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:JSX\\.Element|React\\.JSX\\.Element|ReactElement|ReactNode)\\b/u.test(typeName);\n}\n\nfunction isFunctionLikeCandidate(node: Node): boolean {\n if (node.kind === SyntaxKind.FunctionDeclaration && !getNodeProperty(node, 'body')) {\n return false;\n }\n\n return (\n node.kind === SyntaxKind.FunctionDeclaration ||\n node.kind === SyntaxKind.FunctionExpression ||\n node.kind === SyntaxKind.ArrowFunction\n );\n}\n\nfunction isVariableFunctionCandidate(node: Node): boolean {\n if (node.kind !== SyntaxKind.VariableDeclaration) {\n return false;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n return unwrappedInitializer\n ? isFunctionLikeCandidate(unwrappedInitializer) || unwrappedInitializer.kind === SyntaxKind.CallExpression\n : false;\n}\n\nfunction findComponentFunctionNode(node: Node): Node {\n if (isFunctionLikeCandidate(node)) {\n return node;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n if (!unwrappedInitializer) {\n return node;\n }\n if (isFunctionLikeCandidate(unwrappedInitializer)) {\n return unwrappedInitializer;\n }\n if (isComponentImplementationWrapperCall(unwrappedInitializer)) {\n return findComponentImplementationWrapperFunction(unwrappedInitializer) ?? node;\n }\n return node;\n}\n\nfunction isComponentImplementationWrapperCall(node: Node): boolean {\n if (node.kind !== SyntaxKind.CallExpression) {\n return false;\n }\n\n const expression = getNodeProperty(node, 'expression');\n const expressionName = expression ? findCallExpressionName(expression) : undefined;\n return expressionName === 'memo' || expressionName === 'forwardRef';\n}\n\nfunction findCallExpressionName(expression: Node): string | undefined {\n const name = getNodeProperty(expression, 'name');\n return name ? findCandidateName(name) : findCandidateName(expression);\n}\n\nfunction findComponentImplementationWrapperFunction(node: Node): Node | undefined {\n let functionNode: Node | undefined;\n node.forEachChild((child) => {\n const unwrappedChild = unwrapExpression(child);\n if (isFunctionLikeCandidate(unwrappedChild)) {\n functionNode = unwrappedChild;\n } else if (isComponentImplementationWrapperCall(unwrappedChild)) {\n functionNode = findComponentImplementationWrapperFunction(unwrappedChild);\n }\n return functionNode;\n });\n return functionNode;\n}\n\nfunction findFunctionStartPosition(\n text: string,\n functionNode: Node,\n name: Node | undefined,\n sourceFile: SourceFile\n): number {\n const tokenPosition = getTokenPosOfNode(functionNode, sourceFile);\n if (functionNode.kind !== SyntaxKind.FunctionDeclaration || !name) {\n return tokenPosition;\n }\n\n const functionName = findCandidateName(name);\n const namePosition = functionName ? text.indexOf(functionName, tokenPosition) : -1;\n const functionPosition = namePosition >= 0 ? text.lastIndexOf('function', namePosition) : -1;\n if (functionPosition < tokenPosition) {\n return tokenPosition;\n }\n\n const modifierText = text.slice(tokenPosition, functionPosition);\n const asyncMatch = /\\basync\\s*$/u.exec(modifierText);\n return asyncMatch ? tokenPosition + asyncMatch.index : functionPosition;\n}\n\nfunction dedupeReactComponentFunctions(\n components: readonly ReactComponentCandidateMetric[]\n): Omit<ReactComponentFunctionMetric, 'file'>[] {\n const componentByKey = new Map<string, ReactComponentCandidateMetric>();\n for (const component of components) {\n const existingComponent = componentByKey.get(component.implementationKey);\n if (!existingComponent || (!existingComponent.name && component.name)) {\n componentByKey.set(component.implementationKey, component);\n }\n }\n return [...componentByKey.values()].map((component) => ({\n name: component.name,\n startColumn: component.startColumn,\n startLine: component.startLine,\n }));\n}\n\nfunction findCandidateName(name: Node): string | undefined {\n if ('escapedText' in name) {\n return String(name.escapedText);\n }\n if ('text' in name && typeof name.text === 'string') {\n return name.text;\n }\n return undefined;\n}\n\nfunction isUppercaseComponentName(name: string | undefined): boolean {\n return name !== undefined && /^[A-Z]/u.test(name);\n}\n\nfunction findNameNode(node: Node): Node | undefined {\n const name = getNodeProperty(node, 'name');\n if (name) {\n return name;\n }\n\n let currentNode: Node | undefined = node;\n while (currentNode) {\n const parent = getNodeProperty(currentNode, 'parent');\n if (!parent) {\n return undefined;\n }\n if (parent.kind === SyntaxKind.VariableDeclaration) {\n return getNodeProperty(parent, 'name');\n }\n if (!isExpressionWrapperNode(parent)) {\n return undefined;\n }\n currentNode = parent;\n }\n return undefined;\n}\n\nfunction unwrapExpression(node: Node): Node {\n let currentNode = node;\n while (isExpressionWrapperNode(currentNode)) {\n const expression = getNodeProperty(currentNode, 'expression');\n if (!expression) {\n return currentNode;\n }\n currentNode = expression;\n }\n return currentNode;\n}\n\nfunction isExpressionWrapperNode(node: Node): boolean {\n return (\n node.kind === SyntaxKind.ParenthesizedExpression ||\n node.kind === SyntaxKind.AsExpression ||\n node.kind === SyntaxKind.TypeAssertionExpression ||\n node.kind === SyntaxKind.SatisfiesExpression\n );\n}\n\nfunction getNodeProperty(\n node: Node,\n property: 'body' | 'expression' | 'initializer' | 'name' | 'parent'\n): Node | undefined {\n const value = (node as Partial<Record<typeof property, Node>>)[property];\n return isNode(value) ? value : undefined;\n}\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && 'kind' in value && 'pos' in value && 'end' in value;\n}\n\nfunction positionToLineColumn(text: string, position: number): { column: number; line: number } {\n const lines = text.slice(0, position).split('\\n');\n return { column: lines.at(-1)?.length ?? 0, line: lines.length };\n}\n\nfunction collectCallExpressions(root: Node): Node[] {\n const calls: Node[] = [];\n visitNode(root, (node) => {\n if (node.kind === SyntaxKind.CallExpression) {\n calls.push(node);\n }\n });\n return calls;\n}\n\nfunction visitNode(node: Node, visitor: (node: Node) => void): void {\n visitor(node);\n node.forEachChild((child) => {\n visitNode(child, visitor);\n return;\n });\n}\n\nfunction addDiagnosticFiles(files: Set<string>, diagnostics: readonly DiagnosticLike[]): void {\n for (const diagnostic of diagnostics) {\n if (diagnostic.fileName) {\n files.add(diagnostic.fileName);\n }\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-gauge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Measure code metrics with tree-sitter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
"@types/node": "25.9.4",
|
|
69
69
|
"@willbooster/oxfmt-config": "1.2.2",
|
|
70
70
|
"@willbooster/oxlint-config": "1.4.8",
|
|
71
|
-
"@willbooster/wb": "
|
|
72
|
-
"build-ts": "21.0.
|
|
71
|
+
"@willbooster/wb": "21.1.0",
|
|
72
|
+
"build-ts": "21.0.4",
|
|
73
73
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
74
74
|
"lefthook": "2.1.10",
|
|
75
75
|
"oxfmt": "0.61.0",
|