knip 6.37.0 → 6.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ConfigurationChief.d.ts +18 -0
- package/dist/compilers/compilers.js +1 -1
- package/dist/constants.js +1 -0
- package/dist/graph/analyze.js +30 -17
- package/dist/graph-explorer/explorer.d.ts +1 -1
- package/dist/graph-explorer/operations/is-referenced.d.ts +1 -1
- package/dist/graph-explorer/operations/is-referenced.js +8 -4
- package/dist/plugins/_vue/auto-import.js +17 -10
- package/dist/plugins/angular/index.js +1 -1
- package/dist/plugins/cucumber/index.js +36 -4
- package/dist/plugins/cucumber/types.d.ts +2 -1
- package/dist/plugins/eslint/helpers.d.ts +2 -0
- package/dist/plugins/eslint/helpers.js +14 -3
- package/dist/plugins/eslint/resolveFromAST.js +12 -8
- package/dist/plugins/eve/index.js +26 -8
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.js +6 -0
- package/dist/plugins/mdx/index.d.ts +3 -0
- package/dist/plugins/mdx/index.js +16 -6
- package/dist/plugins/mocha/index.js +3 -1
- package/dist/plugins/mocha/types.d.ts +1 -0
- package/dist/plugins/oxlint/index.js +24 -8
- package/dist/plugins/oxlint/types.d.ts +1 -0
- package/dist/plugins/playwright/index.js +3 -3
- package/dist/plugins/plop/index.js +2 -2
- package/dist/plugins/railway/index.d.ts +3 -0
- package/dist/plugins/railway/index.js +129 -0
- package/dist/plugins/rstest/index.js +116 -17
- package/dist/plugins/rstest/types.d.ts +29 -5
- package/dist/plugins/rstest/visitors/mock.d.ts +2 -0
- package/dist/plugins/rstest/visitors/mock.js +52 -0
- package/dist/plugins/turbo/index.d.ts +3 -0
- package/dist/plugins/turbo/index.js +23 -0
- package/dist/plugins/typescript/content-mappers.d.ts +4 -0
- package/dist/plugins/typescript/content-mappers.js +4 -0
- package/dist/plugins/typescript/index.js +14 -18
- package/dist/plugins/typescript-content-mapper/index.d.ts +3 -0
- package/dist/plugins/typescript-content-mapper/index.js +23 -0
- package/dist/plugins/typescript-content-mapper/types.d.ts +3 -0
- package/dist/plugins/typescript-content-mapper/types.js +1 -0
- package/dist/plugins/vite/helpers.d.ts +1 -1
- package/dist/plugins/vite/helpers.js +7 -8
- package/dist/plugins/vitest/index.js +15 -5
- package/dist/plugins/vitest/types.d.ts +10 -0
- package/dist/schema/configuration.d.ts +45 -0
- package/dist/schema/plugins.d.ts +15 -0
- package/dist/schema/plugins.js +3 -0
- package/dist/types/PluginNames.d.ts +2 -2
- package/dist/types/PluginNames.js +3 -0
- package/dist/types/package-json.d.ts +5 -0
- package/dist/types/tsconfig-json.d.ts +1 -3
- package/dist/util/create-options.d.ts +30 -0
- package/dist/util/git.d.ts +1 -0
- package/dist/util/git.js +23 -1
- package/dist/util/package-json.d.ts +8 -0
- package/dist/util/tag.d.ts +1 -0
- package/dist/util/tag.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/schema.json +12 -0
|
@@ -1,25 +1,35 @@
|
|
|
1
|
-
import { toDependency } from '../../util/input.js';
|
|
1
|
+
import { toDeferResolveProductionEntry, toDependency } from '../../util/input.js';
|
|
2
|
+
import { isInternal } from '../../util/path.js';
|
|
2
3
|
import { hasDependency } from '../../util/plugin.js';
|
|
3
4
|
const title = 'MDX';
|
|
4
5
|
const enablers = ['astro', 'mdxlint'];
|
|
5
6
|
const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
|
|
6
7
|
const config = ['tsconfig.json'];
|
|
7
|
-
const takeDependencies = (
|
|
8
|
+
const takeDependencies = (plugins) => {
|
|
8
9
|
const inputs = [];
|
|
9
|
-
if (Array.isArray(
|
|
10
|
-
for (const plugin of
|
|
10
|
+
if (Array.isArray(plugins)) {
|
|
11
|
+
for (const plugin of plugins) {
|
|
11
12
|
if (typeof plugin === 'string')
|
|
12
13
|
inputs.push(toDependency(plugin));
|
|
13
|
-
else if (typeof plugin[0] === 'string')
|
|
14
|
+
else if (Array.isArray(plugin) && typeof plugin[0] === 'string')
|
|
14
15
|
inputs.push(toDependency(plugin[0]));
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
return inputs;
|
|
18
19
|
};
|
|
20
|
+
export const contentMappers = ['mdx-content-mapper', '@mdx-js/content-mapper'];
|
|
21
|
+
const bundledPlugins = ['remark-mdx-frontmatter'];
|
|
22
|
+
export const resolveContentMapperOptions = (options) => {
|
|
23
|
+
const inputs = takeDependencies(options.remarkPlugins).filter(input => !bundledPlugins.includes(input.specifier));
|
|
24
|
+
const provider = options.providerImportSource;
|
|
25
|
+
if (typeof provider === 'string' && isInternal(provider))
|
|
26
|
+
inputs.push(toDeferResolveProductionEntry(provider));
|
|
27
|
+
return inputs;
|
|
28
|
+
};
|
|
19
29
|
const resolveConfig = async (config, options) => {
|
|
20
30
|
const { configFileName } = options;
|
|
21
31
|
if (configFileName === 'tsconfig.json' && 'mdx' in config)
|
|
22
|
-
return takeDependencies(config.mdx);
|
|
32
|
+
return takeDependencies(config.mdx.plugins);
|
|
23
33
|
return [];
|
|
24
34
|
};
|
|
25
35
|
const plugin = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toDeferResolve, toEntry } from '../../util/input.js';
|
|
1
|
+
import { toDeferResolve, toDeferResolveEntry, toEntry } from '../../util/input.js';
|
|
2
2
|
import { isDirectory } from '../../util/fs.js';
|
|
3
3
|
import { hasDependency } from '../../util/plugin.js';
|
|
4
4
|
const title = 'Mocha';
|
|
@@ -9,9 +9,11 @@ const entry = ['**/test/*.{js,cjs,mjs}'];
|
|
|
9
9
|
const defaultExtensions = ['js', 'cjs', 'mjs'];
|
|
10
10
|
const resolveConfig = localConfig => {
|
|
11
11
|
const entryPatterns = localConfig.spec ? [localConfig.spec].flat() : entry;
|
|
12
|
+
const files = localConfig.file ? [localConfig.file].flat() : [];
|
|
12
13
|
const require = localConfig.require ? [localConfig.require].flat() : [];
|
|
13
14
|
const inputs = [];
|
|
14
15
|
inputs.push(...entryPatterns.map(id => toEntry(id)));
|
|
16
|
+
inputs.push(...files.map(id => toDeferResolveEntry(id)));
|
|
15
17
|
inputs.push(...require.map(id => toDeferResolve(id)));
|
|
16
18
|
return inputs;
|
|
17
19
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Visitor } from 'oxc-parser';
|
|
2
2
|
import { findProperty, getPropertyValues } from '../../typescript/ast-helpers.js';
|
|
3
|
-
import { toDependency, toEntry } from '../../util/input.js';
|
|
4
|
-
import { isInternal } from '../../util/path.js';
|
|
3
|
+
import { toConfig, toDependency, toEntry } from '../../util/input.js';
|
|
4
|
+
import { dirname, isInternal, toAbsolute } from '../../util/path.js';
|
|
5
5
|
import { hasDependency } from '../../util/plugin.js';
|
|
6
6
|
import { getInputsFromSettings } from '../eslint/helpers.js';
|
|
7
7
|
import { getInputsFromSettingsAST } from '../eslint/resolveFromAST.js';
|
|
@@ -13,28 +13,42 @@ const isViteConfig = (configFileName) => configFileName.startsWith('vite.config.
|
|
|
13
13
|
const args = {
|
|
14
14
|
config: true,
|
|
15
15
|
};
|
|
16
|
-
const resolveJsPlugins = (jsPlugins) => {
|
|
16
|
+
const resolveJsPlugins = (jsPlugins, configFilePath) => {
|
|
17
17
|
const inputs = [];
|
|
18
|
+
const dir = dirname(configFilePath);
|
|
18
19
|
for (const plugin of jsPlugins ?? []) {
|
|
19
20
|
const specifier = typeof plugin === 'string' ? plugin : plugin.specifier;
|
|
20
21
|
if (!isInternal(specifier))
|
|
21
22
|
inputs.push(toDependency(specifier));
|
|
22
23
|
else
|
|
23
|
-
inputs.push(toEntry(specifier));
|
|
24
|
+
inputs.push(toEntry(toAbsolute(specifier, dir)));
|
|
24
25
|
}
|
|
25
26
|
return inputs;
|
|
26
27
|
};
|
|
27
28
|
const isLoadConfig = ({ configFileName }) => !isViteConfig(configFileName);
|
|
28
|
-
const
|
|
29
|
-
const inputs =
|
|
29
|
+
const resolveExtendedConfig = (config, configFilePath) => {
|
|
30
|
+
const inputs = [];
|
|
31
|
+
for (const entry of config.extends ?? []) {
|
|
32
|
+
if (typeof entry === 'string') {
|
|
33
|
+
const filePath = toAbsolute(entry, dirname(configFilePath));
|
|
34
|
+
inputs.push(toConfig('oxlint', filePath, { containingFilePath: configFilePath }));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
for (const input of resolveExtendedConfig(entry, configFilePath))
|
|
38
|
+
inputs.push(input);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const input of resolveJsPlugins(config.jsPlugins, configFilePath))
|
|
42
|
+
inputs.push(input);
|
|
30
43
|
for (const override of config.overrides ?? []) {
|
|
31
|
-
for (const input of resolveJsPlugins(override.jsPlugins))
|
|
44
|
+
for (const input of resolveJsPlugins(override.jsPlugins, configFilePath))
|
|
32
45
|
inputs.push(input);
|
|
33
46
|
}
|
|
34
47
|
for (const input of getInputsFromSettings(config.settings))
|
|
35
48
|
inputs.push(input);
|
|
36
49
|
return inputs;
|
|
37
50
|
};
|
|
51
|
+
const resolveConfig = (config, options) => resolveExtendedConfig(config, options.configFilePath);
|
|
38
52
|
const resolveFromAST = (program, options) => {
|
|
39
53
|
if (!isViteConfig(options.configFileName))
|
|
40
54
|
return [];
|
|
@@ -55,8 +69,9 @@ const resolveFromAST = (program, options) => {
|
|
|
55
69
|
},
|
|
56
70
|
});
|
|
57
71
|
visitor.visit(program);
|
|
58
|
-
return [...resolveJsPlugins([...jsPlugins]), ...getInputsFromSettingsAST(program)];
|
|
72
|
+
return [...resolveJsPlugins([...jsPlugins], options.configFilePath), ...getInputsFromSettingsAST(program)];
|
|
59
73
|
};
|
|
74
|
+
const isFilterTransitiveDependencies = true;
|
|
60
75
|
const plugin = {
|
|
61
76
|
title,
|
|
62
77
|
enablers,
|
|
@@ -65,6 +80,7 @@ const plugin = {
|
|
|
65
80
|
isLoadConfig,
|
|
66
81
|
resolveConfig,
|
|
67
82
|
resolveFromAST,
|
|
83
|
+
isFilterTransitiveDependencies,
|
|
68
84
|
args,
|
|
69
85
|
};
|
|
70
86
|
export default plugin;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { arrayify } from '../../util/array.js';
|
|
2
2
|
import { toDeferResolve, toEntry } from '../../util/input.js';
|
|
3
|
-
import { join, relative } from '../../util/path.js';
|
|
3
|
+
import { join, relative, toAbsolute } from '../../util/path.js';
|
|
4
4
|
import { hasDependency } from '../../util/plugin.js';
|
|
5
5
|
const title = 'Playwright';
|
|
6
6
|
const enablers = ['@playwright/test'];
|
|
@@ -18,9 +18,9 @@ export const resolveConfig = async (localConfig, options) => {
|
|
|
18
18
|
const { cwd, configFileDir } = options;
|
|
19
19
|
const inputs = [];
|
|
20
20
|
for (const id of arrayify(localConfig.globalSetup))
|
|
21
|
-
inputs.push(toEntry(id));
|
|
21
|
+
inputs.push(toEntry(toAbsolute(id, configFileDir)));
|
|
22
22
|
for (const id of arrayify(localConfig.globalTeardown))
|
|
23
|
-
inputs.push(toEntry(id));
|
|
23
|
+
inputs.push(toEntry(toAbsolute(id, configFileDir)));
|
|
24
24
|
const projects = localConfig.projects ? [localConfig, ...localConfig.projects] : [localConfig];
|
|
25
25
|
const reporters = [localConfig.reporter].flat().flatMap(reporter => {
|
|
26
26
|
const name = typeof reporter === 'string' ? reporter : reporter?.[0];
|
|
@@ -2,11 +2,11 @@ import { hasDependency } from '../../util/plugin.js';
|
|
|
2
2
|
const title = 'Plop';
|
|
3
3
|
const enablers = ['plop'];
|
|
4
4
|
const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
|
|
5
|
-
const
|
|
5
|
+
const entry = ['plopfile.{cjs,mjs,js,ts}'];
|
|
6
6
|
const plugin = {
|
|
7
7
|
title,
|
|
8
8
|
enablers,
|
|
9
9
|
isEnabled,
|
|
10
|
-
|
|
10
|
+
entry,
|
|
11
11
|
};
|
|
12
12
|
export default plugin;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Visitor } from 'oxc-parser';
|
|
2
|
+
import { findProperty, getFirstPropertyValue, getStringValues } from '../../typescript/ast-helpers.js';
|
|
3
|
+
import { getStringValue } from '../../typescript/ast-nodes.js';
|
|
4
|
+
import { getGitRemoteUrls } from '../../util/git.js';
|
|
5
|
+
import { isEntry } from '../../util/input.js';
|
|
6
|
+
import { join, toAbsolute } from '../../util/path.js';
|
|
7
|
+
import { hasDependency } from '../../util/plugin.js';
|
|
8
|
+
const title = 'Railway';
|
|
9
|
+
const enablers = ['railway'];
|
|
10
|
+
const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
|
|
11
|
+
const config = ['.railway/railway.ts'];
|
|
12
|
+
const args = {
|
|
13
|
+
config: ['file'],
|
|
14
|
+
resolve: ['runner'],
|
|
15
|
+
};
|
|
16
|
+
const getCommands = (node) => {
|
|
17
|
+
const command = getStringValue(node);
|
|
18
|
+
return command ? [command] : [...getStringValues(node)];
|
|
19
|
+
};
|
|
20
|
+
const isExternalSourceType = (type) => type === 'github' || type === 'image' || type === 'template';
|
|
21
|
+
const getGitHubRepository = (value) => {
|
|
22
|
+
const normalized = value
|
|
23
|
+
?.trim()
|
|
24
|
+
.replace(/^git\+/, '')
|
|
25
|
+
.replace(/\.git\/?$/, '')
|
|
26
|
+
.replace(/\/$/, '');
|
|
27
|
+
return normalized?.match(/(?:^|github(?:\.com)?[/:])([^/:]+\/[^/]+)$/i)?.[1].toLowerCase();
|
|
28
|
+
};
|
|
29
|
+
const resolveFromAST = (program, options) => {
|
|
30
|
+
const serviceNames = new Set();
|
|
31
|
+
const sourceTypes = new Map();
|
|
32
|
+
const bindings = new Map();
|
|
33
|
+
const localRepositories = new Set();
|
|
34
|
+
for (const repository of [options.manifest.repository, options.rootManifest?.repository]) {
|
|
35
|
+
const name = getGitHubRepository(typeof repository === 'string' ? repository : repository?.url);
|
|
36
|
+
if (name)
|
|
37
|
+
localRepositories.add(name);
|
|
38
|
+
}
|
|
39
|
+
for (const url of getGitRemoteUrls(options.rootCwd)) {
|
|
40
|
+
const name = getGitHubRepository(url);
|
|
41
|
+
if (name)
|
|
42
|
+
localRepositories.add(name);
|
|
43
|
+
}
|
|
44
|
+
for (const node of program.body) {
|
|
45
|
+
if (node.type === 'ImportDeclaration' && getStringValue(node.source) === 'railway/iac') {
|
|
46
|
+
for (const specifier of node.specifiers) {
|
|
47
|
+
if (specifier.type !== 'ImportSpecifier' || specifier.imported.type !== 'Identifier')
|
|
48
|
+
continue;
|
|
49
|
+
if (specifier.imported.name === 'service')
|
|
50
|
+
serviceNames.add(specifier.local.name);
|
|
51
|
+
if (isExternalSourceType(specifier.imported.name)) {
|
|
52
|
+
sourceTypes.set(specifier.local.name, specifier.imported.name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (node.type === 'VariableDeclaration') {
|
|
57
|
+
for (const declaration of node.declarations) {
|
|
58
|
+
if (declaration.id.type === 'Identifier' && declaration.init)
|
|
59
|
+
bindings.set(declaration.id.name, declaration.init);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const resolveBinding = (node) => {
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
while (node?.type === 'Identifier' && bindings.has(node.name) && !seen.has(node.name)) {
|
|
66
|
+
seen.add(node.name);
|
|
67
|
+
node = bindings.get(node.name);
|
|
68
|
+
}
|
|
69
|
+
return node;
|
|
70
|
+
};
|
|
71
|
+
const inputs = [];
|
|
72
|
+
const visitor = new Visitor({
|
|
73
|
+
CallExpression(node) {
|
|
74
|
+
if (node.callee.type !== 'Identifier' || !serviceNames.has(node.callee.name))
|
|
75
|
+
return;
|
|
76
|
+
const serviceConfig = node.arguments[1];
|
|
77
|
+
if (serviceConfig?.type !== 'ObjectExpression')
|
|
78
|
+
return;
|
|
79
|
+
const source = resolveBinding(findProperty(serviceConfig, 'source'));
|
|
80
|
+
const repository = getFirstPropertyValue(source, 'repo');
|
|
81
|
+
const image = getFirstPropertyValue(source, 'image');
|
|
82
|
+
const template = getFirstPropertyValue(source, 'template');
|
|
83
|
+
const sourceType = (source?.type === 'CallExpression' && source.callee.type === 'Identifier'
|
|
84
|
+
? sourceTypes.get(source.callee.name)
|
|
85
|
+
: undefined) ??
|
|
86
|
+
getFirstPropertyValue(source, 'type') ??
|
|
87
|
+
(repository ? 'github' : image ? 'image' : template ? 'template' : undefined);
|
|
88
|
+
const sourceRepository = getGitHubRepository(source?.type === 'CallExpression' ? getStringValue(source.arguments[0]) : repository);
|
|
89
|
+
if (sourceType === 'image' ||
|
|
90
|
+
sourceType === 'template' ||
|
|
91
|
+
(sourceType === 'github' &&
|
|
92
|
+
sourceRepository !== undefined &&
|
|
93
|
+
localRepositories.size > 0 &&
|
|
94
|
+
!localRepositories.has(sourceRepository))) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const sourceConfig = source?.type === 'CallExpression' ? source.arguments[1] : source;
|
|
98
|
+
const rootDirectory = getFirstPropertyValue(sourceConfig, 'rootDirectory') ??
|
|
99
|
+
getFirstPropertyValue(serviceConfig, 'root') ??
|
|
100
|
+
getFirstPropertyValue(serviceConfig, 'rootDirectory');
|
|
101
|
+
const cwd = rootDirectory ? join(options.cwd, rootDirectory) : options.cwd;
|
|
102
|
+
const manifest = options.getManifest(cwd) ?? options.manifest;
|
|
103
|
+
const build = findProperty(serviceConfig, 'build');
|
|
104
|
+
const commands = [
|
|
105
|
+
...getCommands(build),
|
|
106
|
+
...getCommands(findProperty(build, 'buildCommand')),
|
|
107
|
+
...getCommands(findProperty(serviceConfig, 'start')),
|
|
108
|
+
...getCommands(findProperty(serviceConfig, 'preDeploy')),
|
|
109
|
+
...getCommands(findProperty(findProperty(serviceConfig, 'deploy'), 'preDeployCommand')),
|
|
110
|
+
];
|
|
111
|
+
for (const input of options.getInputsFromScripts(commands, { optionalBinaries: true, cwd, manifest })) {
|
|
112
|
+
if (isEntry(input))
|
|
113
|
+
input.specifier = toAbsolute(input.specifier, cwd);
|
|
114
|
+
inputs.push({ ...input, dir: cwd });
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
visitor.visit(program);
|
|
119
|
+
return inputs;
|
|
120
|
+
};
|
|
121
|
+
const plugin = {
|
|
122
|
+
title,
|
|
123
|
+
enablers,
|
|
124
|
+
isEnabled,
|
|
125
|
+
config,
|
|
126
|
+
args,
|
|
127
|
+
resolveFromAST,
|
|
128
|
+
};
|
|
129
|
+
export default plugin;
|
|
@@ -1,31 +1,130 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { arrayify } from '../../util/array.js';
|
|
2
|
+
import { findFileWithExtensions } from '../../util/fs.js';
|
|
3
|
+
import { _dirGlob, _glob, negate } from '../../util/glob.js';
|
|
4
|
+
import { toAlias, toConfig, toDeferResolve, toDependency, toEntry } from '../../util/input.js';
|
|
5
|
+
import { dirname, join, relative, toAbsolute } from '../../util/path.js';
|
|
6
|
+
import { hasDependency, load } from '../../util/plugin.js';
|
|
7
|
+
import { createRstestMockVisitor } from './visitors/mock.js';
|
|
3
8
|
const title = 'Rstest';
|
|
4
9
|
const enablers = ['@rstest/core'];
|
|
5
10
|
const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
|
|
11
|
+
const configExtensions = ['.mts', '.mjs', '.ts', '.js', '.cjs', '.cts'];
|
|
6
12
|
const config = ['rstest.config.{js,cjs,mjs,ts,cts,mts}'];
|
|
7
13
|
const mocks = ['**/__mocks__/**/*.?(c|m)[jt]s?(x)'];
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
14
|
+
const testEntry = ['**/*.{test,spec}.?(c|m)[jt]s?(x)'];
|
|
15
|
+
const entry = [...testEntry, ...mocks];
|
|
16
|
+
const getEnvironmentDependency = (name) => (name && name !== 'node' ? [toDependency(name)] : []);
|
|
17
|
+
const replaceRootDir = (path, root) => path.replace('<rootDir>', root);
|
|
18
|
+
const configParams = { env: 'test', command: 'run', envMode: 'test' };
|
|
19
|
+
const getConfig = async (config) => typeof config === 'function' ? await config(configParams) : config;
|
|
20
|
+
const resolveConfig = async (localConfig, options) => {
|
|
21
|
+
const { cwd, configFileDir, configFilePath, getManifest, isResolvedConfigFile } = options;
|
|
22
|
+
const cfg = await getConfig(localConfig);
|
|
23
|
+
const runnerRoot = toAbsolute(cfg.root ?? '.', isResolvedConfigFile ? configFileDir : cwd);
|
|
24
|
+
const manifest = getManifest(cwd);
|
|
25
|
+
const seen = new Set([configFilePath]);
|
|
26
|
+
const inputs = [];
|
|
27
|
+
const addProject = async (project, projectRoot) => {
|
|
28
|
+
const toPattern = (pattern) => relative(cwd, toAbsolute(replaceRootDir(pattern, projectRoot), projectRoot));
|
|
29
|
+
if (!options.config.entry) {
|
|
30
|
+
const include = [];
|
|
31
|
+
const exclude = [...(Array.isArray(project.exclude) ? project.exclude : (project.exclude?.patterns ?? []))];
|
|
32
|
+
for (const pattern of [...(project.include ?? testEntry), ...mocks]) {
|
|
33
|
+
if (pattern.startsWith('!'))
|
|
34
|
+
exclude.push(pattern.slice(1));
|
|
35
|
+
else
|
|
36
|
+
include.push(toPattern(pattern));
|
|
37
|
+
}
|
|
38
|
+
const entries = exclude.length === 0
|
|
39
|
+
? include
|
|
40
|
+
: await _glob({ cwd, patterns: [...include, ...exclude.map(pattern => negate(toPattern(pattern)))] });
|
|
41
|
+
for (const entry of entries)
|
|
42
|
+
inputs.push(toEntry(entry));
|
|
43
|
+
}
|
|
44
|
+
const env = project.testEnvironment;
|
|
45
|
+
inputs.push(...getEnvironmentDependency(typeof env === 'string' ? env : env?.name));
|
|
46
|
+
for (const specifier of [project.setupFiles ?? [], project.globalSetup ?? []].flat()) {
|
|
47
|
+
inputs.push(toDeferResolve(replaceRootDir(specifier, projectRoot), { dir: projectRoot }));
|
|
48
|
+
}
|
|
49
|
+
if (project.browser?.enabled) {
|
|
50
|
+
inputs.push(toDependency('@rstest/browser'), toDependency('playwright'));
|
|
51
|
+
}
|
|
52
|
+
for (const [name, value] of Object.entries(project.resolve?.alias ?? {})) {
|
|
53
|
+
const prefixes = [];
|
|
54
|
+
for (const target of arrayify(value))
|
|
55
|
+
prefixes.push(target.startsWith('.') ? join(runnerRoot, target) : target);
|
|
56
|
+
if (name.endsWith('$')) {
|
|
57
|
+
inputs.push(toAlias(name.slice(0, -1), prefixes));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
inputs.push(toAlias(name, prefixes), toAlias(`${name}/*`, prefixes.map(prefix => `${prefix}/*`)));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const addProjects = async (projects, projectsRoot) => {
|
|
65
|
+
const patterns = [];
|
|
66
|
+
for (const project of projects) {
|
|
67
|
+
if (typeof project === 'string')
|
|
68
|
+
patterns.push(replaceRootDir(project, projectsRoot));
|
|
69
|
+
else
|
|
70
|
+
await addProject(project, toAbsolute(replaceRootDir(project.root ?? projectsRoot, projectsRoot), runnerRoot));
|
|
71
|
+
}
|
|
72
|
+
const [files, dirs] = await Promise.all([
|
|
73
|
+
_glob({ cwd: projectsRoot, patterns, gitignore: false }),
|
|
74
|
+
_dirGlob({ cwd: projectsRoot, patterns, gitignore: false }),
|
|
75
|
+
]);
|
|
76
|
+
for (const dir of dirs) {
|
|
77
|
+
const projectRoot = toAbsolute(dir, projectsRoot);
|
|
78
|
+
const filePath = findFileWithExtensions(join(projectRoot, 'rstest.config'), configExtensions);
|
|
79
|
+
if (filePath)
|
|
80
|
+
files.push(filePath);
|
|
81
|
+
else
|
|
82
|
+
await addProject({}, projectRoot);
|
|
83
|
+
}
|
|
84
|
+
for (const filePath of files) {
|
|
85
|
+
if (seen.has(filePath))
|
|
86
|
+
continue;
|
|
87
|
+
seen.add(filePath);
|
|
88
|
+
const project = getManifest(dirname(filePath)) === manifest
|
|
89
|
+
? await load(filePath)
|
|
90
|
+
.then(getConfig)
|
|
91
|
+
.catch(() => undefined)
|
|
92
|
+
: undefined;
|
|
93
|
+
if (!project) {
|
|
94
|
+
inputs.push(toConfig('rstest', filePath, { containingFilePath: configFilePath }));
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
inputs.push(toEntry(filePath));
|
|
98
|
+
if (project.projects)
|
|
99
|
+
await addProjects(project.projects, dirname(filePath));
|
|
100
|
+
else
|
|
101
|
+
await addProject(project, toAbsolute(project.root ?? dirname(filePath), runnerRoot));
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
if (cfg.projects)
|
|
105
|
+
await addProjects(cfg.projects, runnerRoot);
|
|
106
|
+
else
|
|
107
|
+
await addProject(cfg, runnerRoot);
|
|
108
|
+
if (!isResolvedConfigFile) {
|
|
109
|
+
inputs.push(toDependency(`@rstest/coverage-${cfg.coverage?.provider ?? 'istanbul'}`, { optional: true }));
|
|
110
|
+
}
|
|
111
|
+
return inputs;
|
|
112
|
+
};
|
|
113
|
+
const args = {
|
|
114
|
+
config: true,
|
|
115
|
+
resolveInputs: parsed => getEnvironmentDependency(parsed.testEnvironment),
|
|
116
|
+
};
|
|
117
|
+
const registerVisitors = ({ ctx, registerVisitor }) => {
|
|
118
|
+
registerVisitor(createRstestMockVisitor(ctx));
|
|
23
119
|
};
|
|
24
120
|
const plugin = {
|
|
25
121
|
title,
|
|
26
122
|
enablers,
|
|
27
123
|
isEnabled,
|
|
28
124
|
config,
|
|
125
|
+
entry,
|
|
29
126
|
resolveConfig,
|
|
127
|
+
args,
|
|
128
|
+
registerVisitors,
|
|
30
129
|
};
|
|
31
130
|
export default plugin;
|
|
@@ -1,8 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
type TestEnvironment = 'node' | 'jsdom' | 'happy-dom';
|
|
2
|
+
export type RstestProjectConfig = {
|
|
3
|
+
root?: string;
|
|
2
4
|
include?: string[];
|
|
3
|
-
exclude?: string[]
|
|
4
|
-
|
|
5
|
-
name: 'node' | 'jsdom' | 'happy-dom';
|
|
5
|
+
exclude?: string[] | {
|
|
6
|
+
patterns: string[];
|
|
6
7
|
};
|
|
7
|
-
|
|
8
|
+
testEnvironment?: TestEnvironment | {
|
|
9
|
+
name: TestEnvironment;
|
|
10
|
+
};
|
|
11
|
+
setupFiles?: string | string[];
|
|
12
|
+
globalSetup?: string | string[];
|
|
13
|
+
browser?: {
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
};
|
|
16
|
+
resolve?: {
|
|
17
|
+
alias?: Record<string, string | false | (string | false)[]>;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
type RstestConfig = RstestProjectConfig & {
|
|
21
|
+
projects?: (string | RstestProjectConfig)[];
|
|
22
|
+
coverage?: {
|
|
23
|
+
provider?: 'istanbul' | 'v8';
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
type ConfigParams = {
|
|
27
|
+
env: string;
|
|
28
|
+
command: string;
|
|
29
|
+
envMode: string;
|
|
8
30
|
};
|
|
31
|
+
export type RstestConfigOrFn = RstestConfig | ((params: ConfigParams) => RstestConfig | Promise<RstestConfig>);
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { IMPORT_FLAGS } from '../../../constants.js';
|
|
2
|
+
import { getStringValue, isStringLiteral } from '../../../typescript/ast-nodes.js';
|
|
3
|
+
import { isShadowed } from '../../../typescript/visitors/walk.js';
|
|
4
|
+
const rstestApiNames = new Set(['rs', 'rstest']);
|
|
5
|
+
const mockMethods = new Set(['mock', 'doMock']);
|
|
6
|
+
export function createRstestMockVisitor(ctx) {
|
|
7
|
+
const rsNames = new Set();
|
|
8
|
+
return {
|
|
9
|
+
Program(node) {
|
|
10
|
+
rsNames.clear();
|
|
11
|
+
for (const statement of node.body) {
|
|
12
|
+
if (statement.type !== 'ImportDeclaration' ||
|
|
13
|
+
!isStringLiteral(statement.source) ||
|
|
14
|
+
getStringValue(statement.source) !== '@rstest/core')
|
|
15
|
+
continue;
|
|
16
|
+
for (const specifier of statement.specifiers ?? []) {
|
|
17
|
+
if (specifier.type === 'ImportSpecifier' &&
|
|
18
|
+
specifier.imported.type === 'Identifier' &&
|
|
19
|
+
rstestApiNames.has(specifier.imported.name)) {
|
|
20
|
+
rsNames.add(specifier.local.name);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
CallExpression(node) {
|
|
26
|
+
const argument = node.arguments[0];
|
|
27
|
+
const factory = node.arguments[1];
|
|
28
|
+
const factoryBody = factory?.type === 'ArrowFunctionExpression' &&
|
|
29
|
+
factory.body.type === 'ParenthesizedExpression' &&
|
|
30
|
+
factory.body.expression.type === 'ObjectExpression'
|
|
31
|
+
? factory.body.expression
|
|
32
|
+
: undefined;
|
|
33
|
+
if (argument?.type !== 'ImportExpression' ||
|
|
34
|
+
!isStringLiteral(argument.source) ||
|
|
35
|
+
factory?.type !== 'ArrowFunctionExpression' ||
|
|
36
|
+
!factoryBody ||
|
|
37
|
+
factory.async ||
|
|
38
|
+
factory.params.length > 0 ||
|
|
39
|
+
factoryBody.properties.some(property => property.type === 'SpreadElement') ||
|
|
40
|
+
node.callee.type !== 'MemberExpression' ||
|
|
41
|
+
node.callee.computed ||
|
|
42
|
+
node.callee.object.type !== 'Identifier' ||
|
|
43
|
+
!rsNames.has(node.callee.object.name) ||
|
|
44
|
+
isShadowed(node.callee.object.name, node.callee.object.start) ||
|
|
45
|
+
node.callee.property.type !== 'Identifier' ||
|
|
46
|
+
!mockMethods.has(node.callee.property.name))
|
|
47
|
+
return;
|
|
48
|
+
ctx.markImportExpressionHandled(argument.start);
|
|
49
|
+
ctx.addImport(getStringValue(argument.source), argument.source.start, IMPORT_FLAGS.SIDE_EFFECTS);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { toEntry } from '../../util/input.js';
|
|
2
|
+
import { hasDependency } from '../../util/plugin.js';
|
|
3
|
+
const title = 'Turborepo';
|
|
4
|
+
const enablers = ['turbo', '@turbo/gen'];
|
|
5
|
+
const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
|
|
6
|
+
const extensions = '{ts,js,cjs,mts,mjs}';
|
|
7
|
+
const entry = [`turbo/generators/config.${extensions}`];
|
|
8
|
+
const resolve = ({ cwd, rootCwd }) => (cwd === rootCwd ? [toEntry(`plopfile.${extensions}`)] : []);
|
|
9
|
+
const args = {
|
|
10
|
+
string: ['config'],
|
|
11
|
+
resolveInputs: parsed => (parsed._[0] === 'gen' || parsed._[0] === 'generate') && typeof parsed.config === 'string'
|
|
12
|
+
? [toEntry(parsed.config)]
|
|
13
|
+
: [],
|
|
14
|
+
};
|
|
15
|
+
const plugin = {
|
|
16
|
+
title,
|
|
17
|
+
enablers,
|
|
18
|
+
isEnabled,
|
|
19
|
+
entry,
|
|
20
|
+
resolve,
|
|
21
|
+
args,
|
|
22
|
+
};
|
|
23
|
+
export default plugin;
|