@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.
- package/dist/constants.d.ts +10 -0
- package/{constants.ts → dist/constants.js} +11 -15
- package/dist/constants.js.map +1 -0
- package/dist/core/chunkProcessor.d.ts +2 -0
- package/dist/core/chunkProcessor.js +20 -0
- package/dist/core/chunkProcessor.js.map +1 -0
- package/dist/core/reporter.d.ts +2 -0
- package/dist/core/reporter.js +33 -0
- package/dist/core/reporter.js.map +1 -0
- package/dist/core/typeDebt.d.ts +5 -0
- package/dist/core/typeDebt.js +93 -0
- package/dist/core/typeDebt.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40 -22
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +33 -0
- package/dist/utils.js.map +1 -0
- package/package.json +10 -5
- package/core/chunkProcessor.ts +0 -27
- package/core/reporter.ts +0 -46
- package/core/typeDebt.ts +0 -125
- package/index.ts +0 -66
- package/tests/typeDebt.test.ts +0 -93
- package/tsconfig.json +0 -46
- package/type-debt-report.md +0 -22
- package/types.ts +0 -14
- package/utils.ts +0 -43
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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,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,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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"}
|
package/dist/index.d.ts
ADDED
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
37
|
+
Usage: npx type-debt [directory]
|
|
23
38
|
|
|
24
39
|
Examples:
|
|
25
|
-
npx
|
|
26
|
-
npx
|
|
27
|
-
`)
|
|
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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":""}
|
package/dist/utils.d.ts
ADDED
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.
|
|
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": "
|
|
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
|
+
}
|
package/core/chunkProcessor.ts
DELETED
|
@@ -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
|
-
|
package/tests/typeDebt.test.ts
DELETED
|
@@ -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
|
-
}
|
package/type-debt-report.md
DELETED
|
@@ -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
|
-
}
|