ts-unused 1.0.2 → 1.0.3

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,27 @@
1
+ /**
2
+ * Extracts TODO comment text from the leading comments of a property.
3
+ * Returns undefined if no TODO comment is found.
4
+ */
5
+ export function extractTodoComment(prop) {
6
+ const leadingComments = prop.getLeadingCommentRanges();
7
+ for (const comment of leadingComments) {
8
+ const commentText = comment.getText();
9
+ // Match single-line comments: // TODO ...
10
+ const singleLineMatch = commentText.match(/\/\/\s*TODO\s+(.+)/i);
11
+ if (singleLineMatch) {
12
+ const todoText = singleLineMatch[1];
13
+ if (todoText) {
14
+ return todoText.trim();
15
+ }
16
+ }
17
+ // Match multi-line comments: /* TODO ... */ or /** TODO ... */
18
+ const multiLineMatch = commentText.match(/\/\*+\s*TODO\s+(.+?)\*\//is);
19
+ if (multiLineMatch) {
20
+ const todoText = multiLineMatch[1];
21
+ if (todoText) {
22
+ return todoText.trim().replace(/\s+/g, " ");
23
+ }
24
+ }
25
+ }
26
+ return undefined;
27
+ }
@@ -1,3 +1,6 @@
1
1
  import type { Project } from "ts-morph";
2
2
  import type { IsTestFileFn, NeverReturnedTypeResult } from "./types";
3
- export declare function findNeverReturnedTypes(project: Project, tsConfigDir: string, isTestFile: IsTestFileFn, onProgress?: (filePath: string) => void, targetFilePath?: string): NeverReturnedTypeResult[];
3
+ export interface FindNeverReturnedTypesOptions {
4
+ ignoreFilePatterns?: string[];
5
+ }
6
+ export declare function findNeverReturnedTypes(project: Project, tsConfigDir: string, isTestFile: IsTestFileFn, onProgress?: (filePath: string) => void, targetFilePath?: string, options?: FindNeverReturnedTypesOptions): NeverReturnedTypeResult[];
@@ -0,0 +1,36 @@
1
+ import path from "node:path";
2
+ import { SyntaxKind } from "ts-morph";
3
+ import { analyzeFunctionReturnTypes } from "./analyzeFunctionReturnTypes";
4
+ import { hasNoCheck } from "./hasNoCheck";
5
+ import { matchesFilePattern } from "./patternMatcher";
6
+ export function findNeverReturnedTypes(project, tsConfigDir, isTestFile, onProgress, targetFilePath, options = {}) {
7
+ const { ignoreFilePatterns = [] } = options;
8
+ const results = [];
9
+ for (const sourceFile of project.getSourceFiles()) {
10
+ if (isTestFile(sourceFile)) {
11
+ continue;
12
+ }
13
+ if (hasNoCheck(sourceFile)) {
14
+ continue;
15
+ }
16
+ const filePath = sourceFile.getFilePath();
17
+ if (targetFilePath && filePath !== targetFilePath) {
18
+ continue;
19
+ }
20
+ // Check if file matches ignore patterns
21
+ if (ignoreFilePatterns.length > 0 && matchesFilePattern(filePath, ignoreFilePatterns)) {
22
+ continue;
23
+ }
24
+ if (onProgress) {
25
+ const relativePath = path.relative(tsConfigDir, filePath);
26
+ onProgress(relativePath);
27
+ }
28
+ // Get all function declarations
29
+ const functions = sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration);
30
+ for (const func of functions) {
31
+ const funcResults = analyzeFunctionReturnTypes(func, sourceFile, tsConfigDir);
32
+ results.push(...funcResults);
33
+ }
34
+ }
35
+ return results;
36
+ }
@@ -0,0 +1,60 @@
1
+ import { SyntaxKind } from "ts-morph";
2
+ function checkInterfaceProperties(iface, propName, propType, originalProp, equivalentProps) {
3
+ const properties = iface.getProperties();
4
+ for (const p of properties) {
5
+ if (p === originalProp) {
6
+ continue;
7
+ }
8
+ if (p.getName() === propName) {
9
+ const pType = p.getType().getText();
10
+ if (pType === propType) {
11
+ equivalentProps.push(p);
12
+ }
13
+ }
14
+ }
15
+ }
16
+ function checkTypeAliasProperties(typeAlias, propName, propType, originalProp, equivalentProps) {
17
+ const typeNode = typeAlias.getTypeNode();
18
+ if (!typeNode || typeNode.getKind() !== SyntaxKind.TypeLiteral) {
19
+ return;
20
+ }
21
+ const properties = typeNode.getChildren().filter((child) => child.getKind() === SyntaxKind.PropertySignature);
22
+ for (const p of properties) {
23
+ if (p === originalProp) {
24
+ continue;
25
+ }
26
+ if (p.getKind() === SyntaxKind.PropertySignature && p.getName() === propName) {
27
+ const pType = p.getType().getText();
28
+ if (pType === propType) {
29
+ equivalentProps.push(p);
30
+ }
31
+ }
32
+ }
33
+ }
34
+ /**
35
+ * Finds properties that are structurally equivalent to the given property.
36
+ * Two properties are structurally equivalent if they have:
37
+ * - Same property name
38
+ * - Same type signature
39
+ *
40
+ * This handles cases where properties are re-declared across interfaces,
41
+ * such as when one interface extends another and re-declares properties,
42
+ * or when spread operations flow values between structurally similar types.
43
+ */
44
+ export function findStructurallyEquivalentProperties(prop, project) {
45
+ const propName = prop.getName();
46
+ const propType = prop.getType().getText();
47
+ const equivalentProps = [];
48
+ const sourceFiles = project.getSourceFiles();
49
+ for (const sourceFile of sourceFiles) {
50
+ const interfaces = sourceFile.getInterfaces();
51
+ for (const iface of interfaces) {
52
+ checkInterfaceProperties(iface, propName, propType, prop, equivalentProps);
53
+ }
54
+ const typeAliases = sourceFile.getTypeAliases();
55
+ for (const typeAlias of typeAliases) {
56
+ checkTypeAliasProperties(typeAlias, propName, propType, prop, equivalentProps);
57
+ }
58
+ }
59
+ return equivalentProps;
60
+ }
@@ -1,3 +1,7 @@
1
1
  import type { Project } from "ts-morph";
