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,48 @@
1
+ import { findStructurallyEquivalentProperties } from "./findStructurallyEquivalentProperties";
2
+ function countPropertyReferences(prop, isTestFile) {
3
+ const references = prop.findReferences();
4
+ let totalReferences = 0;
5
+ let testReferences = 0;
6
+ let nonTestReferences = 0;
7
+ for (const refGroup of references) {
8
+ const refs = refGroup.getReferences();
9
+ for (const ref of refs) {
10
+ const refSourceFile = ref.getSourceFile();
11
+ totalReferences++;
12
+ if (isTestFile(refSourceFile)) {
13
+ testReferences++;
14
+ }
15
+ else {
16
+ nonTestReferences++;
17
+ }
18
+ }
19
+ }
20
+ const result = {
21
+ totalReferences,
22
+ testReferences,
23
+ nonTestReferences,
24
+ };
25
+ return result;
26
+ }
27
+ export function isPropertyUnused(prop, isTestFile, project) {
28
+ const counts = countPropertyReferences(prop, isTestFile);
29
+ // A property is unused if it only has 1 reference (the definition itself)
30
+ // A property is test-only if it has references only from tests
31
+ const onlyUsedInTests = counts.nonTestReferences === 1 && counts.testReferences > 0;
32
+ if (counts.totalReferences > 1 && !onlyUsedInTests) {
33
+ const result = { isUnusedOrTestOnly: false, onlyUsedInTests: false };
34
+ return result; // Used in production code
35
+ }
36
+ // Check structurally equivalent properties
37
+ const equivalentProps = findStructurallyEquivalentProperties(prop, project);
38
+ for (const equivalentProp of equivalentProps) {
39
+ const equivalentCounts = countPropertyReferences(equivalentProp, isTestFile);
40
+ const equivalentOnlyUsedInTests = equivalentCounts.nonTestReferences === 1 && equivalentCounts.testReferences > 0;
41
+ if (equivalentCounts.totalReferences > 1 && !equivalentOnlyUsedInTests) {
42
+ const result = { isUnusedOrTestOnly: false, onlyUsedInTests: false };
43
+ return result;
44
+ }
45
+ }
46
+ const result = { isUnusedOrTestOnly: true, onlyUsedInTests };
47
+ return result;
48
+ }
@@ -1,2 +1,14 @@
1
1
  import type { SourceFile } from "ts-morph";
2
+ import type { IsTestFileFn } from "./types";
3
+ /**
4
+ * Default test file detection using built-in patterns.
5
+ * Checks for test file extensions and __tests__ directory.
6
+ */
2
7
  export declare function isTestFile(sourceFile: SourceFile): boolean;
