knip
Version:
Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects
203 lines (202 loc) • 8.55 kB
JavaScript
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;