@m4x_7/type-debt 1.0.4 → 1.0.6

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.
@@ -0,0 +1,10 @@
1
+ export declare const CONSTANTS: {
2
+ readonly CHUNK_SIZE: 50;
3
+ readonly TYPE_DEBT_METRICS: {
4
+ readonly EXPLICIT_ANY_WEIGHT: 2;
5
+ readonly IMPLICIT_ANY_WEIGHT: 3;
6
+ readonly AS_ANY_WEIGHT: 2;
7
+ readonly SUPPRESSION_WEIGHT: 4;
8
+ readonly NON_NULL_ASSERTION_WEIGHT: 1;
9
+ };
10
+ };
@@ -1,15 +1,11 @@
1
- export const CONSTANTS = {
2
-
3
- CHUNK_SIZE: 50,
4
-
5
- TYPE_DEBT_METRICS:{
6
- EXPLICIT_ANY_WEIGHT: 2,
7
- IMPLICIT_ANY_WEIGHT: 3,
8
- AS_ANY_WEIGHT: 2,
9
- SUPPRESSION_WEIGHT: 4,
10
- NON_NULL_ASSERTION_WEIGHT: 1,
11
- },
12
-
13
- } as const;
14
-
15
-
1
+ export const CONSTANTS = {
2
+ CHUNK_SIZE: 50,
3
+ TYPE_DEBT_METRICS: {
4
+ EXPLICIT_ANY_WEIGHT: 2,
5
+ IMPLICIT_ANY_WEIGHT: 3,
6
+ AS_ANY_WEIGHT: 2,
7
+ SUPPRESSION_WEIGHT: 4,
8
+ NON_NULL_ASSERTION_WEIGHT: 1,
9
+ },
10
+ };
11
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG;IAErB,UAAU,EAAE,EAAE;IAEd,iBAAiB,EAAC;QACd,mBAAmB,EAAE,CAAC;QACtB,mBAAmB,EAAE,CAAC;QACtB,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,yBAAyB,EAAE,CAAC;KAC/B;CAEK,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { FileReport } from "../types.js";
2
+ export declare function processChunk(chunk: string[]): FileReport[];
@@ -0,0 +1,20 @@
1
+ import { Project } from "ts-morph";
2
+ import { getTypeDebt } from "./typeDebt.js";
3
+ export function processChunk(chunk) {
4
+ const project = new Project({ skipAddingFilesFromTsConfig: true });
5
+ project.addSourceFilesAtPaths(chunk);
6
+ const chunkResults = [];
7
+ project.forgetNodesCreatedInBlock(() => {
8
+ for (const file of project.getSourceFiles()) {
9
+ chunkResults.push({
10
+ filePath: file.getFilePath(),
11
+ typeDebtMetrics: getTypeDebt(file)
12
+ });
13
+ }
14
+ });
15
+ for (const file of project.getSourceFiles()) {
16
+ project.removeSourceFile(file);
17
+ }
18
+ return chunkResults;
19
+ }
20
+ //# sourceMappingURL=chunkProcessor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunkProcessor.js","sourceRoot":"","sources":["../../core/chunkProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5C,MAAM,UAAU,YAAY,CAAC,KAAe;IAE1C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,2BAA2B,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAErC,MAAM,YAAY,GAAiB,EAAE,CAAC;IAEtC,OAAO,CAAC,yBAAyB,CAAC,GAAG,EAAE;QACrC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;YAC5C,YAAY,CAAC,IAAI,CAAC;gBAChB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE;gBAC5B,eAAe,EAAE,WAAW,CAAC,IAAI,CAAC;aACnC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;QAC5C,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { FileReport } from "../types.js";
2
+ export declare function generateMarkdownReport(results: FileReport[], targetDir: string, outputPath?: string): Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { promises as fs } from "fs";
2
+ import path from "path";
3
+ export async function generateMarkdownReport(results, targetDir, outputPath = "type-debt-report.md") {
4
+ if (results.length === 0)
5
+ return;
6
+ const totalFiles = results.length;
7
+ const totalScore = results.reduce((acc, curr) => acc + curr.typeDebtMetrics.score, 0);
8
+ const averageScore = Math.round(totalScore / totalFiles);
9
+ const sortedResults = [...results].sort((a, b) => a.typeDebtMetrics.score - b.typeDebtMetrics.score);
10
+ let md = `Type Debt Report\n\n`;
11
+ md += `> Generated on: ${new Date().toUTCString()}\n\n`;
12
+ md += `## Summary\n`;
13
+ md += `- Total Files Scanned: ${totalFiles}\n`;
14
+ md += `- Average Score: ${averageScore}/100\n\n`;
15
+ md += `## Top 10 Worst Offenders\n\n`;
16
+ md += `| Score | File Path | Suppressions | Implicit \`any\` | Explicit \`any\` | \`as any\` | Non-Null (\`!\`) |\n`;
17
+ md += `| :---: | :--- | :---: | :---: | :---: | :---: | :---: |\n`;
18
+ const worstOffenders = sortedResults.slice(0, 10);
19
+ const absoluteTargetDir = path.resolve(targetDir);
20
+ for (const result of worstOffenders) {
21
+ const metrics = result.typeDebtMetrics;
22
+ const cleanPath = path.relative(absoluteTargetDir, result.filePath);
23
+ md += `| ${metrics.score} | \`${cleanPath}\` | ${metrics.suppressions} | ${metrics.implicitAny} | ${metrics.explicitAny} | ${metrics.asAny} | ${metrics.nonNullAssertions} |\n`;
24
+ }
25
+ try {
26
+ await fs.writeFile(outputPath, md, "utf-8");
27
+ console.log(`\nMarkdown report successfully generated at: ${outputPath}`);
28
+ }
29
+ catch (error) {
30
+ console.error(`\nFailed to write Markdown report:`, error);
31
+ }
32
+ }
33
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../core/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AAIxB,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,OAAqB,EAAE,SAAiB,EAAE,UAAU,GAAG,qBAAqB;IACvH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEjC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAClC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACtF,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC;IAEzD,MAAM,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC/C,CAAC,CAAC,eAAe,CAAC,KAAK,GAAG,CAAC,CAAC,eAAe,CAAC,KAAK,CAClD,CAAC;IAEF,IAAI,EAAE,GAAG,sBAAsB,CAAC;IAChC,EAAE,IAAI,mBAAmB,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC;IAExD,EAAE,IAAI,cAAc,CAAC;IACrB,EAAE,IAAI,0BAA0B,UAAU,IAAI,CAAC;IAC/C,EAAE,IAAI,oBAAoB,YAAY,UAAU,CAAC;IAEjD,EAAE,IAAI,+BAA+B,CAAC;IAEtC,EAAE,IAAI,8GAA8G,CAAC;IAErH,EAAE,IAAI,4DAA4D,CAAC;IAEnE,MAAM,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAElD,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAElD,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,MAAM,CAAC,eAAe,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACpE,EAAE,IAAI,KAAK,OAAO,CAAC,KAAK,QAAQ,SAAS,QAAQ,OAAO,CAAC,YAAY,MAAM,OAAO,CAAC,WAAW,MAAM,OAAO,CAAC,WAAW,MAAM,OAAO,CAAC,KAAK,MAAM,OAAO,CAAC,iBAAiB,MAAM,CAAC;IAClL,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,gDAAgD,UAAU,EAAE,CAAC,CAAC;IAC5E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { SourceFile } from "ts-morph";
2
+ import { TypeDebtMetrics } from "../types.js";
3
+ export declare function getTypeDebt(sourceFile: SourceFile): TypeDebtMetrics;
4
+ export declare function calculateScore(metrics: TypeDebtMetrics): number;
5
+ export declare function calculatePenalty(metrics: TypeDebtMetrics): number;
@@ -0,0 +1,93 @@
1
+ import { SyntaxKind } from "ts-morph";
2
+ import { CONSTANTS } from "../constants.js";
3
+ const SUPPRESSION_REGEX = /(?:\/\/|\/\*)\s*@ts-(ignore|expect-error|nocheck)/g;
4
+ export function getTypeDebt(sourceFile) {
5
+ const metrics = {
6
+ explicitAny: 0,
7
+ implicitAny: 0,
8
+ asAny: 0,
9
+ suppressions: 0,
10
+ nonNullAssertions: 0,
11
+ validTypes: 0,
12
+ score: 0
13
+ };
14
+ sourceFile.forEachDescendant(node => {
15
+ const kind = node.getKind();
16
+ // explicit any
17
+ if (kind === SyntaxKind.AnyKeyword) {
18
+ const parent = node.getParent();
19
+ if (parent?.getKind() !== SyntaxKind.AsExpression) {
20
+ metrics.explicitAny++;
21
+ }
22
+ }
23
+ // as any
24
+ else if (kind === SyntaxKind.AsExpression) {
25
+ const asExpression = node.asKindOrThrow(SyntaxKind.AsExpression);
26
+ if (asExpression.getTypeNode()?.getKind() === SyntaxKind.AnyKeyword) {
27
+ metrics.asAny++;
28
+ }
29
+ }
30
+ // implicit any
31
+ if (kind === SyntaxKind.Parameter) {
32
+ const parameter = node.asKindOrThrow(SyntaxKind.Parameter);
33
+ if (!parameter.getTypeNode() && !parameter.getInitializer()) {
34
+ const parentFunction = parameter.getParent();
35
+ const grandParent = parentFunction?.getParent();
36
+ // Ignore parameters in callbacks (e.g., .map(item => ...))
37
+ const isCallback = (parentFunction?.getKind() === SyntaxKind.ArrowFunction ||
38
+ parentFunction?.getKind() === SyntaxKind.FunctionExpression) &&
39
+ grandParent?.getKind() === SyntaxKind.CallExpression;
40
+ if (!isCallback) {
41
+ metrics.implicitAny++;
42
+ }
43
+ }
44
+ }
45
+ // not null expression
46
+ if (kind === SyntaxKind.NonNullExpression) {
47
+ metrics.nonNullAssertions++;
48
+ }
49
+ // valid types
50
+ else if (isValidType(kind)) {
51
+ metrics.validTypes++;
52
+ }
53
+ });
54
+ //suppressions
55
+ const text = sourceFile.getFullText();
56
+ const matches = text.match(SUPPRESSION_REGEX);
57
+ metrics.suppressions = matches ? matches.length : 0;
58
+ metrics.score = calculateScore(metrics);
59
+ return metrics;
60
+ }
61
+ export function calculateScore(metrics) {
62
+ const totalDebtInstances = metrics.explicitAny +
63
+ metrics.implicitAny +
64
+ metrics.asAny +
65
+ metrics.suppressions +
66
+ metrics.nonNullAssertions;
67
+ const totalTypeNodes = metrics.validTypes + totalDebtInstances;
68
+ if (totalTypeNodes == 0) {
69
+ return 100;
70
+ }
71
+ else {
72
+ const baseScore = (metrics.validTypes / totalTypeNodes) * 100;
73
+ const rawPenalty = calculatePenalty(metrics);
74
+ const scaledPenalty = (rawPenalty / totalTypeNodes) * 100;
75
+ return Math.max(0, Math.round(baseScore - scaledPenalty));
76
+ }
77
+ }
78
+ export function calculatePenalty(metrics) {
79
+ return metrics.explicitAny * CONSTANTS.TYPE_DEBT_METRICS.EXPLICIT_ANY_WEIGHT
80
+ + metrics.implicitAny * CONSTANTS.TYPE_DEBT_METRICS.IMPLICIT_ANY_WEIGHT
81
+ + metrics.asAny * CONSTANTS.TYPE_DEBT_METRICS.AS_ANY_WEIGHT
82
+ + metrics.nonNullAssertions * CONSTANTS.TYPE_DEBT_METRICS.NON_NULL_ASSERTION_WEIGHT
83
+ + metrics.suppressions * CONSTANTS.TYPE_DEBT_METRICS.SUPPRESSION_WEIGHT;
84
+ }
85
+ function isValidType(kind) {
86
+ return (kind === SyntaxKind.TypeReference ||
87
+ kind === SyntaxKind.StringKeyword ||
88
+ kind === SyntaxKind.NumberKeyword ||
89
+ kind === SyntaxKind.BooleanKeyword ||
90
+ kind === SyntaxKind.InterfaceDeclaration ||
91
+ kind === SyntaxKind.TypeAliasDeclaration);
92
+ }
93
+ //# sourceMappingURL=typeDebt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeDebt.js","sourceRoot":"","sources":["../../core/typeDebt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js CHANGED
@@ -1,27 +1,45 @@
1
1
  #!/usr/bin/env node
