UNPKG

knip

Version:

Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects

203 lines (202 loc) 8.55 kB
import { DEFAULT_EXTENSIONS } from "../../constants.js"; import { _glob } from "../../util/glob.js"; import { toAlias, toConfig, toDeferResolve, toDependency, toEntry } from "../../util/input.js"; import { isAbsolute, isInternal, join, toAbsolute, toPosix } from "../../util/path.js"; import { hasDependency } from "../../util/plugin.js"; import { getIndexHtmlEntries } from "../vite/helpers.js"; import { getEnvSpecifier, getExternalReporters } from "./helpers.js"; const title = 'Vitest'; const enablers = ['vitest']; const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers); const config = ['vitest.config.{js,mjs,ts,cjs,mts,cts}', 'vitest.{workspace,projects}.{js,mjs,ts,cjs,mts,cts,json}']; const mocks = ['**/__mocks__/**/*.[jt]s?(x)']; const entry = ['**/*.{bench,test,test-d,spec,spec-d}.?(c|m)[jt]s?(x)', ...mocks]; const findConfigDependencies = (localConfig, options, vitestRoot) => { const { configFileDir: dir } = options; const testConfig = localConfig.test; if (!testConfig) return []; const env = testConfig.environment; const environments = env && env !== 'node' ? isInternal(env) || isAbsolute(env) ? [toDeferResolve(env)] : [toDependency(getEnvSpecifier(env))] : []; const reporters = getExternalReporters(testConfig.reporters); const hasCoverage = testConfig.coverage && (testConfig.coverage.enabled !== false || testConfig.coverage.provider); const coverage = hasCoverage ? [`@vitest/coverage-${testConfig.coverage?.provider ?? 'v8'}`] : []; const setupFiles = [testConfig.setupFiles ?? []] .flat() .map(specifier => ({ ...toDeferResolve(specifier), dir: vitestRoot })); const globalSetup = [testConfig.globalSetup ?? []].flat().map(specifier => ({ ...toDeferResolve(specifier), dir })); const workspaceDependencies = []; if (testConfig.workspace !== undefined) { for (const workspaceConfig of testConfig.workspace) { workspaceDependencies.push(...findConfigDependencies(workspaceConfig, options, vitestRoot)); } } const projectsDependencies = []; if (testConfig.projects !== undefined) { for (const projectConfig of testConfig.projects) { if (typeof projectConfig !== 'string') { projectsDependencies.push(...findConfigDependencies(projectConfig, options, vitestRoot)); } } } return [ ...environments, ...reporters.map(id => toDependency(id)), ...coverage.map(id => toDependency(id)), ...setupFiles, ...globalSetup, ...workspaceDependencies, ...projectsDependencies, ]; }; const getConfigs = async (localConfig) => { const configs = []; for (const config of [localConfig].flat()) { if (config && typeof config !== 'string') { if (typeof config === 'function') { for (const command of ['dev', 'serve', 'build']) { for (const mode of ['development', 'production']) { const cfg = await config({ command, mode, ssrBuild: undefined }); configs.push(cfg); if (cfg.test?.projects) { for (const project of cfg.test.projects) { if (typeof project !== 'string') { configs.push(project); } } } } } } else { configs.push(config); if (config.test?.projects) { for (const project of config.test.projects) { if (typeof project !== 'string') { configs.push(project); } } } } } } return configs; }; export const resolveConfig = async (localConfig, options) => { const inputs = new Set(); inputs.add(toEntry(join(options.cwd, 'src/vite-env.d.ts'))); const configs = await getConfigs(localConfig); for (const cfg of configs) { if (cfg.test?.projects) { for (const project of cfg.test.projects) { if (typeof project === 'string') { const projectFiles = await _glob({ cwd: options.cwd, patterns: [project], gitignore: false, }); for (const projectFile of projectFiles) { inputs.add(toConfig('vitest', projectFile, { containingFilePath: options.configFilePath })); } } } } } const addStar = (value) => (value.endsWith('*') ? value : join(value, '*').replace(/\/\*\*$/, '/*')); const addAliases = (aliasOptions) => { for (const [alias, value] of Object.entries(aliasOptions)) { if (!value) continue; const prefixes = [value] .flat() .filter(value => typeof value === 'string') .map(prefix => { if (toPosix(prefix).startsWith(options.cwd)) return prefix; return join(options.cwd, prefix); }); if (alias.length > 1) inputs.add(toAlias(alias, prefixes)); inputs.add(toAlias(addStar(alias), prefixes.map(addStar))); } }; const seenRoots = new Set(); for (const cfg of configs) { const viteRoot = toAbsolute(cfg.root ?? '.', options.cwd); if (!seenRoots.has(viteRoot)) { seenRoots.add(viteRoot); for (const entry of await getIndexHtmlEntries(viteRoot)) inputs.add(entry); } const dir = join(options.cwd, cfg.test?.root ?? '.'); if (cfg.test) { if (cfg.test?.include) { for (const dependency of cfg.test.include) dependency[0] !== '!' && inputs.add(toEntry(join(dir, dependency))); if (!options.config.entry) for (const dependency of mocks) inputs.add(toEntry(join(dir, dependency))); } else { for (const dependency of options.config.entry ?? entry) inputs.add(toEntry(join(dir, dependency))); } if (cfg.test.alias) addAliases(cfg.test.alias); } if (cfg.resolve?.alias) addAliases(cfg.resolve.alias); if (cfg.resolve?.extensions) { const customExtensions = cfg.resolve.extensions.filter(ext => ext.startsWith('.') && !DEFAULT_EXTENSIONS.includes(ext)); for (const ext of customExtensions) { inputs.add(toEntry(`src/**/*${ext}`)); } } for (const dependency of findConfigDependencies(cfg, options, dir)) inputs.add(dependency); const _entry = cfg.build?.lib?.entry ?? []; const deps = (typeof _entry === 'string' ? [_entry] : Object.values(_entry)) .map(specifier => join(dir, specifier)) .map(id => toEntry(id)); for (const dependency of deps) inputs.add(dependency); } return Array.from(inputs); }; const args = { config: true, resolveInputs: (parsed) => { const inputs = []; if (parsed['ui']) inputs.push(toDependency('@vitest/ui', { optional: true })); if (typeof parsed['coverage'] === 'object' && parsed['coverage'].provider) { inputs.push(toDependency(`@vitest/coverage-${parsed['coverage'].provider}`)); } if (parsed['reporter']) { for (const reporter of getExternalReporters([parsed['reporter']].flat())) { inputs.push(toDependency(reporter)); } } if (parsed['environment'] && parsed['environment'] !== 'node') { inputs.push(toDependency(getEnvSpecifier(parsed['environment']))); } if (typeof parsed['typecheck'] === 'object' && parsed['typecheck'].checker) { inputs.push(toDependency(parsed['typecheck'].checker === 'tsc' ? 'typescript' : parsed['typecheck'].checker)); } return inputs; }, }; const plugin = { title, enablers, isEnabled, config, entry, resolveConfig, args, }; export default plugin;