2
+ import { type CheckExportOptions } from "./checkExportUsage";
2
3
  import type { IsTestFileFn, UnusedExportResult } from "./types";
3
- export declare function findUnusedExports(project: Project, tsConfigDir: string, isTestFile: IsTestFileFn, onProgress?: (filePath: string) => void, targetFilePath?: string): UnusedExportResult[];
4
+ export interface FindUnusedExportsOptions extends CheckExportOptions {
5
+ ignoreFilePatterns?: string[];
6
+ }
7
+ export declare function findUnusedExports(project: Project, tsConfigDir: string, isTestFile: IsTestFileFn, onProgress?: (filePath: string) => void, targetFilePath?: string, options?: FindUnusedExportsOptions): UnusedExportResult[];
@@ -0,0 +1,36 @@
1
+ import path from "node:path";
2
+ import { checkExportUsage } from "./checkExportUsage";
3
+ import { hasNoCheck } from "./hasNoCheck";
4
+ import { matchesFilePattern } from "./patternMatcher";
5
+ export function findUnusedExports(project, tsConfigDir, isTestFile, onProgress, targetFilePath, options = {}) {
6
+ const { ignoreFilePatterns = [], ...checkOptions } = options;
7
+ const results = [];
8
+ for (const sourceFile of project.getSourceFiles()) {
9
+ if (isTestFile(sourceFile)) {
10
+ continue;
11
+ }
12
+ if (hasNoCheck(sourceFile)) {
13
+ continue;
14
+ }
15
+ const filePath = sourceFile.getFilePath();
16
+ if (targetFilePath && filePath !== targetFilePath) {
17
+ continue;
18
+ }
19
+ // Check if file should be ignored based on patterns
20
+ if (ignoreFilePatterns.length > 0 && matchesFilePattern(filePath, ignoreFilePatterns)) {
21
+ continue;
22
+ }
23
+ if (onProgress) {
24
+ const relativePath = path.relative(tsConfigDir, filePath);
25
+ onProgress(relativePath);
26
+ }
27
+ const exports = sourceFile.getExportedDeclarations();
28
+ for (const [exportName, declarations] of exports.entries()) {
29
+ const unusedExport = checkExportUsage(exportName, declarations, sourceFile, tsConfigDir, isTestFile, checkOptions);
30
+ if (unusedExport) {
31
+ results.push(unusedExport);
32
+ }
33
+ }
34
+ }
35
+ return results;
36
+ }
@@ -1,3 +1,7 @@
1
1
  import type { Project } from "ts-morph";
2
+ import { type AnalyzeInterfacesOptions } from "./analyzeInterfaces";
2
3
  import type { IsTestFileFn, UnusedPropertyResult } from "./types";
3
- export declare function findUnusedProperties(project: Project, tsConfigDir: string, isTestFile: IsTestFileFn, onProgress?: (filePath: string) => void, targetFilePath?: string): UnusedPropertyResult[];
4
+ export interface FindUnusedPropertiesOptions extends AnalyzeInterfacesOptions {
5
+ ignoreFilePatterns?: string[];
6
+ }
7
+ export declare function findUnusedProperties(project: Project, tsConfigDir: string, isTestFile: IsTestFileFn, onProgress?: (filePath: string) => void, targetFilePath?: string, options?: FindUnusedPropertiesOptions): UnusedPropertyResult[];
@@ -0,0 +1,32 @@
1
+ import path from "node:path";
2
+ import { analyzeInterfaces } from "./analyzeInterfaces";
3
+ import { analyzeTypeAliases } from "./analyzeTypeAliases";
4
+ import { hasNoCheck } from "./hasNoCheck";
5
+ import { matchesFilePattern } from "./patternMatcher";
6
+ export function findUnusedProperties(project, tsConfigDir, isTestFile, onProgress, targetFilePath, options = {}) {
7
+ const { ignoreFilePatterns = [], ...analyzeOptions } = options;
8
+ const results = [];
9
+ for (const sourceFile of project.getSourceFiles()) {
10
+ if (isTestFile(sourceFile)) {
11
+ continue;
12
+ }
13
+ if (hasNoCheck(sourceFile)) {
14
+ continue;
15
+ }
16
+ const filePath = sourceFile.getFilePath();
17
+ if (targetFilePath && filePath !== targetFilePath) {
18
+ continue;
19
+ }
20
+ // Check if file should be ignored based on patterns
21
+ if (ignoreFilePatterns.length > 0 && matchesFilePattern(filePath, ignoreFilePatterns)) {
22
+ continue;
23
+ }
24
+ if (onProgress) {
25
+ const relativePath = path.relative(tsConfigDir, filePath);
26
+ onProgress(relativePath);
27
+ }
28
+ analyzeInterfaces(sourceFile, tsConfigDir, isTestFile, results, project, analyzeOptions);
29
+ analyzeTypeAliases(sourceFile, tsConfigDir, isTestFile, results, project, analyzeOptions);
30
+ }
31
+ return results;
32
+ }