2
- var V=Object.defineProperty;var v=Object.getOwnPropertySymbols;var J=Object.prototype.hasOwnProperty,tt=Object.prototype.propertyIsEnumerable;var k=(t,e,s)=>e in t?V(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s,E=(t,e)=>{for(var s in e||(e={}))J.call(e,s)&&k(t,s,e[s]);if(v)for(var s of v(e))tt.call(e,s)&&k(t,s,e[s]);return t};var c=(t,e,s)=>k(t,typeof e!="symbol"?e+"":e,s);var S=(t,e,s)=>new Promise((r,i)=>{var o=u=>{try{n(s.next(u))}catch(h){i(h)}},l=u=>{try{n(s.throw(u))}catch(h){i(h)}},n=u=>u.done?r(u.value):Promise.resolve(u.value).then(o,l);n((s=s.apply(t,e)).next())});import{createRequire as et}from"module";import{basename as st,dirname as P,normalize as rt,relative as it,resolve as nt,sep as C}from"path";import*as ot from"fs";var te={};var I=et(te.url);function lt(t){let e=rt(t);return e.length>1&&e[e.length-1]===C&&(e=e.substring(0,e.length-1)),e}var ut=/[\\/]/g;function N(t,e){return t.replace(ut,e)}var ct=/^[a-z]:[\\/]$/i;function at(t){return t==="/"||ct.test(t)}function D(t,e){let{resolvePaths:s,normalizePath:r,pathSeparator:i}=e,o=process.platform==="win32"&&t.includes("/")||t.startsWith(".");if(s&&(t=nt(t)),(r||o)&&(t=lt(t)),t===".")return"";let l=t[t.length-1]!==i;return N(l?t+i:t,i)}function M(t,e){return e+t}function ht(t,e){return function(s,r){return r.startsWith(t)?r.slice(t.length)+s:N(it(t,r),e.pathSeparator)+e.pathSeparator+s}}function pt(t){return t}function ft(t,e,s){return e+t+s}function yt(t,e){let{relativePaths:s,includeBasePath:r}=e;return s&&t?ht(t,e):r?M:pt}function mt(t){return function(e,s){s.push(e.substring(t.length)||".")}}function gt(t){return function(e,s,r){let i=e.substring(t.length)||".";r.every(o=>o(i,!0))&&s.push(i)}}var dt=(t,e)=>{e.push(t||".")},St=(t,e,s)=>{let r=t||".";s.every(i=>i(r,!0))&&e.push(r)},wt=()=>{};function Tt(t,e){let{includeDirs:s,filters:r,relativePaths:i}=e;return s?i?r&&r.length?gt(t):mt(t):r&&r.length?St:dt:wt}var Et=(t,e,s,r)=>{r.every(i=>i(t,!1))&&s.files++},bt=(t,e,s,r)=>{r.every(i=>i(t,!1))&&e.push(t)},xt=(t,e,s,r)=>{s.files++},At=(t,e)=>{e.push(t)},Ft=()=>{};function kt(t){let{excludeFiles:e,filters:s,onlyCounts:r}=t;return e?Ft:s&&s.length?r?Et:bt:r?xt:At}var Dt=t=>t,Pt=()=>[""].slice(0,0);function _t(t){return t.group?Pt:Dt}var vt=(t,e,s)=>{t.push({directory:e,files:s,dir:e})},It=()=>{};function Rt(t){return t.group?vt:It}var Ct=function(t,e,s){let{queue:r,fs:i,options:{suppressErrors:o}}=e;r.enqueue(),i.realpath(t,(l,n)=>{if(l)return r.dequeue(o?null:l,e);i.stat(n,(u,h)=>{if(u)return r.dequeue(o?null:u,e);if(h.isDirectory()&&W(t,n,e))return r.dequeue(null,e);s(h,n),r.dequeue(null,e)})})},Nt=function(t,e,s){let{queue:r,fs:i,options:{suppressErrors:o}}=e;r.enqueue();try{let l=i.realpathSync(t),n=i.statSync(l);if(n.isDirectory()&&W(t,l,e))return;s(n,l)}catch(l){if(!o)throw l}};function Mt(t,e){return!t.resolveSymlinks||t.excludeSymlinks?null:e?Nt:Ct}function W(t,e,s){if(s.options.useRealPaths)return Wt(e,s);let r=P(t),i=1;for(;r!==s.root&&i<2;){let o=s.symlinks.get(r);!!o&&(o===e||o.startsWith(e)||e.startsWith(o))?i++:r=P(r)}return s.symlinks.set(t,e),i>1}function Wt(t,e){return e.visited.includes(t+e.options.pathSeparator)}var $t=t=>t.counts,Ot=t=>t.groups,qt=t=>t.paths,Kt=t=>t.paths.slice(0,t.options.maxFiles),Gt=(t,e,s)=>(b(e,s,t.counts,t.options.suppressErrors),null),Bt=(t,e,s)=>(b(e,s,t.paths,t.options.suppressErrors),null),Ht=(t,e,s)=>(b(e,s,t.paths.slice(0,t.options.maxFiles),t.options.suppressErrors),null),jt=(t,e,s)=>(b(e,s,t.groups,t.options.suppressErrors),null);function b(t,e,s,r){e(t&&!r?t:null,s)}function Lt(t,e){let{onlyCounts:s,group:r,maxFiles:i}=t;return s?e?$t:Gt:r?e?Ot:jt:i?e?Kt:Ht:e?qt:Bt}var $={withFileTypes:!0},Yt=(t,e,s,r,i)=>{if(t.queue.enqueue(),r<0)return t.queue.dequeue(null,t);let{fs:o}=t;t.visited.push(e),t.counts.directories++,o.readdir(e||".",$,(l,n=[])=>{i(n,s,r),t.queue.dequeue(t.options.suppressErrors?null:l,t)})},Ut=(t,e,s,r,i)=>{let{fs:o}=t;if(r<0)return;t.visited.push(e),t.counts.directories++;let l=[];try{l=o.readdirSync(e||".",$)}catch(n){if(!t.options.suppressErrors)throw n}i(l,s,r)};function zt(t){return t?Ut:Yt}var Qt=class{constructor(t){c(this,"count",0);this.onQueueEmpty=t}enqueue(){return this.count++,this.count}dequeue(t,e){this.onQueueEmpty&&(--this.count<=0||t)&&(this.onQueueEmpty(t,e),t&&(e.controller.abort(),this.onQueueEmpty=void 0))}},Xt=class{constructor(){c(this,"_files",0);c(this,"_directories",0)}set files(t){this._files=t}get files(){return this._files}set directories(t){this._directories=t}get directories(){return this._directories}get dirs(){return this._directories}},Zt=class{constructor(){c(this,"aborted",!1)}abort(){this.aborted=!0}},O=class{constructor(t,e,s){c(this,"root");c(this,"isSynchronous");c(this,"state");c(this,"joinPath");c(this,"pushDirectory");c(this,"pushFile");c(this,"getArray");c(this,"groupFiles");c(this,"resolveSymlink");c(this,"walkDirectory");c(this,"callbackInvoker");c(this,"walk",(t,e,s)=>{let{paths:r,options:{filters:i,resolveSymlinks:o,excludeSymlinks:l,exclude:n,maxFiles:u,signal:h,useRealPaths:p,pathSeparator:f},controller:x}=this.state;if(x.aborted||h&&h.aborted||u&&r.length>u)return;let A=this.getArray(this.state.paths);for(let F=0;F<t.length;++F){let g=t[F];if(g.isFile()||g.isSymbolicLink()&&!o&&!l){let y=this.joinPath(g.name,e);this.pushFile(y,A,this.state.counts,i)}else if(g.isDirectory()){let y=ft(g.name,e,this.state.options.pathSeparator);if(n&&n(g.name,y))continue;this.pushDirectory(y,r,i),this.walkDirectory(this.state,y,y,s-1,this.walk)}else if(this.resolveSymlink&&g.isSymbolicLink()){let y=M(g.name,e);this.resolveSymlink(y,this.state,(Q,m)=>{if(Q.isDirectory()){if(m=D(m,this.state.options),n&&n(g.name,p?m:y+f))return;this.walkDirectory(this.state,m,p?m:y+f,s-1,this.walk)}else{m=p?m:y;let X=st(m),Z=D(P(m),this.state.options);m=this.joinPath(X,Z),this.pushFile(m,A,this.state.counts,i)}})}}this.groupFiles(this.state.groups,e,A)});this.isSynchronous=!s,this.callbackInvoker=Lt(e,this.isSynchronous),this.root=D(t,e),this.state={root:at(this.root)?this.root:this.root.slice(0,-1),paths:[""].slice(0,0),groups:[],counts:new Xt,options:e,queue:new Qt((r,i)=>this.callbackInvoker(i,r,s)),symlinks:new Map,visited:[""].slice(0,0),controller:new Zt,fs:e.fs||ot},this.joinPath=yt(this.root,e),this.pushDirectory=Tt(this.root,e),this.pushFile=kt(e),this.getArray=_t(e),this.groupFiles=Rt(e),this.resolveSymlink=Mt(e,this.isSynchronous),this.walkDirectory=zt(this.isSynchronous)}start(){return this.pushDirectory(this.root,this.state.paths,this.state.options.filters),this.walkDirectory(this.state,this.root,this.root,this.state.options.maxDepth,this.walk),this.isSynchronous?this.callbackInvoker(this.state,null):null}};function Vt(t,e){return new Promise((s,r)=>{q(t,e,(i,o)=>{if(i)return r(i);s(o)})})}function q(t,e,s){new O(t,e,s).start()}function Jt(t,e){return new O(t,e).start()}var R=class{constructor(t,e){this.root=t,this.options=e}withPromise(){return Vt(this.root,this.options)}withCallback(t){q(this.root,this.options,t)}sync(){return Jt(this.root,this.options)}},K=null;try{I.resolve("picomatch"),K=I("picomatch")}catch(t){}var G=class{constructor(t){c(this,"globCache",{});c(this,"options",{maxDepth:1/0,suppressErrors:!0,pathSeparator:C,filters:[]});c(this,"globFunction");this.options=E(E({},this.options),t),this.globFunction=this.options.globFunction}group(){return this.options.group=!0,this}withPathSeparator(t){return this.options.pathSeparator=t,this}withBasePath(){return this.options.includeBasePath=!0,this}withRelativePaths(){return this.options.relativePaths=!0,this}withDirs(){return this.options.includeDirs=!0,this}withMaxDepth(t){return this.options.maxDepth=t,this}withMaxFiles(t){return this.options.maxFiles=t,this}withFullPaths(){return this.options.resolvePaths=!0,this.options.includeBasePath=!0,this}withErrors(){return this.options.suppressErrors=!1,this}withSymlinks({resolvePaths:t=!0}={}){return this.options.resolveSymlinks=!0,this.options.useRealPaths=t,this.withFullPaths()}withAbortSignal(t){return this.options.signal=t,this}normalize(){return this.options.normalizePath=!0,this}filter(t){return this.options.filters.push(t),this}onlyDirs(){return this.options.excludeFiles=!0,this.options.includeDirs=!0,this}exclude(t){return this.options.exclude=t,this}onlyCounts(){return this.options.onlyCounts=!0,this}crawl(t){return new R(t||".",this.options)}withGlobFunction(t){return this.globFunction=t,this}crawlWithOptions(t,e){return this.options=E(E({},this.options),e),new R(t||".",this.options)}glob(...t){return this.globFunction?this.globWithOptions(t):this.globWithOptions(t,{dot:!0})}globWithOptions(t,...e){let s=this.globFunction||K;if(!s)throw new Error("Please specify a glob function to use glob matching.");var r=this.globCache[t.join("\0")];return r||(r=s(t,...e),this.globCache[t.join("\0")]=r),this.options.filters.push(i=>r(i)),this}};import{stat as ee}from"fs/promises";function B(t){return new G().withFullPaths().exclude(s=>s==="node_modules"||s==="dist"||s==="build"||s===".git").filter(s=>s.endsWith(".ts")||s.endsWith(".tsx")).crawl(t).sync()}function H(t,e){let s=[];for(let r=0;r<t.length;r+=e)s.push(t.slice(r,r+e));return s}function j(t){return S(this,null,function*(){let e;try{e=yield ee(t)}catch(s){throw new Error(`Directory "${t}" does not exist.`)}if(!e.isDirectory())throw new Error(`"${t}" is not a directory.`)})}import{Project as oe}from"ts-morph";import{SyntaxKind as a}from"ts-morph";var d={CHUNK_SIZE:50,TYPE_DEBT_METRICS:{EXPLICIT_ANY_WEIGHT:2,IMPLICIT_ANY_WEIGHT:3,AS_ANY_WEIGHT:2,SUPPRESSION_WEIGHT:4,NON_NULL_ASSERTION_WEIGHT:1}};var se=/(?:\/\/|\/\*)\s*@ts-(ignore|expect-error|nocheck)/g;function L(t){let e={explicitAny:0,implicitAny:0,asAny:0,suppressions:0,nonNullAssertions:0,validTypes:0,score:0};t.forEachDescendant(i=>{var l;let o=i.getKind();if(o===a.AnyKeyword){let n=i.getParent();(n==null?void 0:n.getKind())!==a.AsExpression&&e.explicitAny++}else o===a.AsExpression&&((l=i.asKindOrThrow(a.AsExpression).getTypeNode())==null?void 0:l.getKind())===a.AnyKeyword&&e.asAny++;if(o===a.Parameter){let n=i.asKindOrThrow(a.Parameter);if(!n.getTypeNode()&&!n.getInitializer()){let u=n.getParent(),h=u==null?void 0:u.getParent();((u==null?void 0:u.getKind())===a.ArrowFunction||(u==null?void 0:u.getKind())===a.FunctionExpression)&&(h==null?void 0:h.getKind())===a.CallExpression||e.implicitAny++}}o===a.NonNullExpression?e.nonNullAssertions++:ne(o)&&e.validTypes++});let r=t.getFullText().match(se);return e.suppressions=r?r.length:0,e.score=re(e),e}function re(t){let e=t.explicitAny+t.implicitAny+t.asAny+t.suppressions+t.nonNullAssertions,s=t.validTypes+e;if(s==0)return 100;{let r=t.validTypes/s*100,o=ie(t)/s*100;return Math.max(0,Math.round(r-o))}}function ie(t){return t.explicitAny*d.TYPE_DEBT_METRICS.EXPLICIT_ANY_WEIGHT+t.implicitAny*d.TYPE_DEBT_METRICS.IMPLICIT_ANY_WEIGHT+t.asAny*d.TYPE_DEBT_METRICS.AS_ANY_WEIGHT+t.nonNullAssertions*d.TYPE_DEBT_METRICS.NON_NULL_ASSERTION_WEIGHT+t.suppressions*d.TYPE_DEBT_METRICS.SUPPRESSION_WEIGHT}function ne(t){return t===a.TypeReference||t===a.StringKeyword||t===a.NumberKeyword||t===a.BooleanKeyword||t===a.InterfaceDeclaration||t===a.TypeAliasDeclaration}function Y(t){let e=new oe({skipAddingFilesFromTsConfig:!0});e.addSourceFilesAtPaths(t);let s=[];e.forgetNodesCreatedInBlock(()=>{for(let r of e.getSourceFiles())s.push({filePath:r.getFilePath(),typeDebtMetrics:L(r)})});for(let r of e.getSourceFiles())e.removeSourceFile(r);return s}import{promises as le}from"fs";import U from"path";function z(t,e,s="type-debt-report.md"){return S(this,null,function*(){if(t.length===0)return;let r=t.length,i=t.reduce((p,f)=>p+f.typeDebtMetrics.score,0),o=Math.round(i/r),l=[...t].sort((p,f)=>p.typeDebtMetrics.score-f.typeDebtMetrics.score),n=`Type Debt Report
3
-
4
- `;n+=`> Generated on: ${new Date().toUTCString()}
5
-
6
- `,n+=`## Summary
7
- `,n+=`- Total Files Scanned: ${r}
8
- `,n+=`- Average Score: ${o}/100
9
-
10
- `,n+=`## Top 10 Worst Offenders
11
-
12
- `,n+="| Score | File Path | Suppressions | Implicit `any` | Explicit `any` | `as any` | Non-Null (`!`) |\n",n+=`| :---: | :--- | :---: | :---: | :---: | :---: | :---: |
13
- `;let u=l.slice(0,10),h=U.resolve(e);for(let p of u){let f=p.typeDebtMetrics,x=U.relative(h,p.filePath);n+=`| ${f.score} | \`${x}\` | ${f.suppressions} | ${f.implicitAny} | ${f.explicitAny} | ${f.asAny} | ${f.nonNullAssertions} |
14
- `}try{yield le.writeFile(s,n,"utf-8"),console.log(`
15
- Markdown report successfully generated at: ${s}`)}catch(p){console.error(`
16
- Failed to write Markdown report:`,p)}})}var _=process.argv.slice(2);(_.includes("--help")||_.includes("-h"))&&(ae(),process.exit(0));var ue=_[0]||".";ce(ue).catch(t=>{console.error(`
17
- \x1B[31m Error: ${t.message}\x1B[0m
18
- `),process.exit(1)});function ce(t){return S(this,null,function*(){yield j(t),console.log(`
19
- Scanning directory: ${t}`);let e=B(t);if(e.length===0)throw new Error("No TypeScript files found to process.");let s=H(e,d.CHUNK_SIZE),r=[];for(let[i,o]of s.entries()){let l=Y(o);r=r.concat(l),process.stdout.write(`\rProcessed chunk ${i+1}/${s.length}`)}yield z(r,t)})}function ae(){console.log(`
2
+ import { getTargetFiles } from "./utils.js";
3
+ import { chunkArray, validateDirectory } from "./utils.js";
4
+ import { processChunk } from "./core/chunkProcessor.js";
5
+ import { CONSTANTS } from "./constants.js";
6
+ import { generateMarkdownReport } from "./core/reporter.js";
7
+ const args = process.argv.slice(2);
8
+ if (args.includes('--help') || args.includes('-h')) {
9
+ printHelp();
10
+ process.exit(0);
11
+ }
12
+ const target = args[0] || ".";
13
+ runCLI(target).catch((err) => {
14
+ console.error(`\n\x1b[31m Error: ${err.message}\x1b[0m\n`);
15
+ process.exit(1);
16
+ });
17
+ async function runCLI(targetDir) {
18
+ await validateDirectory(targetDir);
19
+ console.log(`\nScanning directory: ${targetDir}`);
20
+ const allFiles = getTargetFiles(targetDir);
21
+ if (allFiles.length === 0) {
22
+ throw new Error(`No TypeScript files found to process.`);
23
+ }
24
+ const fileChunks = chunkArray(allFiles, CONSTANTS.CHUNK_SIZE);
25
+ let allResults = [];
26
+ for (const [index, chunk] of fileChunks.entries()) {
27
+ const chunkResults = processChunk(chunk);
28
+ allResults = allResults.concat(chunkResults);
29
+ process.stdout.write(`\rProcessed chunk ${index + 1}/${fileChunks.length}`);
30
+ }
31
+ await generateMarkdownReport(allResults, targetDir);
32
+ }
33
+ function printHelp() {
34
+ console.log(`
20
35
  Type Debt Analyzer
21
36
 
22
- Usage: npx tsx index.ts [directory]
37
+ Usage: npx type-debt [directory]
23
38
 
24
39
  Examples:
25
- npx tsx index.ts .
26
- npx tsx index.ts ./src
27
- `),process.exit(0)}
40
+ npx type-debt .
41
+ npx type-debt ./src
42
+ `);
43
+ process.exit(0);
44
+ }
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAI5D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACnD,SAAS,EAAE,CAAC;IACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;AAE9B,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAC3B,OAAO,CAAC,KAAK,CAAC,qBAAqB,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC;IAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAGH,KAAK,UAAU,MAAM,CAAC,SAAiB;IAErC,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,yBAAyB,SAAS,EAAE,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAE3C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,UAAU,GAAG,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IAE9D,IAAI,UAAU,GAAiB,EAAE,CAAC;IAElC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;QAElD,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACzC,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,KAAK,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,sBAAsB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAEtD,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;GAQX,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
@@ -0,0 +1,13 @@
1
+ export interface FileReport {
2
+ filePath: string;
3
+ typeDebtMetrics: TypeDebtMetrics;
4
+ }
5
+ export interface TypeDebtMetrics {
6
+ explicitAny: number;
7
+ implicitAny: number;
8
+ asAny: number;
9
+ suppressions: number;
10
+ nonNullAssertions: number;
11
+ validTypes: number;
12
+ score: number;
13
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ export declare function getTargetFiles(directoryPath: string): string[];
2
+ export declare function chunkArray<T>(array: T[], chunkSize: number): T[][];
3
+ export declare function validateDirectory(path: string): Promise<void>;
package/dist/utils.js ADDED
@@ -0,0 +1,33 @@
1
+ import { fdir } from "fdir";
2
+ import { stat } from "fs/promises";
3
+ export function getTargetFiles(directoryPath) {
4
+ const crawler = new fdir()
5
+ .withFullPaths()
6
+ .exclude((dirName) => dirName === "node_modules" ||
7
+ dirName === "dist" ||
8
+ dirName === "build" ||
9
+ dirName === ".git")
10
+ .filter((path) => path.endsWith(".ts") || path.endsWith(".tsx"))
11
+ .crawl(directoryPath);
12
+ return crawler.sync();
13
+ }
14
+ export function chunkArray(array, chunkSize) {
15
+ const chunks = [];
16
+ for (let i = 0; i < array.length; i += chunkSize) {
17
+ chunks.push(array.slice(i, i + chunkSize));
18
+ }
19
+ return chunks;
20
+ }
21
+ export async function validateDirectory(path) {
22
+ let stats;
23
+ try {
24
+ stats = await stat(path);
25
+ }
26
+ catch {
27
+ throw new Error(`Directory "${path}" does not exist.`);
28
+ }
29
+ if (!stats.isDirectory()) {
30
+ throw new Error(`"${path}" is not a directory.`);
31
+ }
32
+ }
33
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGnC,MAAM,UAAU,cAAc,CAAC,aAAqB;IAClD,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE;SACvB,aAAa,EAAE;SACf,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CACnB,OAAO,KAAK,cAAc;QAC1B,OAAO,KAAK,MAAM;QAClB,OAAO,KAAK,OAAO;QACnB,OAAO,KAAK,MAAM,CACnB;SACA,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;SAC/D,KAAK,CAAC,aAAa,CAAC,CAAC;IAExB,OAAO,OAAO,CAAC,IAAI,EAAc,CAAC;AACpC,CAAC;AAGD,MAAM,UAAU,UAAU,CAAI,KAAU,EAAE,SAAiB;IACzD,MAAM,MAAM,GAAU,EAAE,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAGD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY;IAElD,IAAI,KAAK,CAAC;IAEV,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,mBAAmB,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,uBAAuB,CAAC,CAAC;IACnD,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,14 +1,19 @@
1
1
  {
2
2
  "name": "@m4x_7/type-debt",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "A high-performance TypeScript static analyzer that calculates Type Debt and generates reports.",
6
- "main": "dist/index.js",
6
+ "main": "./dist/index.js",
7
7
  "bin": {
8
- "type-debt": "dist/index.js"
8
+ "type-debt": "./dist/index.js"
9
9
  },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
10
15
  "scripts": {
11
- "build": "tsup index.ts --format esm --minify --shims --clean",
16
+ "build": "tsc",
12
17
  "test": "vitest",
13
18
  "dev": "nodemon --ignore temp_repos/ --exec ts-node main.ts"
14
19
  },
@@ -37,4 +42,4 @@
37
42
  "typescript": "^6.0.2",
38
43
  "vitest": "^4.1.9"
39
44
  }
40
- }
45
+ }
@@ -1,27 +0,0 @@
1
- import { Project } from "ts-morph";
2
- import { getTypeDebt } from "./typeDebt";
3
- import { FileReport } from "../types";
4
-
5
-
6
- export function processChunk(chunk: string[]): FileReport[] {
7
-
8
- const project = new Project({ skipAddingFilesFromTsConfig: true });
9
- project.addSourceFilesAtPaths(chunk);
10
-
11
- const chunkResults: FileReport[] = [];
12
-
13
- project.forgetNodesCreatedInBlock(() => {
14
- for (const file of project.getSourceFiles()) {
15
- chunkResults.push({
16
- filePath: file.getFilePath(),
17
- typeDebtMetrics: getTypeDebt(file)
18
- });
19
- }
20
- });
21
-
22
- for (const file of project.getSourceFiles()) {
23
- project.removeSourceFile(file);
24
- }
25
-
26
- return chunkResults;
27
- }
package/core/reporter.ts DELETED
@@ -1,46 +0,0 @@
1
- import { promises as fs } from "fs";
2
- import path from "path";
3
- import { FileReport } from "../types"
4
-
5
-
6
- export async function generateMarkdownReport(results: FileReport[], targetDir: string, outputPath = "type-debt-report.md") {
7
- if (results.length === 0) return;
8
-
9
- const totalFiles = results.length;
10
- const totalScore = results.reduce((acc, curr) => acc + curr.typeDebtMetrics.score, 0);
11
- const averageScore = Math.round(totalScore / totalFiles);
12
-
13
- const sortedResults = [...results].sort((a, b) =>
14
- a.typeDebtMetrics.score - b.typeDebtMetrics.score
15
- );
16
-
17
- let md = `Type Debt Report\n\n`;
18
- md += `> Generated on: ${new Date().toUTCString()}\n\n`;
19
-
20
- md += `## Summary\n`;
21
- md += `- Total Files Scanned: ${totalFiles}\n`;
22
- md += `- Average Score: ${averageScore}/100\n\n`;
23
-
24
- md += `## Top 10 Worst Offenders\n\n`;
25
-
26
- md += `| Score | File Path | Suppressions | Implicit \`any\` | Explicit \`any\` | \`as any\` | Non-Null (\`!\`) |\n`;
27
-
28
- md += `| :---: | :--- | :---: | :---: | :---: | :---: | :---: |\n`;
29
-
30
- const worstOffenders = sortedResults.slice(0, 10);
31
-
32
- const absoluteTargetDir = path.resolve(targetDir);
33
-
34
- for (const result of worstOffenders) {
35
- const metrics = result.typeDebtMetrics;
36
- const cleanPath = path.relative(absoluteTargetDir, result.filePath);
37
- md += `| ${metrics.score} | \`${cleanPath}\` | ${metrics.suppressions} | ${metrics.implicitAny} | ${metrics.explicitAny} | ${metrics.asAny} | ${metrics.nonNullAssertions} |\n`;
38
- }
39
-
40
- try {
41
- await fs.writeFile(outputPath, md, "utf-8");
42
- console.log(`\nMarkdown report successfully generated at: ${outputPath}`);
43
- } catch (error) {
44
- console.error(`\nFailed to write Markdown report:`, error);
45
- }
46
- }
package/core/typeDebt.ts DELETED
@@ -1,125 +0,0 @@
1
- import { SourceFile, SyntaxKind } from "ts-morph";
2
- import { TypeDebtMetrics } from "../types";
3
- import { CONSTANTS } from "../constants";
4
-
5
-
6
- const SUPPRESSION_REGEX = /(?:\/\/|\/\*)\s*@ts-(ignore|expect-error|nocheck)/g;
7
-
8
- export function getTypeDebt(sourceFile: SourceFile): TypeDebtMetrics {
9
-
10
- const metrics: TypeDebtMetrics = {
11
- explicitAny: 0,
12
- implicitAny: 0,
13
- asAny: 0,
14
- suppressions: 0,
15
- nonNullAssertions: 0,
16
- validTypes: 0,
17
- score: 0
18
- }
19
-
20
- sourceFile.forEachDescendant(node => {
21
-
22
- const kind = node.getKind();
23
-
24
- // explicit any
25
- if (kind === SyntaxKind.AnyKeyword) {
26
- const parent = node.getParent();
27
- if (parent?.getKind() !== SyntaxKind.AsExpression) {
28
- metrics.explicitAny++;
29
- }
30
- }
31
-
32
- // as any
33
- else if (kind === SyntaxKind.AsExpression) {
34
-
35
- const asExpression = node.asKindOrThrow(SyntaxKind.AsExpression);
36
-
37
- if (asExpression.getTypeNode()?.getKind() === SyntaxKind.AnyKeyword) {
38
- metrics.asAny++;
39
- }
40
-
41
- }
42
-
43
- // implicit any
44
- if (kind === SyntaxKind.Parameter) {
45
- const parameter = node.asKindOrThrow(SyntaxKind.Parameter);
46
-
47
- if (!parameter.getTypeNode() && !parameter.getInitializer()) {
48
- const parentFunction = parameter.getParent();
49
- const grandParent = parentFunction?.getParent();
50
-
51
- // Ignore parameters in callbacks (e.g., .map(item => ...))
52
- const isCallback =
53
- (parentFunction?.getKind() === SyntaxKind.ArrowFunction ||
54
- parentFunction?.getKind() === SyntaxKind.FunctionExpression) &&
55
- grandParent?.getKind() === SyntaxKind.CallExpression;
56
-
57
- if (!isCallback) {
58
- metrics.implicitAny++;
59
- }
60
- }
61
- }
62
-
63
- // not null expression
64
- if (kind === SyntaxKind.NonNullExpression) {
65
- metrics.nonNullAssertions++;
66
- }
67
-
68
-
69
- // valid types
70
- else if (isValidType(kind)) {
71
- metrics.validTypes++;
72
- }
73
-
74
- });
75
-
76
- //suppressions
77
- const text = sourceFile.getFullText();
78
- const matches = text.match(SUPPRESSION_REGEX);
79
- metrics.suppressions = matches ? matches.length : 0;
80
-
81
- metrics.score = calculateScore(metrics);
82
-
83
- return metrics;
84
- }
85
-
86
- export function calculateScore(metrics: TypeDebtMetrics): number {
87
-
88
- const totalDebtInstances =
89
- metrics.explicitAny +
90
- metrics.implicitAny +
91
- metrics.asAny +
92
- metrics.suppressions +
93
- metrics.nonNullAssertions;
94
-
95
- const totalTypeNodes = metrics.validTypes + totalDebtInstances;
96
-
97
- if (totalTypeNodes == 0) {
98
- return 100;
99
- }
100
-
101
- else {
102
- const baseScore = (metrics.validTypes / totalTypeNodes) * 100;
103
- const rawPenalty = calculatePenalty(metrics);
104
- const scaledPenalty = (rawPenalty / totalTypeNodes) * 100;
105
- return Math.max(0, Math.round(baseScore - scaledPenalty));
106
- }
107
-
108
- }
109
-
110
- export function calculatePenalty(metrics: TypeDebtMetrics): number {
111
- return metrics.explicitAny * CONSTANTS.TYPE_DEBT_METRICS.EXPLICIT_ANY_WEIGHT
112
- + metrics.implicitAny * CONSTANTS.TYPE_DEBT_METRICS.IMPLICIT_ANY_WEIGHT
113
- + metrics.asAny * CONSTANTS.TYPE_DEBT_METRICS.AS_ANY_WEIGHT
114
- + metrics.nonNullAssertions * CONSTANTS.TYPE_DEBT_METRICS.NON_NULL_ASSERTION_WEIGHT
115
- + metrics.suppressions * CONSTANTS.TYPE_DEBT_METRICS.SUPPRESSION_WEIGHT;
116
- }
117
-
118
- function isValidType(kind: SyntaxKind): boolean {
119
- return (kind === SyntaxKind.TypeReference ||
120
- kind === SyntaxKind.StringKeyword ||
121
- kind === SyntaxKind.NumberKeyword ||
122
- kind === SyntaxKind.BooleanKeyword ||
123
- kind === SyntaxKind.InterfaceDeclaration ||
124
- kind === SyntaxKind.TypeAliasDeclaration);
125
- }
package/index.ts DELETED
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { getTargetFiles } from "./utils.js";
4
- import { chunkArray, validateDirectory } from "./utils.js";
5
- import { processChunk } from "./core/chunkProcessor.js";
6
- import { FileReport } from "./types.js";
7
- import { CONSTANTS } from "./constants.js";
8
- import { generateMarkdownReport } from "./core/reporter.js";
9
-
10
-
11
-
12
- const args = process.argv.slice(2);
13
-
14
- if (args.includes('--help') || args.includes('-h')) {
15
- printHelp();
16
- process.exit(0);
17
- }
18
-
19
- const target = args[0] || ".";
20
-
21
- runCLI(target).catch((err) => {
22
- console.error(`\n\x1b[31m Error: ${err.message}\x1b[0m\n`);
23
- process.exit(1);
24
- });
25
-
26
-
27
- async function runCLI(targetDir: string) {
28
-
29
- await validateDirectory(targetDir);
30
-
31
- console.log(`\nScanning directory: ${targetDir}`);
32
- const allFiles = getTargetFiles(targetDir);
33
-
34
- if (allFiles.length === 0) {
35
- throw new Error(`No TypeScript files found to process.`);
36
- }
37
-
38
- const fileChunks = chunkArray(allFiles, CONSTANTS.CHUNK_SIZE);
39
-
40
- let allResults: FileReport[] = [];
41
-
42
- for (const [index, chunk] of fileChunks.entries()) {
43
-
44
- const chunkResults = processChunk(chunk);
45
- allResults = allResults.concat(chunkResults);
46
- process.stdout.write(`\rProcessed chunk ${index + 1}/${fileChunks.length}`);
47
- }
48
-
49
- await generateMarkdownReport(allResults, targetDir);
50
-
51
- }
52
-
53
- function printHelp() {
54
- console.log(`
55
- Type Debt Analyzer
56
-
57
- Usage: npx tsx index.ts [directory]
58
-
59
- Examples:
60
- npx tsx index.ts .
61
- npx tsx index.ts ./src
62
- `);
63
- process.exit(0);
64
- }
65
-
66
-
@@ -1,93 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { Project } from "ts-morph";
3
- import { calculatePenalty, calculateScore, getTypeDebt } from "../core/typeDebt";
4
- import { TypeDebtMetrics } from "../types";
5
-
6
- describe("Type Debt Analysis", () => {
7
- it("should correctly identify all forms of type debt", () => {
8
-
9
- const project = new Project({ useInMemoryFileSystem: true });
10
-
11
- const sourceCode = `
12
-
13
- interface ValidUser { id: number; }
14
-
15
- // @ts-ignore
16
- const id: any = 123;
17
-
18
- const user = fetchUser() as any;
19
-
20
- function process(payload) {
21
- console.log(payload!.data);
22
- }
23
- `;
24
-
25
- const sourceFile = project.createSourceFile("test.ts", sourceCode);
26
-
27
- const metrics = getTypeDebt(sourceFile);
28
-
29
- expect(metrics.explicitAny).toBe(1);
30
- expect(metrics.asAny).toBe(1);
31
- expect(metrics.suppressions).toBe(1);
32
- expect(metrics.implicitAny).toBe(1);
33
- expect(metrics.nonNullAssertions).toBe(1);
34
- expect(metrics.validTypes).toBeGreaterThan(0);
35
-
36
- });
37
-
38
- it("should correctly calculate penalty", () => {
39
-
40
- const metrics: TypeDebtMetrics = {
41
- explicitAny: 2,
42
- implicitAny: 3,
43
- asAny: 2,
44
- suppressions: 1,
45
- nonNullAssertions: 1,
46
- validTypes: 40,
47
- score: 0
48
- }
49
-
50
- const penalty = calculatePenalty(metrics);
51
- expect(penalty).toBe(22);
52
-
53
- });
54
-
55
-
56
-
57
- it("should correctly calculate score for ordinary data", () => {
58
-
59
- let metrics: TypeDebtMetrics = {
60
- explicitAny: 2,
61
- implicitAny: 3,
62
- asAny: 2,
63
- suppressions: 1,
64
- nonNullAssertions: 1,
65
- validTypes: 40,
66
- score: 0
67
- }
68
-
69
- metrics.score = calculateScore(metrics);
70
- expect(metrics.score).toBe(37);
71
-
72
- });
73
-
74
- it("should correctly calculate score when no type nodes were found in the file", () => {
75
-
76
- let metrics: TypeDebtMetrics = {
77
- explicitAny: 0,
78
- implicitAny: 0,
79
- asAny: 0,
80
- suppressions: 0,
81
- nonNullAssertions: 0,
82
- validTypes: 0,
83
- score: 0
84
- }
85
-
86
- metrics.score = calculateScore(metrics);
87
- expect(metrics.score).toBe(100);
88
-
89
- });
90
-
91
-
92
- })
93
-
package/tsconfig.json DELETED
@@ -1,46 +0,0 @@
1
- {
2
- // Visit https://aka.ms/tsconfig to read more about this file
3
- "compilerOptions": {
4
- // File Layout
5
- // "rootDir": "./src",
6
- // "outDir": "./dist",
7
-
8
- // Environment Settings
9
- // See also https://aka.ms/tsconfig/module
10
- "module": "CommonJS",
11
- "esModuleInterop": true,
12
- "target": "es2016",
13
- "types": [],
14
- // For nodejs:
15
- // "lib": ["esnext"],
16
- // "types": ["node"],
17
- // and npm install -D @types/node
18
-
19
- // Other Outputs
20
- "sourceMap": true,
21
- "declaration": true,
22
- "declarationMap": true,
23
-
24
- // Stricter Typechecking Options
25
- "noUncheckedIndexedAccess": true,
26
- "exactOptionalPropertyTypes": true,
27
-
28
-
29
- // Style Options
30
- // "noImplicitReturns": true,
31
- // "noImplicitOverride": true,
32
- // "noUnusedLocals": true,
33
- // "noUnusedParameters": true,
34
- // "noFallthroughCasesInSwitch": true,
35
- // "noPropertyAccessFromIndexSignature": true,
36
-
37
- // Recommended Options
38
- "strict": true,
39
- "jsx": "react-jsx",
40
- "verbatimModuleSyntax": false,
41
- "isolatedModules": true,
42
- "noUncheckedSideEffectImports": true,
43
- "moduleDetection": "force",
44
- "skipLibCheck": true,
45
- }
46
- }
@@ -1,22 +0,0 @@
1
- Type Debt Report
2
-
3
- > Generated on: Mon, 29 Jun 2026 14:06:35 GMT
4
-
5
- ## Summary
6
- - Total Files Scanned: 709
7
- - Average Score: 73/100
8
-
9
- ## Top 10 Worst Offenders
10
-
11
- | Score | File Path | Suppressions | Implicit `any` | Explicit `any` | `as any` | Non-Null (`!`) |
12
- | :---: | :--- | :---: | :---: | :---: | :---: | :---: |
13
- | 0 | `compiler/visitorPublic.ts` | 0 | 810 | 1 | 0 | 4 |
14
- | 0 | `harness/harnessGlobals.ts` | 0 | 3 | 1 | 0 | 0 |
15
- | 0 | `lib/es2017.intl.d.ts` | 0 | 0 | 11 | 0 | 0 |
16
- | 0 | `server/moduleSpecifierCache.ts` | 0 | 22 | 0 | 0 | 0 |
17
- | 0 | `services/codefixes/addEmptyExportDeclaration.ts` | 0 | 1 | 0 | 0 | 0 |
18
- | 0 | `services/codefixes/addMissingDeclareProperty.ts` | 0 | 2 | 0 | 0 | 0 |
19
- | 0 | `services/codefixes/addMissingInvocationForDecorator.ts` | 0 | 2 | 0 | 0 | 1 |
20
- | 0 | `services/codefixes/addMissingResolutionModeImportAttribute.ts` | 0 | 2 | 0 | 0 | 1 |
21
- | 0 | `services/codefixes/disableJsDiagnostics.ts` | 3 | 2 | 0 | 0 | 0 |
22
- | 0 | `services/codefixes/fixAddMissingNewOperator.ts` | 0 | 2 | 0 | 0 | 0 |
package/types.ts DELETED
@@ -1,14 +0,0 @@
1
- export interface FileReport {
2
- filePath: string;
3
- typeDebtMetrics: TypeDebtMetrics;
4
- }
5
-
6
- export interface TypeDebtMetrics {
7
- explicitAny: number;
8
- implicitAny: number;
9
- asAny: number;
10
- suppressions: number;
11
- nonNullAssertions: number;
12
- validTypes: number;
13
- score: number;
14
- }
package/utils.ts DELETED
@@ -1,43 +0,0 @@
1
- import { fdir } from "fdir";
2
- import { stat } from "fs/promises";
3
-
4
-
5
- export function getTargetFiles(directoryPath: string): string[] {
6
- const crawler = new fdir()
7
- .withFullPaths()
8
- .exclude((dirName) =>
9
- dirName === "node_modules" ||
10
- dirName === "dist" ||
11
- dirName === "build" ||
12
- dirName === ".git"
13
- )
14
- .filter((path) => path.endsWith(".ts") || path.endsWith(".tsx"))
15
- .crawl(directoryPath);
16
-
17
- return crawler.sync() as string[];
18
- }
19
-
20
-
21
- export function chunkArray<T>(array: T[], chunkSize: number): T[][] {
22
- const chunks: T[][] = [];
23
- for (let i = 0; i < array.length; i += chunkSize) {
24
- chunks.push(array.slice(i, i + chunkSize));
25
- }
26
- return chunks;
27
- }
28
-
29
-
30
- export async function validateDirectory(path: string) {
31
-
32
- let stats;
33
-
34
- try {
35
- stats = await stat(path);
36
- } catch {
37
- throw new Error(`Directory "${path}" does not exist.`);
38
- }
39
-
40
- if (!stats.isDirectory()) {
41
- throw new Error(`"${path}" is not a directory.`);
42
- }
43
- }