Skip to content

Commit

Permalink
Move test/entry file patterns to plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
webpro committed Sep 26, 2023
1 parent 92c6f1b commit 82278f8
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 92 deletions.
89 changes: 29 additions & 60 deletions src/WorkspaceWorker.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { TEST_FILE_PATTERNS } from './constants.js';
import * as npm from './manifest/index.js';
import * as plugins from './plugins/index.js';
import { debugLogArray, debugLogObject } from './util/debug.js';
import { _pureGlob, negate, hasProductionSuffix, hasNoProductionSuffix, prependDirToPattern } from './util/glob.js';
import { getKeysByValue } from './util/object.js';
import { join, toPosix } from './util/path.js';
import {
fromEntryPattern,
fromProductionEntryPattern,
isEntryPattern,
isProductionEntryPattern,
} from './util/protocols.js';
import type { Configuration, PluginConfiguration, PluginName, WorkspaceConfiguration } from './types/config.js';
import type { PackageJsonWithPlugins } from './types/plugins.js';
import type { InstalledBinaries, HostDependencies } from './types/workspace.js';
Expand All @@ -27,8 +32,6 @@ type WorkspaceManagerOptions = {

type ReferencedDependencies = Set<[string, string]>;

const negatedTestFilePatterns = TEST_FILE_PATTERNS.map(negate);

/**
* - Determines enabled plugins
* - Finds referenced dependencies and binaries in npm scripts
Expand All @@ -53,6 +56,8 @@ export class WorkspaceWorker {
referencedDependencies: ReferencedDependencies = new Set();
hostDependencies: HostDependencies = new Map();
installedBinaries: InstalledBinaries = new Map();
entryFilePatterns: Set<string> = new Set();
productionEntryFilePatterns: Set<string> = new Set();

constructor({
name,
Expand Down Expand Up @@ -136,59 +141,32 @@ export class WorkspaceWorker {
getEntryFilePatterns() {
const { entry } = this.config;
if (entry.length === 0) return [];
return [entry, TEST_FILE_PATTERNS, this.negatedWorkspacePatterns].flat();
return [entry, this.negatedWorkspacePatterns].flat();
}

getProjectFilePatterns() {
getProjectFilePatterns(testFilePatterns: string[]) {
const { project } = this.config;
if (project.length === 0) return [];

const negatedPluginConfigPatterns = this.getPluginConfigPatterns().map(negate);
const negatedPluginEntryFilePatterns = this.getPluginEntryFilePatterns(false).map(negate);
const negatedPluginProjectFilePatterns = this.getPluginProjectFilePatterns().map(negate);

return [
project,
negatedPluginConfigPatterns,
negatedPluginEntryFilePatterns,
negatedPluginProjectFilePatterns,
TEST_FILE_PATTERNS,
testFilePatterns,
this.negatedWorkspacePatterns,
].flat();
}

getPluginEntryFilePatterns(isIncludeProductionEntryFiles = true) {
const patterns: string[] = [];
for (const [pluginName, plugin] of Object.entries(plugins) as PluginNames) {
const pluginConfig = this.getConfigForPlugin(pluginName);
if (this.enabled[pluginName] && pluginConfig) {
const { entry } = pluginConfig;
const defaultEntryFiles = 'ENTRY_FILE_PATTERNS' in plugin ? plugin.ENTRY_FILE_PATTERNS : [];
patterns.push(...(entry ?? defaultEntryFiles));
if (isIncludeProductionEntryFiles) {
const entry = 'PRODUCTION_ENTRY_FILE_PATTERNS' in plugin ? plugin.PRODUCTION_ENTRY_FILE_PATTERNS : [];
patterns.push(...entry);
}
}
}
return [patterns, this.negatedWorkspacePatterns].flat();
}

getPluginProjectFilePatterns() {
const patterns: string[] = [];
for (const [pluginName, plugin] of Object.entries(plugins) as PluginNames) {
const pluginConfig = this.getConfigForPlugin(pluginName);
if (this.enabled[pluginName] && pluginConfig) {
const { entry, project } = pluginConfig;
patterns.push(
...(project ??
entry ??
('PROJECT_FILE_PATTERNS' in plugin
? plugin.PROJECT_FILE_PATTERNS
: 'ENTRY_FILE_PATTERNS' in plugin
? plugin.ENTRY_FILE_PATTERNS
: []))
);
patterns.push(...(project ?? entry ?? ('PROJECT_FILE_PATTERNS' in plugin ? plugin.PROJECT_FILE_PATTERNS : [])));
}
}
return [patterns, this.negatedWorkspacePatterns].flat();
Expand All @@ -207,50 +185,34 @@ export class WorkspaceWorker {
return patterns;
}

getProductionEntryFilePatterns() {
getProductionEntryFilePatterns(negatedTestFilePatterns: string[]) {
const entry = this.config.entry.filter(hasProductionSuffix);
if (entry.length === 0) return [];
const negatedEntryFiles = this.config.entry.filter(hasNoProductionSuffix).map(negate);
return [entry, negatedEntryFiles, negatedTestFilePatterns, this.negatedWorkspacePatterns].flat();
}

getProductionProjectFilePatterns() {
getProductionProjectFilePatterns(negatedTestFilePatterns: string[]) {
const project = this.config.project;
if (project.length === 0) return this.getProductionEntryFilePatterns();
if (project.length === 0) return this.getProductionEntryFilePatterns(negatedTestFilePatterns);
const _project = this.config.project.map(pattern => {
if (!pattern.endsWith('!') && !pattern.startsWith('!')) return negate(pattern);
return pattern;
});
const negatedEntryFiles = this.config.entry.filter(hasNoProductionSuffix).map(negate);
const negatedPluginConfigPatterns = this.getPluginConfigPatterns().map(negate);
const negatedPluginEntryFilePatterns = this.getPluginEntryFilePatterns(false).map(negate);
const negatedPluginProjectFilePatterns = this.getPluginProjectFilePatterns().map(negate);

return [
_project,
negatedEntryFiles,
negatedPluginConfigPatterns,
negatedPluginEntryFilePatterns,
negatedPluginProjectFilePatterns,
negatedTestFilePatterns,
this.negatedWorkspacePatterns,
].flat();
}

getProductionPluginEntryFilePatterns() {
const patterns: string[] = [];
for (const [pluginName, plugin] of Object.entries(plugins) as PluginNames) {
const pluginConfig = this.getConfigForPlugin(pluginName);
if (this.enabled[pluginName] && pluginConfig) {
if ('PRODUCTION_ENTRY_FILE_PATTERNS' in plugin) {
patterns.push(...(pluginConfig.entry ?? plugin.PRODUCTION_ENTRY_FILE_PATTERNS));
}
}
}
if (patterns.length === 0) return [];
return [patterns.flat(), negatedTestFilePatterns].flat();
}

private getConfigurationFilePatterns(pluginName: PluginName) {
const plugin = plugins[pluginName];
const pluginConfig = this.getConfigForPlugin(pluginName);
Expand All @@ -270,8 +232,7 @@ export class WorkspaceWorker {
const ignore = this.getIgnorePatterns();

for (const [pluginName, plugin] of Object.entries(plugins) as PluginNames) {
const isIncludePlugin = this.isProduction ? `PRODUCTION_ENTRY_FILE_PATTERNS` in plugin : true;
if (this.enabled[pluginName] && isIncludePlugin) {
if (this.enabled[pluginName]) {
const hasDependencyFinder = 'findDependencies' in plugin && typeof plugin.findDependencies === 'function';
if (hasDependencyFinder) {
const pluginConfig = this.getConfigForPlugin(pluginName);
Expand All @@ -283,7 +244,9 @@ export class WorkspaceWorker {

debugLogArray(`Found ${plugin.NAME} config file paths`, configFilePaths);

if (configFilePaths.length === 0) continue;
// TODO Fix up
if (patterns.length > 0 && configFilePaths.length === 0) continue;
if (patterns.length === 0 && configFilePaths.length === 0) configFilePaths.push('fake');

const pluginDependencies: Set<string> = new Set();

Expand All @@ -295,12 +258,16 @@ export class WorkspaceWorker {
isProduction: this.isProduction,
});

dependencies.map(toPosix).forEach(specifier => {
dependencies.forEach(specifier => {
pluginDependencies.add(specifier);
this.referencedDependencies.add([configFilePath, specifier]);
if (isEntryPattern(specifier)) {
this.entryFilePatterns.add(fromEntryPattern(specifier));
} else if (isProductionEntryPattern(specifier)) {
this.productionEntryFilePatterns.add(fromProductionEntryPattern(specifier));
} else {
this.referencedDependencies.add([configFilePath, toPosix(specifier)]);
}
});

dependencies.forEach(dependency => pluginDependencies.add(dependency));
}

debugLogArray(`Dependencies referenced in ${plugin.NAME}`, pluginDependencies);
Expand All @@ -317,6 +284,8 @@ export class WorkspaceWorker {
installedBinaries: this.installedBinaries,
referencedDependencies: this.referencedDependencies,
enabledPlugins: this.enabledPlugins,
entryFilePatterns: Array.from(this.entryFilePatterns),
productionEntryFilePatterns: Array.from(this.productionEntryFilePatterns),
};
}
}
6 changes: 0 additions & 6 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ export const KNIP_CONFIG_LOCATIONS = ['knip.json', 'knip.jsonc', '.knip.json', '
// TS extensions: https://github.com/microsoft/TypeScript/blob/da8dfbf0ff6a94df65568fd048aec0d763c65811/src/compiler/types.ts#L7637-L7651
export const DEFAULT_EXTENSIONS = ['.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx', '.mts', '.cts'];

// This is ignored in --production mode (apart from what plugins would add)
export const TEST_FILE_PATTERNS = [
'**/*{.,-}{test,spec}.{js,jsx,ts,tsx,mjs,cjs}',
'**/{test,__tests__}/**/*.{js,jsx,ts,tsx,mjs,cjs}',
];

