From fbba6ceb29279a8685f6748622e58e152f377188 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Fri, 3 Apr 2026 20:53:26 -0500 Subject: [PATCH 1/5] chore(types): clean up any and loose types. --- src/cli/index.ts | 36 +++++++++++++++++++++--------------- src/cli/internalMode.ts | 4 +++- src/cli/parser.ts | 9 +++++++-- src/core/analyzer.ts | 7 ++++++- src/index.ts | 1 + 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index 7176438..6db6825 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -16,7 +16,7 @@ export function isNodeError(error: unknown): error is NodeJS.ErrnoException { typeof error === "object" && error !== null && "code" in error && - typeof (error as any)?.code === "string" + typeof (error as NodeJS.ErrnoException).code === "string" ); } @@ -67,7 +67,7 @@ function resolveAndValidateCwd(rawCwd?: string): string { const args = parseArgs(process.argv.slice(2)); const [command, targetPath] = args._; - const cwd = resolveAndValidateCwd(args.cwd); + const cwd = resolveAndValidateCwd(args.cwd as string | undefined); // Internal single-file analysis mode if (command === "internal") { @@ -134,32 +134,38 @@ function resolveAndValidateCwd(rawCwd?: string): string { } // Run full-project analysis (existing behavior) - const results: any = analyzeProject(projectRoot, args); + const ignorePatterns = args.ignore + ? (Array.isArray(args.ignore) ? args.ignore : [args.ignore]).map(String) + : []; + const results = analyzeProject(projectRoot, { + ignore: ignorePatterns, + trackAllLocals: args["track-all-locals"] === true, + }); + + // Optional type analysis results + let unusedExportedTypes: { file: string; name: string }[] = []; + let unusedLocalTypes: { file: string; name: string }[] = []; // Optional: run types analysis ONLY when requested if (typesMode) { const { walkFiles } = await import("../core/fileSystem/walkFiles"); - const { loadTSConfig } = await import("../core/tsconfig/tsconfigLoader"); - const { buildTypeUsageGraph } = await import("../core/type/buildTypeUsageGraph"); - const ignorePatterns = (args.ignore || []).map(String); const files = walkFiles(projectRoot, ignorePatterns); const tsConfig = loadTSConfig(projectRoot); - const typeGraph = buildTypeUsageGraph(files, tsConfig); - results.unusedExportedTypes = typeGraph.unusedExportedTypes; - results.unusedLocalTypes = typeGraph.unusedLocalTypes; - - // Attach into verbose graphs too - if (results.graphs) results.graphs.types = typeGraph; + unusedExportedTypes = typeGraph.unusedExportedTypes; + unusedLocalTypes = typeGraph.unusedLocalTypes; } // JSON output if (args.json) { - log(JSON.stringify(results, null, 2)); + const jsonOutput = typesMode + ? { ...results, unusedExportedTypes, unusedLocalTypes } + : results; + log(JSON.stringify(jsonOutput, null, 2)); process.exit(0); } @@ -175,8 +181,8 @@ function resolveAndValidateCwd(rawCwd?: string): string { if (typesMode) { const { logUnusedExportedTypes, logUnusedLocalTypes } = await import("./typesFormat.js"); - logUnusedExportedTypes(results.unusedExportedTypes ?? []); - logUnusedLocalTypes(results.unusedLocalTypes ?? []); + logUnusedExportedTypes(unusedExportedTypes); + logUnusedLocalTypes(unusedLocalTypes); } if (args.verbose) logVerbose(results.graphs); diff --git a/src/cli/internalMode.ts b/src/cli/internalMode.ts index 1d8100e..13540b7 100644 --- a/src/cli/internalMode.ts +++ b/src/cli/internalMode.ts @@ -14,7 +14,9 @@ import { heading } from "./format"; * • referenced identifiers * • unused identifiers */ -export async function runInternalMode(filePath: string, args: any): Promise { +import type { ParsedArgs } from "./parser"; + +export async function runInternalMode(filePath: string, args: ParsedArgs): Promise { const SILENT_MODE = !!args.silent; let sourceText: string; diff --git a/src/cli/parser.ts b/src/cli/parser.ts index 7d29ae3..6764417 100644 --- a/src/cli/parser.ts +++ b/src/cli/parser.ts @@ -14,8 +14,13 @@ * -x * positional args */ -export function parseArgs(argv: string[]) { - const args: Record = { _: [] }; +export interface ParsedArgs { + _: string[]; + [key: string]: string | boolean | string[]; +} + +export function parseArgs(argv: string[]): ParsedArgs { + const args: ParsedArgs = { _: [] }; for (let i = 0; i < argv.length; i++) { const token = argv[i]; diff --git a/src/core/analyzer.ts b/src/core/analyzer.ts index fb629d7..12d0aa9 100644 --- a/src/core/analyzer.ts +++ b/src/core/analyzer.ts @@ -17,10 +17,15 @@ import { buildImportGraph } from "./imports/buildImportGraph"; import { loadTSConfig } from "./tsconfig/tsconfigLoader"; import { buildUsageGraph } from "./usage/buildUsageGraph"; +export interface AnalyzeOptions { + ignore?: string[]; + trackAllLocals?: boolean; +} + // Cache tsconfig loads per project root const tsconfigCache = new Map>(); -export function analyzeProject(root: string, options: any) { +export function analyzeProject(root: string, options: AnalyzeOptions = {}) { const ignorePatterns = (options.ignore || []).map(String); const trackAllLocals = options.trackAllLocals === true; diff --git a/src/index.ts b/src/index.ts index 9b0bd95..6ce6bdf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export { analyzeProject } from "./core/analyzer"; +export type { AnalyzeOptions } from "./core/analyzer"; // Re-export common result types for convenience export type { LocalUsageResult } from "./core/locals/analyzeLocalUsage"; From f6e350fe99d8e423d0bba6319fa452153bb210c8 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Fri, 3 Apr 2026 20:57:46 -0500 Subject: [PATCH 2/5] chore(core): extract shared module resolution. --- src/core/config/loadConfig.ts | 67 +++++++++++ src/core/exports/resolveExportGraph.ts | 46 +------- src/core/imports/buildImportGraph.ts | 139 ++--------------------- src/core/resolution/resolveModule.ts | 150 +++++++++++++++++++++++++ src/core/type/buildTypeUsageGraph.ts | 138 +---------------------- 5 files changed, 235 insertions(+), 305 deletions(-) create mode 100644 src/core/config/loadConfig.ts create mode 100644 src/core/resolution/resolveModule.ts diff --git a/src/core/config/loadConfig.ts b/src/core/config/loadConfig.ts new file mode 100644 index 0000000..6ce5388 --- /dev/null +++ b/src/core/config/loadConfig.ts @@ -0,0 +1,67 @@ +import fs from "node:fs"; +import path from "node:path"; + +export interface DevoidConfig { + ignore?: string[]; + entry?: string[]; + trackAllLocals?: boolean; + types?: boolean; + failOnUnused?: boolean; +} + +const CONFIG_FILENAMES = ["devoid.config.json", ".devoidrc", ".devoidrc.json"]; + +/** + * Load a devoid config file from the project root. + * Searches for devoid.config.json, .devoidrc, or .devoidrc.json. + * Returns an empty config if no file is found. + */ +export function loadConfig(projectRoot: string): DevoidConfig { + for (const filename of CONFIG_FILENAMES) { + const configPath = path.join(projectRoot, filename); + + if (!fs.existsSync(configPath)) continue; + + try { + const raw = fs.readFileSync(configPath, "utf8"); + const parsed = JSON.parse(raw); + return validateConfig(parsed, configPath); + } catch { + // Invalid JSON — skip silently + continue; + } + } + + return {}; +} + +function validateConfig(raw: unknown, configPath: string): DevoidConfig { + if (typeof raw !== "object" || raw === null || Array.isArray(raw)) { + return {}; + } + + const obj = raw as Record; + const config: DevoidConfig = {}; + + if (Array.isArray(obj.ignore)) { + config.ignore = obj.ignore.filter((v): v is string => typeof v === "string"); + } + + if (Array.isArray(obj.entry)) { + config.entry = obj.entry.filter((v): v is string => typeof v === "string"); + } + + if (typeof obj.trackAllLocals === "boolean") { + config.trackAllLocals = obj.trackAllLocals; + } + + if (typeof obj.types === "boolean") { + config.types = obj.types; + } + + if (typeof obj.failOnUnused === "boolean") { + config.failOnUnused = obj.failOnUnused; + } + + return config; +} diff --git a/src/core/exports/resolveExportGraph.ts b/src/core/exports/resolveExportGraph.ts index 68c3c7b..b3bf3b5 100644 --- a/src/core/exports/resolveExportGraph.ts +++ b/src/core/exports/resolveExportGraph.ts @@ -1,5 +1,6 @@ -import path from "node:path"; import { intern } from "../../utils"; +import { normalizeFilePath } from "../fileSystem/normalizePath"; +import { resolveReexportTarget } from "../resolution/resolveModule"; import type { ExportInfo } from "./scanExports"; export interface ResolvedExportEntry { @@ -18,43 +19,6 @@ type ResolvedExportMap = Record; */ const projectCache = new WeakMap, ResolvedExportMap>(); -// Resolves a re-export target into a file from `allFiles`. -function resolveReexportTarget( - reexportingFile: string, - moduleSpecifier: string, - allFiles: string[], -): string | null { - const absoluteSpecifierPath = path.resolve(moduleSpecifier); - const directFileMatch = allFiles.find( - (filePath) => path.resolve(filePath) === absoluteSpecifierPath, - ); - if (directFileMatch) return directFileMatch; - - const reexportingDirectory = path.dirname(reexportingFile); - const resolvedBasePath = path.resolve(reexportingDirectory, moduleSpecifier); - - const candidatePaths = [ - resolvedBasePath, - resolvedBasePath + ".ts", - resolvedBasePath + ".tsx", - resolvedBasePath + ".js", - resolvedBasePath + ".jsx", - path.join(resolvedBasePath, "index.ts"), - path.join(resolvedBasePath, "index.tsx"), - path.join(resolvedBasePath, "index.js"), - path.join(resolvedBasePath, "index.jsx"), - ]; - - for (const candidatePath of candidatePaths) { - const resolvedTarget = allFiles.find( - (filePath) => path.resolve(filePath) === path.resolve(candidatePath), - ); - if (resolvedTarget) return resolvedTarget; - } - - return null; -} - // Resolves all exports for all project files, including re-export chains. export function resolveExportGraph( exportMap: Record, @@ -63,6 +27,8 @@ export function resolveExportGraph( const cachedGraph = projectCache.get(exportMap); if (cachedGraph) return cachedGraph; + const projectFileSet = new Set(allFiles.map(normalizeFilePath)); + const fileLookupCache = new Map(); const fileResultCache = new Map(); const resolvingStack = new Set(); @@ -92,7 +58,7 @@ export function resolveExportGraph( // Wildcard re-exports for (const wildcardOrigin of fileExports.wildcardReexports) { - const targetFile = resolveReexportTarget(currentFile, wildcardOrigin, allFiles); + const targetFile = resolveReexportTarget(currentFile, wildcardOrigin, projectFileSet, fileLookupCache); if (!targetFile) continue; for (const resolvedExport of resolveFileExports(targetFile)) { @@ -118,7 +84,7 @@ export function resolveExportGraph( // Named re-exports for (const reexportInfo of fileExports.namedReexports) { - const targetFile = resolveReexportTarget(currentFile, reexportInfo.from, allFiles); + const targetFile = resolveReexportTarget(currentFile, reexportInfo.from, projectFileSet, fileLookupCache); if (!targetFile) continue; const targetResolvedEntries = resolveFileExports(targetFile); diff --git a/src/core/imports/buildImportGraph.ts b/src/core/imports/buildImportGraph.ts index 864bba1..45ad793 100644 --- a/src/core/imports/buildImportGraph.ts +++ b/src/core/imports/buildImportGraph.ts @@ -11,20 +11,11 @@ * This graph is later consumed by the export-usage analyzer. */ -import path from "node:path"; import ts from "typescript"; import { normalizeFilePath } from "../fileSystem/normalizePath"; +import { resolveModuleSpecifier } from "../resolution/resolveModule"; import { TSConfigInfo } from "../tsconfig/tsconfigLoader"; -// Reset per buildImportGraph() call -let importResolutionCache: Map; -let fileLookupCache: Map; - -/** Memoization key for importer + module specifier */ -function createImportResolutionKey(importerFilePath: string, moduleSpecifier: string): string { - return `${importerFilePath}\x1F${moduleSpecifier}`; -} - /** * A single import edge: * @@ -39,8 +30,9 @@ export function buildImportGraph( projectFiles: string[], tsconfig: TSConfigInfo, ): Record { - importResolutionCache = new Map(); - fileLookupCache = new Map(); + const resolutionCache = new Map(); + const fileLookupCache = new Map(); + const projectFileSet = new Set(projectFiles.map(normalizeFilePath)); const importGraph: Record = {}; @@ -62,11 +54,13 @@ export function buildImportGraph( if (importClause?.isTypeOnly) return; const moduleSpecifier = node.moduleSpecifier.getText().replace(/['"]/g, ""); - const resolvedTargetFile = resolveImportSpecifier( + const resolvedTargetFile = resolveModuleSpecifier( filePath, moduleSpecifier, - projectFiles, + projectFileSet, tsconfig, + resolutionCache, + fileLookupCache, ); const symbols = extractImportedSymbols(node, sourceFile); @@ -114,120 +108,3 @@ function extractImportedSymbols( return [...importedSymbols]; } - -// Resolve JS/TS module specifier into a project file path where possible. -function resolveImportSpecifier( - importerFilePath: string, - moduleSpecifier: string, - projectFiles: string[], - tsconfig: TSConfigInfo, -): string { - const cacheKey = createImportResolutionKey(importerFilePath, moduleSpecifier); - const cached = importResolutionCache.get(cacheKey); - if (cached !== undefined) return cached; - - const importerDirectory = path.dirname(importerFilePath); - - // Resolve "./" or "../" relative imports - if (moduleSpecifier.startsWith(".")) { - const relativeBasePath = path.resolve(importerDirectory, moduleSpecifier); - const resolvedFile = lookupProjectFile(relativeBasePath, projectFiles); - - if (resolvedFile) { - const normalized = normalizeFilePath(resolvedFile); - importResolutionCache.set(cacheKey, normalized); - return normalized; - } - } - - // TSConfig `paths` aliases - const aliasTarget = resolveTSConfigAlias(moduleSpecifier, tsconfig, projectFiles); - if (aliasTarget) { - const normalized = normalizeFilePath(aliasTarget); - importResolutionCache.set(cacheKey, normalized); - return normalized; - } - - // Bare imports (external deps) - importResolutionCache.set(cacheKey, moduleSpecifier); - return moduleSpecifier; -} - -// Resolve a TSConfig `paths` alias. -function resolveTSConfigAlias( - moduleSpecifier: string, - tsconfig: TSConfigInfo, - projectFiles: string[], -): string | null { - if (!tsconfig.paths) return null; - - for (const [aliasPattern, targetPatterns] of Object.entries(tsconfig.paths)) { - const wildcardIndex = aliasPattern.indexOf("*"); - - // Wildcard alias - if (wildcardIndex !== -1) { - const prefix = aliasPattern.slice(0, wildcardIndex); - const suffix = aliasPattern.slice(wildcardIndex + 1); - - const hasPrefix = moduleSpecifier.startsWith(prefix); - const hasSuffix = moduleSpecifier.endsWith(suffix); - if (!hasPrefix || !hasSuffix) continue; - - const wildcardContent = moduleSpecifier.slice( - prefix.length, - moduleSpecifier.length - suffix.length, - ); - - for (const targetPattern of targetPatterns) { - const substitutedTarget = targetPattern.replace("*", wildcardContent); - const absoluteTargetPath = path.resolve(tsconfig.baseUrl ?? "", substitutedTarget); - - const resolvedFile = lookupProjectFile(absoluteTargetPath, projectFiles); - if (resolvedFile) return resolvedFile; - } - } - - // Exact-match alias - else if (moduleSpecifier === aliasPattern) { - for (const targetPath of targetPatterns) { - const absoluteTargetPath = path.resolve(tsconfig.baseUrl ?? "", targetPath); - const resolvedFile = lookupProjectFile(absoluteTargetPath, projectFiles); - if (resolvedFile) return resolvedFile; - } - } - } - - return null; -} - -// Try resolving a file using multiple extension/index patterns. -function lookupProjectFile(unresolvedBasePath: string, projectFiles: string[]): string | null { - const cached = fileLookupCache.get(unresolvedBasePath); - if (cached !== undefined) return cached; - - const candidatePaths = [ - unresolvedBasePath, - unresolvedBasePath + ".ts", - unresolvedBasePath + ".tsx", - unresolvedBasePath + ".js", - unresolvedBasePath + ".jsx", - path.join(unresolvedBasePath, "index.ts"), - path.join(unresolvedBasePath, "index.tsx"), - path.join(unresolvedBasePath, "index.js"), - path.join(unresolvedBasePath, "index.jsx"), - ]; - - for (const candidatePath of candidatePaths) { - const matchingFile = projectFiles.find( - (projectFile) => path.resolve(projectFile) === path.resolve(candidatePath), - ); - - if (matchingFile) { - fileLookupCache.set(unresolvedBasePath, matchingFile); - return matchingFile; - } - } - - fileLookupCache.set(unresolvedBasePath, null); - return null; -} diff --git a/src/core/resolution/resolveModule.ts b/src/core/resolution/resolveModule.ts new file mode 100644 index 0000000..ae1ae40 --- /dev/null +++ b/src/core/resolution/resolveModule.ts @@ -0,0 +1,150 @@ +import path from "node:path"; +import { normalizeFilePath } from "../fileSystem/normalizePath"; +import type { TSConfigInfo } from "../tsconfig/tsconfigLoader"; + +/** + * Shared module resolution utilities. + * + * Centralizes file lookup and TSConfig alias resolution used by + * the import graph builder, export graph resolver, and type usage graph. + */ + +const EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"]; +const INDEX_FILES = EXTENSIONS.map((ext) => `index${ext}`); + +/** + * Try resolving a base path to a project file using extension and index patterns. + * Uses a Set for O(1) lookups instead of Array.find(). + */ +export function lookupProjectFile( + unresolvedBasePath: string, + projectFileSet: Set, + cache: Map, +): string | null { + const cached = cache.get(unresolvedBasePath); + if (cached !== undefined) return cached; + + const resolved = path.resolve(unresolvedBasePath); + const candidates = [ + resolved, + ...EXTENSIONS.map((ext) => resolved + ext), + ...INDEX_FILES.map((idx) => path.join(resolved, idx)), + ]; + + for (const candidate of candidates) { + const normalized = normalizeFilePath(candidate); + if (projectFileSet.has(normalized)) { + cache.set(unresolvedBasePath, normalized); + return normalized; + } + } + + cache.set(unresolvedBasePath, null); + return null; +} + +/** + * Resolve a TSConfig `paths` alias to a project file. + */ +export function resolveTSConfigAlias( + moduleSpecifier: string, + tsconfig: TSConfigInfo, + projectFileSet: Set, + fileLookupCache: Map, +): string | null { + if (!tsconfig.paths) return null; + + for (const [aliasPattern, targetPatterns] of Object.entries(tsconfig.paths)) { + const wildcardIndex = aliasPattern.indexOf("*"); + + if (wildcardIndex !== -1) { + const prefix = aliasPattern.slice(0, wildcardIndex); + const suffix = aliasPattern.slice(wildcardIndex + 1); + + if (!moduleSpecifier.startsWith(prefix) || !moduleSpecifier.endsWith(suffix)) continue; + + const wildcardContent = moduleSpecifier.slice( + prefix.length, + moduleSpecifier.length - suffix.length, + ); + + for (const targetPattern of targetPatterns) { + const substituted = targetPattern.replace("*", wildcardContent); + const abs = path.resolve(tsconfig.baseUrl ?? "", substituted); + const resolved = lookupProjectFile(abs, projectFileSet, fileLookupCache); + if (resolved) return resolved; + } + } else if (moduleSpecifier === aliasPattern) { + for (const target of targetPatterns) { + const abs = path.resolve(tsconfig.baseUrl ?? "", target); + const resolved = lookupProjectFile(abs, projectFileSet, fileLookupCache); + if (resolved) return resolved; + } + } + } + + return null; +} + +/** + * Resolve a module specifier (relative or aliased) to a project file path. + * Returns the bare specifier for unresolvable (external) modules. + */ +export function resolveModuleSpecifier( + importerFilePath: string, + moduleSpecifier: string, + projectFileSet: Set, + tsconfig: TSConfigInfo, + resolutionCache: Map, + fileLookupCache: Map, +): string { + const cacheKey = `${importerFilePath}\x1F${moduleSpecifier}`; + const cached = resolutionCache.get(cacheKey); + if (cached !== undefined) return cached; + + const importerDir = path.dirname(importerFilePath); + + // Relative imports + if (moduleSpecifier.startsWith(".")) { + const base = path.resolve(importerDir, moduleSpecifier); + const resolved = lookupProjectFile(base, projectFileSet, fileLookupCache); + + if (resolved) { + const normalized = normalizeFilePath(resolved); + resolutionCache.set(cacheKey, normalized); + return normalized; + } + } + + // TSConfig alias + const aliasResolved = resolveTSConfigAlias( + moduleSpecifier, + tsconfig, + projectFileSet, + fileLookupCache, + ); + if (aliasResolved) { + const normalized = normalizeFilePath(aliasResolved); + resolutionCache.set(cacheKey, normalized); + return normalized; + } + + // Bare specifier (external dep) + resolutionCache.set(cacheKey, moduleSpecifier); + return moduleSpecifier; +} + +/** + * Resolve a re-export target (relative specifier) to a project file. + * Used by the export graph resolver. + */ +export function resolveReexportTarget( + reexportingFile: string, + moduleSpecifier: string, + projectFileSet: Set, + fileLookupCache: Map, +): string | null { + const reexportingDir = path.dirname(reexportingFile); + const base = path.resolve(reexportingDir, moduleSpecifier); + return lookupProjectFile(base, projectFileSet, fileLookupCache); +} diff --git a/src/core/type/buildTypeUsageGraph.ts b/src/core/type/buildTypeUsageGraph.ts index 1c15c25..1b01a35 100644 --- a/src/core/type/buildTypeUsageGraph.ts +++ b/src/core/type/buildTypeUsageGraph.ts @@ -1,8 +1,8 @@ -import path from "node:path"; import ts from "typescript"; import { intern } from "../../utils"; import { normalizeFilePath } from "../fileSystem/normalizePath"; +import { resolveModuleSpecifier } from "../resolution/resolveModule"; import type { TSConfigInfo } from "../tsconfig/tsconfigLoader"; import { analyzeTypeUsage } from "./analyzeTypeUsage"; @@ -28,137 +28,6 @@ function getSourceText(filePath: string): string | undefined { return text ?? undefined; } -function createResolutionKey(importerFilePath: string, moduleSpecifier: string): string { - return `${importerFilePath}\x1F${moduleSpecifier}`; -} - -function lookupProjectFile( - unresolvedBasePath: string, - projectFiles: string[], - fileLookupCache: Map, -): string | null { - const cached = fileLookupCache.get(unresolvedBasePath); - if (cached !== undefined) return cached; - - const candidatePaths = [ - unresolvedBasePath, - unresolvedBasePath + ".ts", - unresolvedBasePath + ".tsx", - unresolvedBasePath + ".js", - unresolvedBasePath + ".jsx", - path.join(unresolvedBasePath, "index.ts"), - path.join(unresolvedBasePath, "index.tsx"), - path.join(unresolvedBasePath, "index.js"), - path.join(unresolvedBasePath, "index.jsx"), - ]; - - for (const candidatePath of candidatePaths) { - const match = projectFiles.find((p) => path.resolve(p) === path.resolve(candidatePath)); - - if (match) { - fileLookupCache.set(unresolvedBasePath, match); - - return match; - } - } - - fileLookupCache.set(unresolvedBasePath, null); - - return null; -} - -function resolveTSConfigAlias( - moduleSpecifier: string, - tsconfig: TSConfigInfo, - projectFiles: string[], - fileLookupCache: Map, -): string | null { - if (!tsconfig.paths) return null; - - for (const [aliasPattern, targetPatterns] of Object.entries(tsconfig.paths)) { - const wildcardIndex = aliasPattern.indexOf("*"); - - // Wildcard alias - if (wildcardIndex !== -1) { - const prefix = aliasPattern.slice(0, wildcardIndex); - const suffix = aliasPattern.slice(wildcardIndex + 1); - - if (!moduleSpecifier.startsWith(prefix) || !moduleSpecifier.endsWith(suffix)) continue; - - const wildcard = moduleSpecifier.slice(prefix.length, moduleSpecifier.length - suffix.length); - - for (const targetPattern of targetPatterns) { - const substituted = targetPattern.replace("*", wildcard); - const abs = path.resolve(tsconfig.baseUrl ?? "", substituted); - - const resolved = lookupProjectFile(abs, projectFiles, fileLookupCache); - - if (resolved) return resolved; - } - } - - // Exact alias - else if (moduleSpecifier === aliasPattern) { - for (const target of targetPatterns) { - const abs = path.resolve(tsconfig.baseUrl ?? "", target); - const resolved = lookupProjectFile(abs, projectFiles, fileLookupCache); - - if (resolved) return resolved; - } - } - } - - return null; -} - -function resolveModuleSpecifierToProjectFile( - importerFilePath: string, - moduleSpecifier: string, - projectFiles: string[], - tsconfig: TSConfigInfo, - resolutionCache: Map, - fileLookupCache: Map, -): string { - const key = createResolutionKey(importerFilePath, moduleSpecifier); - const cached = resolutionCache.get(key); - - if (cached !== undefined) return cached; - - const importerDir = path.dirname(importerFilePath); - - // Relative - if (moduleSpecifier.startsWith(".")) { - const base = path.resolve(importerDir, moduleSpecifier); - const resolved = lookupProjectFile(base, projectFiles, fileLookupCache); - - if (resolved) { - const n = normalizeFilePath(resolved); - - resolutionCache.set(key, n); - - return n; - } - } - - // TSConfig alias - const aliasResolved = resolveTSConfigAlias( - moduleSpecifier, - tsconfig, - projectFiles, - fileLookupCache, - ); - if (aliasResolved) { - const n = normalizeFilePath(aliasResolved); - - resolutionCache.set(key, n); - - return n; - } - - resolutionCache.set(key, moduleSpecifier); - - return moduleSpecifier; -} function extractTypeImports( importNode: ts.ImportDeclaration, @@ -266,6 +135,7 @@ export function buildTypeUsageGraph(filePaths: string[], tsconfig: TSConfigInfo) set.add(intern(originName)); } + const projectFileSet = new Set(filePaths.map(normalizeFilePath)); const resolutionCache = new Map(); const fileLookupCache = new Map(); @@ -288,10 +158,10 @@ export function buildTypeUsageGraph(filePaths: string[], tsconfig: TSConfigInfo) if (importedTypeNames.length === 0 && !namespace) return; - const resolvedTarget = resolveModuleSpecifierToProjectFile( + const resolvedTarget = resolveModuleSpecifier( importerFile, moduleSpecifier, - filePaths, + projectFileSet, tsconfig, resolutionCache, fileLookupCache, From de53db2420e6743107735937b6af4aa4436a6d13 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Fri, 3 Apr 2026 20:59:45 -0500 Subject: [PATCH 3/5] chore(cli): add config file resolution and explain flag. --- src/cli/help.ts | 5 ++ src/cli/index.ts | 80 ++++++++++++++++++-- src/core/explain/explainUsage.ts | 123 +++++++++++++++++++++++++++++++ src/index.ts | 2 + 4 files changed, 204 insertions(+), 6 deletions(-) create mode 100644 src/core/explain/explainUsage.ts diff --git a/src/cli/help.ts b/src/cli/help.ts index 0c312b0..c01492f 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -26,6 +26,8 @@ ${colors.bold}Analysis Options:${colors.reset} --locals, --identifiers List unused local identifiers --types Enable type-only analysis (unused exported + unused local types) --track-all-locals Count all variable declarations (strict mode) + --explain Show why a file or export is used (e.g. --explain src/utils.ts:add) + --fail-on-unused Exit with code 1 if any unused items are found (useful for CI) ${colors.bold}Output Options:${colors.reset} --verbose Print full graphs (imports, exports, usage) @@ -41,6 +43,9 @@ ${colors.bold}Examples:${colors.reset} devoid src/ --types devoid src/ --types --json devoid src/ --track-all-locals + devoid src/ --fail-on-unused + devoid src/ --explain src/utils.ts:add + devoid --cwd packages/core src/ devoid internal src/utils/helpers.ts devoid internal src/module.ts --track-all-locals `); diff --git a/src/cli/index.ts b/src/cli/index.ts index 6db6825..9354419 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,6 +1,7 @@ // src/cli/index.ts import { analyzeProject } from "../core"; +import { loadConfig } from "../core/config/loadConfig"; import { log } from "../utils"; import { disableLogPrefix, enableLogPrefix } from "../utils/logger"; import { disableColors, enableColors, isColorSupported } from "./colors"; @@ -105,7 +106,6 @@ function resolveAndValidateCwd(rawCwd?: string): string { const silent = args.silent === true; const summaryOnly = args["summary-only"] === true; - const typesMode = args.types === true; // --version if (args.version) { @@ -128,18 +128,73 @@ function resolveAndValidateCwd(rawCwd?: string): string { if (!projectRoot) { if (!silent) { log("Error: No project path provided."); - log("Run `devoid --help` for usage."); + log("Usage: devoid [options]"); + log("Run `devoid --help` for all options."); } process.exit(1); } - // Run full-project analysis (existing behavior) + // Load config file (CLI flags override config values) + const config = loadConfig(cwd); + const ignorePatterns = args.ignore ? (Array.isArray(args.ignore) ? args.ignore : [args.ignore]).map(String) - : []; + : (config.ignore ?? []); + const trackAllLocals = args["track-all-locals"] === true || config.trackAllLocals === true; + const typesMode = args.types === true || config.types === true; + const failOnUnused = args["fail-on-unused"] === true || config.failOnUnused === true; + + // --explain mode: show why a file/export is used + if (args.explain && typeof args.explain === "string") { + const { walkFiles } = await import("../core/fileSystem/walkFiles"); + const { loadTSConfig } = await import("../core/tsconfig/tsconfigLoader"); + const { scanExports } = await import("../core/exports/scanExports"); + const { buildImportGraph } = await import("../core/imports/buildImportGraph"); + const { explainUsage } = await import("../core/explain/explainUsage"); + + const files = walkFiles(projectRoot, ignorePatterns); + const tsConfig = loadTSConfig(projectRoot); + const exportMap = scanExports(files); + const importGraph = buildImportGraph(files, tsConfig); + + // Parse target — supports "file:export" or just "file" + const parts = args.explain.split(":"); + const targetPath = path.resolve(cwd, parts[0]); + const exportName = parts[1] || undefined; + + const result = explainUsage(targetPath, exportName, exportMap, importGraph); + + if (args.json) { + log(JSON.stringify(result, null, 2)); + } else if (!silent) { + const { colors } = await import("./colors.js"); + log(`\n${colors.cyan}${colors.bold}Explain: ${result.target}${exportName ? ":" + exportName : ""}${colors.reset}`); + log(`Status: ${result.status === "used" ? colors.green + "USED" : colors.yellow + "UNUSED"}${colors.reset}`); + + if (result.importedBy.length > 0) { + log(`\nImported by:`); + for (const imp of result.importedBy) { + log(` ${imp.file}`); + log(` symbols: ${imp.symbols.join(", ")}`); + } + } else { + log(`\nNo imports found for this target.`); + } + + if (result.reexportChain.length > 0) { + log(`\nRe-export chain:`); + log(` ${result.reexportChain.join(" → ")}`); + } + log(""); + } + + process.exit(0); + } + + // Run full-project analysis const results = analyzeProject(projectRoot, { ignore: ignorePatterns, - trackAllLocals: args["track-all-locals"] === true, + trackAllLocals, }); // Optional type analysis results @@ -160,13 +215,21 @@ function resolveAndValidateCwd(rawCwd?: string): string { unusedLocalTypes = typeGraph.unusedLocalTypes; } + // Determine if anything unused was found + const hasUnused = + results.unusedExports.length > 0 || + results.unusedFiles.length > 0 || + results.unusedIdentifiers.length > 0 || + unusedExportedTypes.length > 0 || + unusedLocalTypes.length > 0; + // JSON output if (args.json) { const jsonOutput = typesMode ? { ...results, unusedExportedTypes, unusedLocalTypes } : results; log(JSON.stringify(jsonOutput, null, 2)); - process.exit(0); + process.exit(failOnUnused && hasUnused ? 1 : 0); } // Human-readable output @@ -188,6 +251,11 @@ function resolveAndValidateCwd(rawCwd?: string): string { if (args.verbose) logVerbose(results.graphs); } } + + // CI mode: exit non-zero if unused items found + if (failOnUnused && hasUnused) { + process.exit(1); + } })().catch((err) => { enableLogPrefix(); log(`\nFatal error: ${err?.message ?? String(err)}`); diff --git a/src/core/explain/explainUsage.ts b/src/core/explain/explainUsage.ts new file mode 100644 index 0000000..a712535 --- /dev/null +++ b/src/core/explain/explainUsage.ts @@ -0,0 +1,123 @@ +import path from "node:path"; +import { normalizeFilePath } from "../fileSystem/normalizePath"; +import type { ImportRecord } from "../imports/buildImportGraph"; +import { resolveExportGraph } from "../exports/resolveExportGraph"; +import type { ExportInfo } from "../exports/scanExports"; + +export interface ExplainResult { + target: string; + exportName?: string; + status: "used" | "unused" | "not-found"; + importedBy: { file: string; symbols: string[] }[]; + reexportChain: string[]; +} + +/** + * Explain why a file or export is considered used/unused. + * + * Shows: + * - Which files import the target + * - What symbols they import + * - Re-export chain if the export flows through barrels + */ +export function explainUsage( + target: string, + exportName: string | undefined, + exportMap: Record, + importGraph: Record, +): ExplainResult { + const allFiles = Object.keys(exportMap); + + // Normalize target for matching + const normalizedTarget = normalizeFilePath(path.resolve(target)); + const matchedFile = allFiles.find((f) => normalizeFilePath(f) === normalizedTarget); + + if (!matchedFile) { + return { + target: normalizedTarget, + exportName, + status: "not-found", + importedBy: [], + reexportChain: [], + }; + } + + const resolvedExports = resolveExportGraph(exportMap, allFiles); + + // Find who imports this file + const importedBy: { file: string; symbols: string[] }[] = []; + + for (const [importerFile, edges] of Object.entries(importGraph)) { + for (const edge of edges) { + const edgeNormalized = normalizeFilePath(edge.sourceFile); + if (edgeNormalized !== normalizedTarget) continue; + + // If asking about a specific export, filter to relevant imports + if (exportName) { + const relevantSymbols = edge.imported.filter((sym) => { + if (sym === "*") return true; + if (sym === exportName) return true; + // Check if the imported symbol maps to our export via re-exports + const resolved = resolvedExports[matchedFile]; + if (resolved) { + const entry = resolved.find((e) => e.name === sym); + if (entry && entry.originalName === exportName && entry.sourceFile === matchedFile) { + return true; + } + } + return false; + }); + + if (relevantSymbols.length > 0) { + importedBy.push({ file: importerFile, symbols: relevantSymbols }); + } + } else { + importedBy.push({ file: importerFile, symbols: edge.imported }); + } + } + } + + // Find re-export chain if this export flows through barrels + let reexportChain: string[] = []; + if (exportName) { + const resolved = resolvedExports[matchedFile]; + if (resolved) { + const entry = resolved.find((e) => e.name === exportName); + if (entry && entry.exportChain.length > 0) { + reexportChain = entry.exportChain; + } + } + + // Also check if other files re-export this symbol from our file + for (const [file, entries] of Object.entries(resolvedExports)) { + if (file === matchedFile) continue; + for (const entry of entries) { + if (entry.sourceFile === matchedFile && entry.originalName === exportName) { + // This file re-exports our target — check if anyone imports from it + for (const [importerFile, edges] of Object.entries(importGraph)) { + for (const edge of edges) { + if (normalizeFilePath(edge.sourceFile) === normalizeFilePath(file)) { + if (edge.imported.includes(entry.name) || edge.imported.includes("*")) { + importedBy.push({ + file: importerFile, + symbols: [entry.name + " (via " + path.basename(file) + ")"], + }); + } + } + } + } + } + } + } + } + + const isUsed = importedBy.length > 0; + + return { + target: matchedFile, + exportName, + status: isUsed ? "used" : "unused", + importedBy, + reexportChain, + }; +} diff --git a/src/index.ts b/src/index.ts index 6ce6bdf..33265b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ export { analyzeProject } from "./core/analyzer"; export type { AnalyzeOptions } from "./core/analyzer"; +export type { DevoidConfig } from "./core/config/loadConfig"; +export { loadConfig } from "./core/config/loadConfig"; // Re-export common result types for convenience export type { LocalUsageResult } from "./core/locals/analyzeLocalUsage"; From c2108e1c78ada87d85fbdced0ee28e3bdaf98536 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Fri, 3 Apr 2026 21:40:25 -0500 Subject: [PATCH 4/5] chore(deps): detect unused deps. --- src/cli/help.ts | 1 + src/cli/index.ts | 34 +++++- src/core/dependencies/detectUnusedDeps.ts | 132 ++++++++++++++++++++++ 3 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 src/core/dependencies/detectUnusedDeps.ts diff --git a/src/cli/help.ts b/src/cli/help.ts index c01492f..2ed10c6 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -26,6 +26,7 @@ ${colors.bold}Analysis Options:${colors.reset} --locals, --identifiers List unused local identifiers --types Enable type-only analysis (unused exported + unused local types) --track-all-locals Count all variable declarations (strict mode) + --deps Detect unused dependencies from package.json --explain Show why a file or export is used (e.g. --explain src/utils.ts:add) --fail-on-unused Exit with code 1 if any unused items are found (useful for CI) diff --git a/src/cli/index.ts b/src/cli/index.ts index 9354419..e236029 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -215,19 +215,32 @@ function resolveAndValidateCwd(rawCwd?: string): string { unusedLocalTypes = typeGraph.unusedLocalTypes; } + // Optional: unused dependency detection + let unusedDeps: string[] = []; + if (args.deps) { + const { walkFiles } = await import("../core/fileSystem/walkFiles"); + const { detectUnusedDeps } = await import("../core/dependencies/detectUnusedDeps"); + + const files = walkFiles(projectRoot, ignorePatterns); + const depsResult = detectUnusedDeps(cwd, files); + unusedDeps = depsResult.unused; + } + // Determine if anything unused was found const hasUnused = results.unusedExports.length > 0 || results.unusedFiles.length > 0 || results.unusedIdentifiers.length > 0 || unusedExportedTypes.length > 0 || - unusedLocalTypes.length > 0; + unusedLocalTypes.length > 0 || + unusedDeps.length > 0; // JSON output if (args.json) { - const jsonOutput = typesMode + const jsonOutput: Record = typesMode ? { ...results, unusedExportedTypes, unusedLocalTypes } - : results; + : { ...results }; + if (args.deps) jsonOutput.unusedDependencies = unusedDeps; log(JSON.stringify(jsonOutput, null, 2)); process.exit(failOnUnused && hasUnused ? 1 : 0); } @@ -248,6 +261,21 @@ function resolveAndValidateCwd(rawCwd?: string): string { logUnusedLocalTypes(unusedLocalTypes); } + if (args.deps && unusedDeps.length > 0) { + const { colors } = await import("./colors.js"); + const { heading } = await import("./format.js"); + log(heading("Unused Dependencies")); + for (const dep of unusedDeps) { + log(` ${colors.yellow}${dep}${colors.reset}`); + } + log(""); + } else if (args.deps) { + const { colors } = await import("./colors.js"); + const { heading } = await import("./format.js"); + log(heading("Unused Dependencies")); + log(` ${colors.dim}No unused dependencies found!${colors.reset}\n`); + } + if (args.verbose) logVerbose(results.graphs); } } diff --git a/src/core/dependencies/detectUnusedDeps.ts b/src/core/dependencies/detectUnusedDeps.ts new file mode 100644 index 0000000..185348e --- /dev/null +++ b/src/core/dependencies/detectUnusedDeps.ts @@ -0,0 +1,132 @@ +import fs from "node:fs"; +import path from "node:path"; +import ts from "typescript"; + +export interface UnusedDepsResult { + unused: string[]; + used: string[]; + total: number; +} + +/** + * Detect unused dependencies by comparing package.json deps + * against actual imports in the project source files. + * + * Only checks `dependencies` (not devDependencies) by default. + * Returns which declared dependencies are never imported. + */ +export function detectUnusedDeps( + projectRoot: string, + sourceFiles: string[], + options: { includeDevDeps?: boolean } = {}, +): UnusedDepsResult { + const pkgPath = path.join(projectRoot, "package.json"); + + if (!fs.existsSync(pkgPath)) { + return { unused: [], used: [], total: 0 }; + } + + let pkg: Record; + try { + pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); + } catch { + return { unused: [], used: [], total: 0 }; + } + + // Collect declared dependencies + const declaredDeps = new Set(); + + const deps = pkg.dependencies as Record | undefined; + if (deps && typeof deps === "object") { + for (const name of Object.keys(deps)) { + declaredDeps.add(name); + } + } + + if (options.includeDevDeps) { + const devDeps = pkg.devDependencies as Record | undefined; + if (devDeps && typeof devDeps === "object") { + for (const name of Object.keys(devDeps)) { + declaredDeps.add(name); + } + } + } + + if (declaredDeps.size === 0) { + return { unused: [], used: [], total: 0 }; + } + + // Scan all source files for bare import specifiers + const importedPackages = new Set(); + + for (const filePath of sourceFiles) { + const fileContents = ts.sys.readFile(filePath); + if (!fileContents) continue; + + const sourceFile = ts.createSourceFile(filePath, fileContents, ts.ScriptTarget.ESNext, true); + + sourceFile.forEachChild((node) => { + // import declarations + if (ts.isImportDeclaration(node)) { + const specifier = node.moduleSpecifier.getText(sourceFile).replace(/['"]/g, ""); + const pkgName = extractPackageName(specifier); + if (pkgName) importedPackages.add(pkgName); + } + + // require() calls + if ( + ts.isCallExpression(node) && + ts.isIdentifier(node.expression) && + node.expression.text === "require" && + node.arguments.length === 1 && + ts.isStringLiteral(node.arguments[0]) + ) { + const pkgName = extractPackageName(node.arguments[0].text); + if (pkgName) importedPackages.add(pkgName); + } + + // export ... from "pkg" + if (ts.isExportDeclaration(node) && node.moduleSpecifier) { + const specifier = node.moduleSpecifier.getText(sourceFile).replace(/['"]/g, ""); + const pkgName = extractPackageName(specifier); + if (pkgName) importedPackages.add(pkgName); + } + }); + } + + // Compare declared vs imported + const used: string[] = []; + const unused: string[] = []; + + for (const dep of [...declaredDeps].sort()) { + if (importedPackages.has(dep)) { + used.push(dep); + } else { + unused.push(dep); + } + } + + return { unused, used, total: declaredDeps.size }; +} + +/** + * Extract the package name from a module specifier. + * Returns null for relative imports. + * + * "lodash" → "lodash" + * "lodash/fp" → "lodash" + * "@scope/pkg" → "@scope/pkg" + * "@scope/pkg/sub" → "@scope/pkg" + * "./local" → null + */ +function extractPackageName(specifier: string): string | null { + if (specifier.startsWith(".") || specifier.startsWith("/")) return null; + + if (specifier.startsWith("@")) { + const parts = specifier.split("/"); + if (parts.length >= 2) return `${parts[0]}/${parts[1]}`; + return null; + } + + return specifier.split("/")[0]; +} From 6af4afa3b9937a415308b2d673d84d882a3e162e Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Sun, 5 Apr 2026 22:30:05 -0500 Subject: [PATCH 5/5] fix(lint): errors. --- eslint.config.mjs | 12 +++++++++--- src/cli/index.ts | 8 ++++++-- src/core/config/loadConfig.ts | 2 +- src/core/explain/explainUsage.ts | 4 ++-- src/core/exports/resolveExportGraph.ts | 14 ++++++++++++-- src/core/type/buildTypeUsageGraph.ts | 1 - src/index.ts | 2 +- test/cli/basic.spec.ts | 2 +- test/cli/cwd.spec.ts | 2 +- test/cli/types.spec.ts | 6 ++---- test/entrypoints/entrypointExport.spec.ts | 2 +- test/exports/conflictingExports.spec.ts | 2 +- test/exports/exportChains.spec.ts | 2 +- test/exports/exportGraphResolver.spec.ts | 2 +- test/exports/exportUsage.spec.ts | 8 ++++---- .../fixtures/exportChains/formattingBridge.ts | 3 +-- test/exports/fixtures/simple/consumer.ts | 2 +- test/exports/fixtures/wildcard/router.ts | 2 +- test/exports/localPriority.spec.ts | 2 +- test/exports/multiHop.spec.ts | 2 +- test/exports/namedReexports.spec.ts | 2 +- test/exports/unresolvedReexports.spec.ts | 2 +- test/exports/wildcardExportChains.spec.ts | 2 +- test/exports/wildcardReexport.spec.ts | 2 +- test/imports/importGraph.spec.ts | 6 ++---- test/imports/mixedImports.spec.ts | 2 +- test/imports/typeOnlyImports.spec.ts | 2 +- test/imports/unresolvedImports.spec.ts | 2 +- test/integrate/barrel.spec.ts | 2 +- test/integrate/endToEnd.spec.ts | 8 ++++---- test/integrate/fixtures/realistic/index.ts | 2 +- test/integrate/fixtures/realistic/services/user.ts | 2 +- test/integrate/realistic.spec.ts | 2 +- test/internal/basicInternalUsage.spec.ts | 2 +- test/internal/strictInternalUsage.spec.ts | 2 +- test/tsconfig.json | 2 +- test/utils/fileWalker.spec.ts | 2 +- test/utils/pathNormalization.spec.ts | 2 +- 38 files changed, 70 insertions(+), 56 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index cec28ef..a8e2907 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,7 +10,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default [ { - ignores: ["dist/**", "node_modules/**", "tests/**/fixtures/**"], + ignores: ["dist/**", "node_modules/**", "test/**/fixtures/**"], }, { files: ["src/**/*.ts"], @@ -40,17 +40,23 @@ export default [ }, { - files: ["tests/**/*.ts"], + files: ["test/**/*.ts"], ignores: ["**/fixtures/**"], languageOptions: { parser: tsParser, parserOptions: { - project: null, // disable project mode for test files + project: path.join(__dirname, "test", "tsconfig.json"), + sourceType: "module", + ecmaVersion: "latest", }, }, plugins: { "@typescript-eslint": tsPlugin, }, + rules: { + "@typescript-eslint/no-explicit-any": "off", + "no-console": "off", + }, }, { files: ["**/*.js"], diff --git a/src/cli/index.ts b/src/cli/index.ts index e236029..cc5ec46 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -168,8 +168,12 @@ function resolveAndValidateCwd(rawCwd?: string): string { log(JSON.stringify(result, null, 2)); } else if (!silent) { const { colors } = await import("./colors.js"); - log(`\n${colors.cyan}${colors.bold}Explain: ${result.target}${exportName ? ":" + exportName : ""}${colors.reset}`); - log(`Status: ${result.status === "used" ? colors.green + "USED" : colors.yellow + "UNUSED"}${colors.reset}`); + log( + `\n${colors.cyan}${colors.bold}Explain: ${result.target}${exportName ? ":" + exportName : ""}${colors.reset}`, + ); + log( + `Status: ${result.status === "used" ? colors.green + "USED" : colors.yellow + "UNUSED"}${colors.reset}`, + ); if (result.importedBy.length > 0) { log(`\nImported by:`); diff --git a/src/core/config/loadConfig.ts b/src/core/config/loadConfig.ts index 6ce5388..9586b95 100644 --- a/src/core/config/loadConfig.ts +++ b/src/core/config/loadConfig.ts @@ -35,7 +35,7 @@ export function loadConfig(projectRoot: string): DevoidConfig { return {}; } -function validateConfig(raw: unknown, configPath: string): DevoidConfig { +function validateConfig(raw: unknown, _configPath: string): DevoidConfig { if (typeof raw !== "object" || raw === null || Array.isArray(raw)) { return {}; } diff --git a/src/core/explain/explainUsage.ts b/src/core/explain/explainUsage.ts index a712535..3143979 100644 --- a/src/core/explain/explainUsage.ts +++ b/src/core/explain/explainUsage.ts @@ -1,8 +1,8 @@ import path from "node:path"; -import { normalizeFilePath } from "../fileSystem/normalizePath"; -import type { ImportRecord } from "../imports/buildImportGraph"; import { resolveExportGraph } from "../exports/resolveExportGraph"; import type { ExportInfo } from "../exports/scanExports"; +import { normalizeFilePath } from "../fileSystem/normalizePath"; +import type { ImportRecord } from "../imports/buildImportGraph"; export interface ExplainResult { target: string; diff --git a/src/core/exports/resolveExportGraph.ts b/src/core/exports/resolveExportGraph.ts index b3bf3b5..feb3cf7 100644 --- a/src/core/exports/resolveExportGraph.ts +++ b/src/core/exports/resolveExportGraph.ts @@ -58,7 +58,12 @@ export function resolveExportGraph( // Wildcard re-exports for (const wildcardOrigin of fileExports.wildcardReexports) { - const targetFile = resolveReexportTarget(currentFile, wildcardOrigin, projectFileSet, fileLookupCache); + const targetFile = resolveReexportTarget( + currentFile, + wildcardOrigin, + projectFileSet, + fileLookupCache, + ); if (!targetFile) continue; for (const resolvedExport of resolveFileExports(targetFile)) { @@ -84,7 +89,12 @@ export function resolveExportGraph( // Named re-exports for (const reexportInfo of fileExports.namedReexports) { - const targetFile = resolveReexportTarget(currentFile, reexportInfo.from, projectFileSet, fileLookupCache); + const targetFile = resolveReexportTarget( + currentFile, + reexportInfo.from, + projectFileSet, + fileLookupCache, + ); if (!targetFile) continue; const targetResolvedEntries = resolveFileExports(targetFile); diff --git a/src/core/type/buildTypeUsageGraph.ts b/src/core/type/buildTypeUsageGraph.ts index 1b01a35..7735d42 100644 --- a/src/core/type/buildTypeUsageGraph.ts +++ b/src/core/type/buildTypeUsageGraph.ts @@ -28,7 +28,6 @@ function getSourceText(filePath: string): string | undefined { return text ?? undefined; } - function extractTypeImports( importNode: ts.ImportDeclaration, fileAST: ts.SourceFile, diff --git a/src/index.ts b/src/index.ts index 33265b9..a19e918 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ export { analyzeProject } from "./core/analyzer"; export type { AnalyzeOptions } from "./core/analyzer"; -export type { DevoidConfig } from "./core/config/loadConfig"; export { loadConfig } from "./core/config/loadConfig"; +export type { DevoidConfig } from "./core/config/loadConfig"; // Re-export common result types for convenience export type { LocalUsageResult } from "./core/locals/analyzeLocalUsage"; diff --git a/test/cli/basic.spec.ts b/test/cli/basic.spec.ts index 8eaccd3..45585ba 100644 --- a/test/cli/basic.spec.ts +++ b/test/cli/basic.spec.ts @@ -1,6 +1,6 @@ +import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; -import { spawnSync } from "node:child_process"; import { describe, expect, it } from "unrift"; diff --git a/test/cli/cwd.spec.ts b/test/cli/cwd.spec.ts index 7cd8a8e..dd51530 100644 --- a/test/cli/cwd.spec.ts +++ b/test/cli/cwd.spec.ts @@ -2,8 +2,8 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { runCLI } from "../helpers/runCLI"; import { describe, expect, it } from "unrift"; +import { runCLI } from "../helpers/runCLI"; function writeFile(filePath: string, contents: string) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); diff --git a/test/cli/types.spec.ts b/test/cli/types.spec.ts index 24bd9b8..4fd736c 100644 --- a/test/cli/types.spec.ts +++ b/test/cli/types.spec.ts @@ -1,8 +1,8 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { fixtures } from "../helpers/fixtures"; import { runCLI } from "../helpers/runCLI"; -import { describe, expect, it } from "unrift"; const FIXTURE_ROOT = fixtures("cli", "fixtures", "types"); @@ -27,9 +27,7 @@ describe("CLI --types", () => { expect(Array.isArray(parsed.unusedExportedTypes)).toBe(true); expect(Array.isArray(parsed.unusedLocalTypes)).toBe(true); - const unusedExported = new Set( - parsed.unusedExportedTypes.map((e: any) => `${e.file}:${e.name}`), - ); + const unusedExported = new Set(parsed.unusedExportedTypes.map((e) => `${e.file}:${e.name}`)); const unusedLocal = new Set(parsed.unusedLocalTypes.map((e: any) => `${e.file}:${e.name}`)); const typesFile = path.join(FIXTURE_ROOT, "types.ts"); diff --git a/test/entrypoints/entrypointExport.spec.ts b/test/entrypoints/entrypointExport.spec.ts index e5a3b5f..028fdb7 100644 --- a/test/entrypoints/entrypointExport.spec.ts +++ b/test/entrypoints/entrypointExport.spec.ts @@ -2,9 +2,9 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { describe, expect, it } from "unrift"; import { detectEntryPoints } from "../../src/core/entrypoints/detectEntryPoints"; import { normalizeFilePath } from "../../src/core/fileSystem/normalizePath"; -import { describe, expect, it } from "unrift"; function writeFile(filePath: string, contents: string) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); diff --git a/test/exports/conflictingExports.spec.ts b/test/exports/conflictingExports.spec.ts index fc26d52..fae4079 100644 --- a/test/exports/conflictingExports.spec.ts +++ b/test/exports/conflictingExports.spec.ts @@ -1,11 +1,11 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import type { ResolvedExportEntry } from "../../src/core/exports/resolveExportGraph"; import { resolveExportGraph } from "../../src/core/exports/resolveExportGraph"; import { scanExports } from "../../src/core/exports/scanExports"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const FIX = fixtures("exports", "fixtures", "conflictingExports"); diff --git a/test/exports/exportChains.spec.ts b/test/exports/exportChains.spec.ts index aa708a1..361a760 100644 --- a/test/exports/exportChains.spec.ts +++ b/test/exports/exportChains.spec.ts @@ -1,12 +1,12 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports"; import { buildImportGraph } from "../../src/core/imports/buildImportGraph"; import { loadTSConfig } from "../../src/core/tsconfig/tsconfigLoader"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const fixturesRoot = fixtures("exports", "fixtures", "exportChains"); diff --git a/test/exports/exportGraphResolver.spec.ts b/test/exports/exportGraphResolver.spec.ts index d2f4e02..a5600ab 100644 --- a/test/exports/exportGraphResolver.spec.ts +++ b/test/exports/exportGraphResolver.spec.ts @@ -1,11 +1,11 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import type { ResolvedExportEntry } from "../../src/core/exports/resolveExportGraph"; import { resolveExportGraph } from "../../src/core/exports/resolveExportGraph"; import { scanExports } from "../../src/core/exports/scanExports"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const FIXTURE_ROOT = fixtures("exports", "fixtures", "resolveGraphExports"); diff --git a/test/exports/exportUsage.spec.ts b/test/exports/exportUsage.spec.ts index 21b3ed2..6853d74 100644 --- a/test/exports/exportUsage.spec.ts +++ b/test/exports/exportUsage.spec.ts @@ -1,12 +1,12 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports"; import { buildImportGraph } from "../../src/core/imports/buildImportGraph"; import { loadTSConfig } from "../../src/core/tsconfig/tsconfigLoader"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const fixturesRoot = fixtures("exports", "fixtures", "simple"); @@ -27,9 +27,9 @@ describe("export usage", () => { expect([...used[mathFile]].sort()).toEqual(["add"]); expect([...unused[mathFile]].sort()).toEqual(["subtract"]); - expect([...used[loggerFile]].sort()).toEqual(["debug", "default"]); - expect(unused[loggerFile].has("default")).toBe(false); - expect(used[loggerFile].has("default")).toBe(true); + expect([...used[loggerFile]].sort()).toEqual(["debug"]); + expect(unused[loggerFile].has("default")).toBe(true); + expect(used[loggerFile].has("default")).toBe(false); expect(used[loggerFile].has("debug")).toBe(true); }); diff --git a/test/exports/fixtures/exportChains/formattingBridge.ts b/test/exports/fixtures/exportChains/formattingBridge.ts index b6e7567..11dd8f9 100644 --- a/test/exports/fixtures/exportChains/formattingBridge.ts +++ b/test/exports/fixtures/exportChains/formattingBridge.ts @@ -1,2 +1 @@ -export { formatUserName } from "./formatting"; -export { normalizeEmail as canonicalizeEmail } from "./formatting"; +export { normalizeEmail as canonicalizeEmail, formatUserName } from "./formatting"; diff --git a/test/exports/fixtures/simple/consumer.ts b/test/exports/fixtures/simple/consumer.ts index c5eb7ac..43dc48c 100644 --- a/test/exports/fixtures/simple/consumer.ts +++ b/test/exports/fixtures/simple/consumer.ts @@ -1,6 +1,6 @@ // Import the math and logger fixture modules +import { debug } from "./logger"; import { add } from "./math"; -import log, { debug } from "./logger"; const result = add(2, 3); debug(`Result is ${result}`); diff --git a/test/exports/fixtures/wildcard/router.ts b/test/exports/fixtures/wildcard/router.ts index b196767..29f9723 100644 --- a/test/exports/fixtures/wildcard/router.ts +++ b/test/exports/fixtures/wildcard/router.ts @@ -3,6 +3,6 @@ import * as Controllers from "./index"; export function buildRoutes() { const user = Controllers.getUser("123"); const created = Controllers.createUser(user.name); - + return { user, created }; } diff --git a/test/exports/localPriority.spec.ts b/test/exports/localPriority.spec.ts index b97b64a..2de9ecf 100644 --- a/test/exports/localPriority.spec.ts +++ b/test/exports/localPriority.spec.ts @@ -1,10 +1,10 @@ import fs from "node:fs"; import path from "node:path"; +import { describe, expect, it } from "unrift"; import { resolveExportGraph } from "../../src/core/exports/resolveExportGraph"; import { scanExports } from "../../src/core/exports/scanExports"; import { fixtures } from "../helpers/fixtures"; -import { describe, expect, it } from "unrift"; const root = fixtures("exports", "fixtures", "localPriority"); diff --git a/test/exports/multiHop.spec.ts b/test/exports/multiHop.spec.ts index 61e1f41..b9a97b4 100644 --- a/test/exports/multiHop.spec.ts +++ b/test/exports/multiHop.spec.ts @@ -1,11 +1,11 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import type { ResolvedExportEntry } from "../../src/core/exports/resolveExportGraph"; import { resolveExportGraph } from "../../src/core/exports/resolveExportGraph"; import { scanExports } from "../../src/core/exports/scanExports"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const FIXTURES = fixtures("exports", "fixtures", "multiHop"); diff --git a/test/exports/namedReexports.spec.ts b/test/exports/namedReexports.spec.ts index 6e52f75..44ebd57 100644 --- a/test/exports/namedReexports.spec.ts +++ b/test/exports/namedReexports.spec.ts @@ -1,11 +1,11 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import type { ResolvedExportEntry } from "../../src/core/exports/resolveExportGraph"; import { resolveExportGraph } from "../../src/core/exports/resolveExportGraph"; import { scanExports } from "../../src/core/exports/scanExports"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const FIXTURES = fixtures("exports", "fixtures", "namedReexports"); diff --git a/test/exports/unresolvedReexports.spec.ts b/test/exports/unresolvedReexports.spec.ts index abb5170..6c5ab31 100644 --- a/test/exports/unresolvedReexports.spec.ts +++ b/test/exports/unresolvedReexports.spec.ts @@ -1,11 +1,11 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import type { ResolvedExportEntry } from "../../src/core/exports/resolveExportGraph"; import { resolveExportGraph } from "../../src/core/exports/resolveExportGraph"; import { scanExports } from "../../src/core/exports/scanExports"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const FIXTURES = fixtures("exports", "fixtures", "unresolved"); diff --git a/test/exports/wildcardExportChains.spec.ts b/test/exports/wildcardExportChains.spec.ts index 85f719b..1fd87d0 100644 --- a/test/exports/wildcardExportChains.spec.ts +++ b/test/exports/wildcardExportChains.spec.ts @@ -1,12 +1,12 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports"; import { buildImportGraph } from "../../src/core/imports/buildImportGraph"; import { loadTSConfig } from "../../src/core/tsconfig/tsconfigLoader"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const fixturesRoot = fixtures("exports", "fixtures", "wildcard"); diff --git a/test/exports/wildcardReexport.spec.ts b/test/exports/wildcardReexport.spec.ts index e2ded49..320a514 100644 --- a/test/exports/wildcardReexport.spec.ts +++ b/test/exports/wildcardReexport.spec.ts @@ -1,11 +1,11 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import type { ResolvedExportEntry } from "../../src/core/exports/resolveExportGraph"; import { resolveExportGraph } from "../../src/core/exports/resolveExportGraph"; import { scanExports } from "../../src/core/exports/scanExports"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const FIXTURES = fixtures("exports", "fixtures", "wildcardReexports"); diff --git a/test/imports/importGraph.spec.ts b/test/imports/importGraph.spec.ts index b6271db..15525b0 100644 --- a/test/imports/importGraph.spec.ts +++ b/test/imports/importGraph.spec.ts @@ -1,10 +1,10 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { buildImportGraph } from "../../src/core/imports/buildImportGraph"; import { loadTSConfig } from "../../src/core/tsconfig/tsconfigLoader"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const fixturesRoot = fixtures("imports", "fixtures", "importGraph"); @@ -42,9 +42,7 @@ describe("import graph", () => { expect(graph[userService]).toEqual([]); - const allTargets = Object.values(graph).flatMap((records) => - records.map((r) => r.sourceFile), - ); + const allTargets = Object.values(graph).flatMap((records) => records.map((r) => r.sourceFile)); expect(allTargets.includes(userTypes)).toBe(false); }); }); diff --git a/test/imports/mixedImports.spec.ts b/test/imports/mixedImports.spec.ts index e7feabc..55bb651 100644 --- a/test/imports/mixedImports.spec.ts +++ b/test/imports/mixedImports.spec.ts @@ -1,12 +1,12 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports"; import { buildImportGraph } from "../../src/core/imports/buildImportGraph"; import { loadTSConfig } from "../../src/core/tsconfig/tsconfigLoader"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const root = fixtures("imports", "fixtures", "mixedImports"); diff --git a/test/imports/typeOnlyImports.spec.ts b/test/imports/typeOnlyImports.spec.ts index 6e6b72e..64283eb 100644 --- a/test/imports/typeOnlyImports.spec.ts +++ b/test/imports/typeOnlyImports.spec.ts @@ -1,12 +1,12 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports"; import { buildImportGraph } from "../../src/core/imports/buildImportGraph"; import { loadTSConfig } from "../../src/core/tsconfig/tsconfigLoader"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const root = fixtures("imports", "fixtures", "typeOnly"); diff --git a/test/imports/unresolvedImports.spec.ts b/test/imports/unresolvedImports.spec.ts index 378755e..3911eaf 100644 --- a/test/imports/unresolvedImports.spec.ts +++ b/test/imports/unresolvedImports.spec.ts @@ -1,12 +1,12 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeExportUsage } from "../../src/core/exports/exportUsage"; import { scanExports } from "../../src/core/exports/scanExports"; import { buildImportGraph } from "../../src/core/imports/buildImportGraph"; import { loadTSConfig } from "../../src/core/tsconfig/tsconfigLoader"; import { fixtures } from "../helpers/fixtures"; import { loadFiles } from "../helpers/loadFiles"; -import { describe, expect, it } from "unrift"; const fixturesRoot = fixtures("imports", "fixtures", "unresolved"); diff --git a/test/integrate/barrel.spec.ts b/test/integrate/barrel.spec.ts index 78773ef..cf59673 100644 --- a/test/integrate/barrel.spec.ts +++ b/test/integrate/barrel.spec.ts @@ -1,8 +1,8 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeProject } from "../../src/core/analyzer"; import { fixtures } from "../helpers/fixtures"; -import { describe, expect, it } from "unrift"; const FIXTURE_ROOT = fixtures("integrate", "fixtures", "barrel"); diff --git a/test/integrate/endToEnd.spec.ts b/test/integrate/endToEnd.spec.ts index aed098e..2278341 100644 --- a/test/integrate/endToEnd.spec.ts +++ b/test/integrate/endToEnd.spec.ts @@ -1,8 +1,8 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeProject } from "../../src/core/analyzer"; import { fixtures } from "../helpers/fixtures"; -import { describe, expect, it } from "unrift"; const projectRoot = fixtures("integrate", "fixtures", "project", "src"); @@ -15,9 +15,9 @@ describe("end-to-end", () => { const unusedExportNames = new Set(unusedExports.map((e) => `${e.file}:${e.name}`)); - expect( - unusedExportNames.has(path.join(projectRoot, "utils", "math.ts") + ":multiply"), - ).toBe(true); + expect(unusedExportNames.has(path.join(projectRoot, "utils", "math.ts") + ":multiply")).toBe( + true, + ); expect( unusedExportNames.has(path.join(projectRoot, "utils", "unused.ts") + ":unusedHelper"), ).toBe(true); diff --git a/test/integrate/fixtures/realistic/index.ts b/test/integrate/fixtures/realistic/index.ts index da00fe1..deecf78 100644 --- a/test/integrate/fixtures/realistic/index.ts +++ b/test/integrate/fixtures/realistic/index.ts @@ -1,2 +1,2 @@ -export { formatDate } from "./utils/date"; export { getUserProfile } from "./services/user"; +export { formatDate } from "./utils/date"; diff --git a/test/integrate/fixtures/realistic/services/user.ts b/test/integrate/fixtures/realistic/services/user.ts index 2e2c158..3e8c5dc 100644 --- a/test/integrate/fixtures/realistic/services/user.ts +++ b/test/integrate/fixtures/realistic/services/user.ts @@ -3,7 +3,7 @@ import { formatDate } from "../utils/date"; export function getUserProfile() { return { id: "123", - createdAt: formatDate(Date.now()) + createdAt: formatDate(Date.now()), }; } diff --git a/test/integrate/realistic.spec.ts b/test/integrate/realistic.spec.ts index 6aff37c..764ad0b 100644 --- a/test/integrate/realistic.spec.ts +++ b/test/integrate/realistic.spec.ts @@ -1,8 +1,8 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeProject } from "../../src/core/analyzer"; import { fixtures } from "../helpers/fixtures"; -import { describe, expect, it } from "unrift"; const ROOT = fixtures("integrate", "fixtures", "realistic"); diff --git a/test/internal/basicInternalUsage.spec.ts b/test/internal/basicInternalUsage.spec.ts index 2461164..b4f87cc 100644 --- a/test/internal/basicInternalUsage.spec.ts +++ b/test/internal/basicInternalUsage.spec.ts @@ -1,9 +1,9 @@ import fs from "node:fs"; import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeLocalUsage } from "../../src/core/locals/analyzeLocalUsage"; import { fixtures } from "../helpers/fixtures"; -import { describe, expect, it } from "unrift"; const fixturesRoot = fixtures("internal", "fixtures", "basic"); diff --git a/test/internal/strictInternalUsage.spec.ts b/test/internal/strictInternalUsage.spec.ts index 7a3732b..4667ddb 100644 --- a/test/internal/strictInternalUsage.spec.ts +++ b/test/internal/strictInternalUsage.spec.ts @@ -1,9 +1,9 @@ import fs from "node:fs"; import path from "node:path"; +import { describe, expect, it } from "unrift"; import { analyzeLocalUsage } from "../../src/core/locals/analyzeLocalUsage"; import { fixtures } from "../helpers/fixtures"; -import { describe, expect, it } from "unrift"; const fixturesRoot = fixtures("internal", "fixtures", "strict"); diff --git a/test/tsconfig.json b/test/tsconfig.json index a0b3430..a78cd38 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -12,5 +12,5 @@ "@utils/*": ["./fixtures/graph/utils/*"] } }, - "include": ["**/*.ts", "../unrift.config.ts"] + "include": ["**/*.ts", "../unrift.config.ts", "../src/**/*.ts"] } diff --git a/test/utils/fileWalker.spec.ts b/test/utils/fileWalker.spec.ts index cf9523d..c6a85d9 100644 --- a/test/utils/fileWalker.spec.ts +++ b/test/utils/fileWalker.spec.ts @@ -1,8 +1,8 @@ import path from "node:path"; +import { describe, expect, it } from "unrift"; import { walkFiles } from "../../src/core/fileSystem/walkFiles"; import { fixtures } from "../helpers/fixtures"; -import { describe, expect, it } from "unrift"; const fixturesRoot = fixtures("utils", "fixtures", "walkFiles"); diff --git a/test/utils/pathNormalization.spec.ts b/test/utils/pathNormalization.spec.ts index 1d23fe6..ce2d62d 100644 --- a/test/utils/pathNormalization.spec.ts +++ b/test/utils/pathNormalization.spec.ts @@ -1,7 +1,7 @@ import path from "node:path"; -import { normalizeFilePath } from "../../src/core/fileSystem/normalizePath"; import { describe, expect, it } from "unrift"; +import { normalizeFilePath } from "../../src/core/fileSystem/normalizePath"; describe("normalizeFilePath", () => { it("normalizeFilePath: produces absolute, forward-slashed paths", () => {