8
+ /**
9
+ * Creates a test file detection function based on custom patterns.
10
+ *
11
+ * @param patterns - Array of glob patterns to identify test files
12
+ * @returns A function that checks if a source file is a test file
13
+ */
14
+ export declare function createIsTestFile(patterns?: string[]): IsTestFileFn;
@@ -0,0 +1,23 @@
1
+ import { defaultConfig } from "./config";
2
+ import { matchesFilePattern } from "./patternMatcher";
3
+ const TEST_FILE_EXTENSIONS = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
4
+ /**
5
+ * Default test file detection using built-in patterns.
6
+ * Checks for test file extensions and __tests__ directory.
7
+ */
8
+ export function isTestFile(sourceFile) {
9
+ const filePath = sourceFile.getFilePath();
10
+ return filePath.includes("__tests__") || TEST_FILE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
11
+ }
12
+ /**
13
+ * Creates a test file detection function based on custom patterns.
14
+ *
15
+ * @param patterns - Array of glob patterns to identify test files
16
+ * @returns A function that checks if a source file is a test file
17
+ */
18
+ export function createIsTestFile(patterns = defaultConfig.testFilePatterns) {
19
+ return (sourceFile) => {
20
+ const filePath = sourceFile.getFilePath();
21
+ return matchesFilePattern(filePath, patterns);
22
+ };
23
+ }
@@ -0,0 +1,20 @@
1
+ import { type UnusedConfig } from "./config";
2
+ /**
3
+ * Loads configuration from the target directory.
4
+ * Looks for `unused.config.ts` in the specified directory.
5
+ * If not found, returns default configuration.
6
+ *
7
+ * @param targetDir - The directory to look for the config file
8
+ * @param configPath - Optional explicit path to config file
9
+ * @returns The merged configuration
10
+ */
11
+ export declare function loadConfig(targetDir: string, configPath?: string): Promise<Required<UnusedConfig>>;
12
+ /**
13
+ * Synchronous version of loadConfig for backward compatibility.
14
+ * Uses Bun's sync import capabilities.
15
+ *
16
+ * @param targetDir - The directory to look for the config file
17
+ * @param configPath - Optional explicit path to config file
18
+ * @returns The merged configuration
19
+ */
20
+ export declare function loadConfigSync(targetDir: string, configPath?: string): Required<UnusedConfig>;
@@ -0,0 +1,54 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { defaultConfig, mergeConfig } from "./config";
4
+ const CONFIG_FILE_NAME = "unused.config.ts";
5
+ /**
6
+ * Loads configuration from the target directory.
7
+ * Looks for `unused.config.ts` in the specified directory.
8
+ * If not found, returns default configuration.
9
+ *
10
+ * @param targetDir - The directory to look for the config file
11
+ * @param configPath - Optional explicit path to config file
12
+ * @returns The merged configuration
13
+ */
14
+ export async function loadConfig(targetDir, configPath) {
15
+ const resolvedConfigPath = configPath ? path.resolve(configPath) : path.join(targetDir, CONFIG_FILE_NAME);
16
+ if (!fs.existsSync(resolvedConfigPath)) {
17
+ return { ...defaultConfig };
18
+ }
19
+ try {
20
+ // Use dynamic import to load the TypeScript config file
21
+ // Bun natively supports TypeScript imports
22
+ const configModule = await import(resolvedConfigPath);
23
+ const userConfig = configModule.default ?? configModule;
24
+ return mergeConfig(userConfig);
25
+ }
26
+ catch (error) {
27
+ const errorMessage = error instanceof Error ? error.message : String(error);
28
+ throw new Error(`Failed to load config from ${resolvedConfigPath}: ${errorMessage}`);
29
+ }
30
+ }
31
+ /**
32
+ * Synchronous version of loadConfig for backward compatibility.
33
+ * Uses Bun's sync import capabilities.
34
+ *
35
+ * @param targetDir - The directory to look for the config file
36
+ * @param configPath - Optional explicit path to config file
37
+ * @returns The merged configuration
38
+ */
39
+ export function loadConfigSync(targetDir, configPath) {
40
+ const resolvedConfigPath = configPath ? path.resolve(configPath) : path.join(targetDir, CONFIG_FILE_NAME);
41
+ if (!fs.existsSync(resolvedConfigPath)) {
42
+ return { ...defaultConfig };
43
+ }
44
+ try {
45
+ // Bun supports synchronous require of TypeScript files
46
+ const configModule = require(resolvedConfigPath);
47
+ const userConfig = configModule.default ?? configModule;
48
+ return mergeConfig(userConfig);
49
+ }
50
+ catch (error) {
51
+ const errorMessage = error instanceof Error ? error.message : String(error);
52
+ throw new Error(`Failed to load config from ${resolvedConfigPath}: ${errorMessage}`);
53
+ }
54
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Converts a glob-like pattern to a RegExp.
3
+ * Supports:
4
+ * - `*` matches any characters except `/`
5
+ * - `**` matches any characters including `/`
6
+ * - `?` matches any single character
7
+ */
8
+ export declare function patternToRegex(pattern: string): RegExp;
9
+ /**
10
+ * Checks if a string matches any of the given patterns.
11
+ * Patterns can be exact strings or glob-like patterns.
12
+ *
13
+ * @param value - The string to test
14
+ * @param patterns - Array of patterns to match against
15
+ * @returns true if the value matches any pattern
16
+ */
17
+ export declare function matchesPattern(value: string, patterns: string[]): boolean;
18
+ /**
19
+ * Checks if a file path matches any of the given patterns.
20
+ * Normalizes the path before matching.
21
+ *
22
+ * @param filePath - The file path to test (can be absolute or relative)
23
+ * @param patterns - Array of glob patterns to match against
24
+ * @returns true if the file matches any pattern
25
+ */
26
+ export declare function matchesFilePattern(filePath: string, patterns: string[]): boolean;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Converts a glob-like pattern to a RegExp.
3
+ * Supports:
4
+ * - `*` matches any characters except `/`
5
+ * - `**` matches any characters including `/`
6
+ * - `?` matches any single character
7
+ */
8
+ export function patternToRegex(pattern) {
9
+ // Escape special regex characters except * and ?
10
+ let regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
11
+ // Use placeholders to handle ** before * to avoid conflicts
12
+ // Replace ** with a unique placeholder first
13
+ const DOUBLE_STAR_PLACEHOLDER = "<<DOUBLESTAR>>";
14
+ regexStr = regexStr.replace(/\*\*/g, DOUBLE_STAR_PLACEHOLDER);
15
+ // Convert single * to match anything except /
16
+ regexStr = regexStr.replace(/\*/g, "[^/]*");
17
+ // Convert ** placeholder back to match anything including /
18
+ regexStr = regexStr.split(DOUBLE_STAR_PLACEHOLDER).join(".*");
19
+ // Convert ? to match any single character
20
+ regexStr = regexStr.replace(/\?/g, ".");
21
+ return new RegExp(`^${regexStr}$`);
22
+ }
23
+ /**
24
+ * Checks if a string matches any of the given patterns.
25
+ * Patterns can be exact strings or glob-like patterns.
26
+ *
27
+ * @param value - The string to test
28
+ * @param patterns - Array of patterns to match against
29
+ * @returns true if the value matches any pattern
30
+ */
31
+ export function matchesPattern(value, patterns) {
32
+ for (const pattern of patterns) {
33
+ // Check exact match first
34
+ if (pattern === value) {
35
+ return true;
36
+ }
37
+ // Check glob pattern
38
+ if (pattern.includes("*") || pattern.includes("?")) {
39
+ const regex = patternToRegex(pattern);
40
+ if (regex.test(value)) {
41
+ return true;
42
+ }
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * Checks if a file path matches any of the given patterns.
49
+ * Normalizes the path before matching.
50
+ *
51
+ * @param filePath - The file path to test (can be absolute or relative)
52
+ * @param patterns - Array of glob patterns to match against
53
+ * @returns true if the file matches any pattern
54
+ */
55
+ export function matchesFilePattern(filePath, patterns) {
56
+ // Normalize path separators
57
+ const normalizedPath = filePath.replace(/\\/g, "/");
58
+ for (const pattern of patterns) {
59
+ const normalizedPattern = pattern.replace(/\\/g, "/");
60
+ // Check if pattern matches anywhere in the path
61
+ if (normalizedPattern.startsWith("**/")) {
62
+ // Pattern like **/*.test.ts should match any path ending with .test.ts
63
+ const suffixPattern = normalizedPattern.slice(3);
64
+ const regex = patternToRegex(`**/${suffixPattern}`);
65
+ if (regex.test(normalizedPath)) {
66
+ return true;
67
+ }
68
+ // Also check if just the suffix matches the end
69
+ const suffixRegex = patternToRegex(suffixPattern);
70
+ const fileName = normalizedPath.split("/").pop() ?? "";
71
+ if (suffixRegex.test(fileName)) {
72
+ return true;
73
+ }
74
+ }
75
+ else {
76
+ const regex = patternToRegex(normalizedPattern);
77
+ if (regex.test(normalizedPath)) {
78
+ return true;
79
+ }
80
+ }
81
+ }
82
+ return false;
83
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-unused",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Find unused exports and properties in TypeScript projects",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -20,9 +20,9 @@
20
20
  "type": "module",
21
21
  "scripts": {
22
22
  "clean": "rm -rf dist",
23
- "build:js": "bun build src/cli.ts --outdir dist --target node --external ts-morph --external typescript && bun build src/index.ts --outdir dist --target node --external ts-morph --external typescript",
24
- "build:types": "tsgo --project tsconfig.build.json",
25
- "build": "bun clean && bun build:js && bun build:types",
23
+ "build:cli": "bun build src/cli.ts --outdir dist --target node --external ts-morph --external typescript",
24
+ "build:lib": "tsgo --project tsconfig.build.json",
25
+ "build": "bun clean && bun build:cli && bun build:lib",
26
26
  "typecheck": "tsgo --noEmit",
27
27
  "lint": "bun run fix && biome check src",
28
28
  "fix": "biome check --write --unsafe src",