export const GLOBAL_IGNORE_PATTERNS = ['**/node_modules/**', '.yarn'];

// Binaries that are expected to be globally installed (i.e. https://www.npmjs.com/package/[name] is NOT the expected dependency)
Expand Down
51 changes: 30 additions & 21 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { compact } from './util/array.js';
import { debugLogObject, debugLogArray, debugLog } from './util/debug.js';
import { LoaderError } from './util/errors.js';
import { findFile } from './util/fs.js';
import { _glob } from './util/glob.js';
import { _glob, negate } from './util/glob.js';
import {
getEntryPathFromManifest,
getPackageNameFromFilePath,
Expand Down Expand Up @@ -168,23 +168,44 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => {
debugLogArray(`Found entry paths in package.json (${name})`, entryPathsFromManifest);
principal.addEntryPaths(entryPathsFromManifest);

// Get peerDependencies, installed binaries, entry files gathered through all plugins, and hand over
// A bit of an entangled hotchpotch, but it's all related, and efficient in terms of reading package.json once, etc.
const dependencies = await worker.findAllDependencies();
const {
referencedDependencies,
hostDependencies,
installedBinaries,
enabledPlugins,
entryFilePatterns,
productionEntryFilePatterns,
} = dependencies;

deputy.addHostDependencies(name, hostDependencies);
deputy.setInstalledBinaries(name, installedBinaries);
enabledPluginsStore.set(name, enabledPlugins);

referencedDependencies.forEach(([containingFilePath, specifier]) => {
handleReferencedDependency({ specifier, containingFilePath, principal, workspace });
});

if (isProduction) {
const negatedEntryPatterns: string[] = entryFilePatterns.map(negate);

{
const patterns = worker.getProductionEntryFilePatterns();
const patterns = worker.getProductionEntryFilePatterns(negatedEntryPatterns);
const workspaceEntryPaths = await _glob({ ...sharedGlobOptions, patterns });
debugLogArray(`Found entry paths (${name})`, workspaceEntryPaths);
principal.addEntryPaths(workspaceEntryPaths);
}

{
const patterns = worker.getProductionPluginEntryFilePatterns();
const pluginWorkspaceEntryPaths = await _glob({ ...sharedGlobOptions, patterns });
const pluginWorkspaceEntryPaths = await _glob({ ...sharedGlobOptions, patterns: productionEntryFilePatterns });
debugLogArray(`Found production plugin entry paths (${name})`, pluginWorkspaceEntryPaths);
principal.addEntryPaths(pluginWorkspaceEntryPaths, { skipExportsAnalysis: true });
}

{
const patterns = worker.getProductionProjectFilePatterns();
const patterns = worker.getProductionProjectFilePatterns(negatedEntryPatterns);
const workspaceProjectPaths = await _glob({ ...sharedGlobOptions, patterns });
debugLogArray(`Found project paths (${name})`, workspaceProjectPaths);
workspaceProjectPaths.forEach(projectPath => principal.addProjectPath(projectPath));
Expand All @@ -198,14 +219,14 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => {
}

{
const patterns = worker.getProjectFilePatterns();
const patterns = worker.getProjectFilePatterns([...productionEntryFilePatterns]);
const workspaceProjectPaths = await _glob({ ...sharedGlobOptions, patterns });
debugLogArray(`Found project paths (${name})`, workspaceProjectPaths);
workspaceProjectPaths.forEach(projectPath => principal.addProjectPath(projectPath));
}

{
const patterns = worker.getPluginEntryFilePatterns();
const patterns = [...entryFilePatterns, ...productionEntryFilePatterns];
const pluginWorkspaceEntryPaths = await _glob({ ...sharedGlobOptions, patterns });
debugLogArray(`Found plugin entry paths (${name})`, pluginWorkspaceEntryPaths);
principal.addEntryPaths(pluginWorkspaceEntryPaths, { skipExportsAnalysis: true });
Expand All @@ -227,21 +248,9 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => {
}

// Add knip.ts (might import dependencies)
if (chief.resolvedConfigFilePath)
if (chief.resolvedConfigFilePath) {
principal.addEntryPath(chief.resolvedConfigFilePath, { skipExportsAnalysis: true });

// Get peerDependencies, installed binaries, entry files gathered through all plugins, and hand over
// A bit of an entangled hotchpotch, but it's all related, and efficient in terms of reading package.json once, etc.
const dependencies = await worker.findAllDependencies();
const { referencedDependencies, hostDependencies, installedBinaries, enabledPlugins } = dependencies;

deputy.addHostDependencies(name, hostDependencies);
deputy.setInstalledBinaries(name, installedBinaries);
enabledPluginsStore.set(name, enabledPlugins);

referencedDependencies.forEach(([containingFilePath, specifier]) => {
handleReferencedDependency({ specifier, containingFilePath, principal, workspace });
});
}
}

const principals = factory.getPrincipals();
Expand Down
2 changes: 1 addition & 1 deletion src/types/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type IsPluginEnabledCallbackOptions = {

export type IsPluginEnabledCallback = (options: IsPluginEnabledCallbackOptions) => boolean | Promise<boolean>;

type GenericPluginCallbackOptions = {
export type GenericPluginCallbackOptions = {
cwd: string;
manifest: PackageJsonWithPlugins;
config: PluginConfiguration;
Expand Down
2 changes: 1 addition & 1 deletion src/util/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { _load as load } from './loader.js';
import { arrayify } from './array.js';
import type { RawPluginConfiguration } from 'src/types/config.js';
import type { RawPluginConfiguration } from '../types/config.js';

export const toCamelCase = (name: string) =>
name.toLowerCase().replace(/(-[a-z])/g, group => group.toUpperCase().replace('-', ''));
Expand Down
12 changes: 9 additions & 3 deletions src/util/protocols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ export const fromBinary = (specifier: string) => specifier.replace(/^(bin:)?/, '

export const isBinary = (specifier: string) => specifier.startsWith('bin:');

export const toEntryPattern = (specifier: string) => specifier.replace(/^(entry:)?/, 'entry:');
export const toEntryPattern = (specifier: string) => specifier.replace(/^(e:)?/, 'e:');

export const fromEntryPattern = (specifier: string) => specifier.replace(/^(entry:)?/, '');
export const fromEntryPattern = (specifier: string) => specifier.replace(/^(e:)?/, '');

export const isEntryPattern = (specifier: string) => specifier.startsWith('entry:');
export const isEntryPattern = (specifier: string) => specifier.startsWith('e:');

export const toProductionEntryPattern = (specifier: string) => specifier.replace(/^(p:)?/, 'p:');

export const fromProductionEntryPattern = (specifier: string) => specifier.replace(/^(p:)?/, '');

export const isProductionEntryPattern = (specifier: string) => specifier.startsWith('p:');

0 comments on commit 82278f8

Please sign in to comment.