Skip to content

Commit 3441bea

Browse files
authored
fix(remark): support all types of dependency names (#1911)
1 parent aa303fb commit 3441bea

16 files changed

Lines changed: 322 additions & 7 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@fixtures/remark-fallback-scoped",
3+
"scripts": {
4+
"format": "remark README.md -o"
5+
},
6+
"devDependencies": {
7+
"remark-cli": "*",
8+
"@scope/pkg-b": "*"
9+
},
10+
"remarkConfig": {
11+
"plugins": [
12+
"@scope/pkg-b"
13+
]
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@fixtures/remark-fallback-unscoped",
3+
"scripts": {
4+
"format": "remark README.md -o"
5+
},
6+
"devDependencies": {
7+
"remark-cli": "*",
8+
"pkg-a": "*"
9+
},
10+
"remarkConfig": {
11+
"plugins": [
12+
"pkg-a"
13+
]
14+
}
15+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function localRemarkPlugin() {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@fixtures/remark-local-path",
3+
"scripts": {
4+
"format": "remark README.md -o"
5+
},
6+
"devDependencies": {
7+
"remark-cli": "*"
8+
},
9+
"remarkConfig": {
10+
"plugins": [
11+
"./index.js"
12+
]
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@fixtures/remark-missing-placeholder",
3+
"scripts": {
4+
"format": "remark README.md -o"
5+
},
6+
"devDependencies": {
7+
"remark-cli": "*"
8+
},
9+
"remarkConfig": {
10+
"plugins": [
11+
"pkg-c"
12+
]
13+
}
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@fixtures/remark-primary-scoped",
3+
"scripts": {
4+
"format": "remark README.md -o"
5+
},
6+
"devDependencies": {
7+
"remark-cli": "*",
8+
"@scope/remark-pkg-b": "*"
9+
},
10+
"remarkConfig": {
11+
"plugins": [
12+
"@scope/pkg-b"
13+
]
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@fixtures/remark-primary-unscoped",
3+
"scripts": {
4+
"format": "remark README.md -o"
5+
},
6+
"devDependencies": {
7+
"remark-cli": "*",
8+
"remark-pkg-a": "*"
9+
},
10+
"remarkConfig": {
11+
"plugins": [
12+
"pkg-a"
13+
]
14+
}
15+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { type Input, toDeferResolve, toDependency } from '../../util/input.ts';
2+
import type { Manifest } from '../../util/package-json.ts';
3+
import { isInternal } from '../../util/path.ts';
4+
5+
const getCandidates = (prefix: string, identifier: string): string[] => {
6+
if (isInternal(identifier)) return [identifier];
7+
8+
if (identifier.startsWith('@')) {
9+
const [scope, name, ...rest] = identifier.split('/');
10+
if (rest.length > 0) return [identifier];
11+
if (scope) {
12+
if (!name) return [[scope, prefix].join('/')];
13+
if (name.startsWith(prefix)) return [identifier];
14+
return [[scope, prefix + name].join('/'), identifier];
15+
}
16+
}
17+
18+
const [name, ...rest] = identifier.split('/');
19+
if (rest.length > 0) return [identifier];
20+
if (name.startsWith(prefix)) return [identifier];
21+
return [prefix + name, name];
22+
};
23+
24+
const getDeclaredDependencies = (manifest: Manifest) =>
25+
new Set([
26+
...Object.keys(manifest.dependencies ?? {}),
27+
...Object.keys(manifest.devDependencies ?? {}),
28+
...Object.keys(manifest.optionalDependencies ?? {}),
29+
...Object.keys(manifest.peerDependencies ?? {}),
30+
]);
31+
32+
const pickCandidate = (prefix: string, identifier: string, manifest: Manifest) => {
33+
const candidates = getCandidates(prefix, identifier);
34+
if (candidates.length === 1 || isInternal(candidates[0])) return candidates[0];
35+
36+
const dependencies = getDeclaredDependencies(manifest);
37+
return candidates.find(candidate => dependencies.has(candidate)) ?? candidates[0];
38+
};
39+
40+
// Resolve plugins for https://github.com/wooorm/load-plugin
41+
export const resolveLoadPluginStylePluginName = (prefix: string, identifier: string, manifest: Manifest): Input => {
42+
// https://github.com/wooorm/load-plugin/blob/4a1b7231e20f64be52625ff3469bbf111dda5949/lib/index.js#L104
43+
prefix = prefix + (prefix.at(-1) === '-' ? '' : '-');
44+
45+
const candidate = pickCandidate(prefix, identifier, manifest);
46+
47+
return isInternal(candidate) ? toDeferResolve(candidate) : toDependency(candidate);
48+
};

packages/knip/src/plugins/remark/index.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { IsPluginEnabled, Plugin, ResolveConfig } from '../../types/config.ts';
2-
import { toDeferResolve } from '../../util/input.ts';
3-
import { isInternal } from '../../util/path.ts';
42
import { hasDependency } from '../../util/plugin.ts';
53
import type { RemarkConfig } from './types.ts';
4+
import { resolveLoadPluginStylePluginName } from './helpers.ts';
65

76
// https://github.com/remarkjs/remark/blob/main/packages/remark-cli/readme.md
87

@@ -16,16 +15,27 @@ const packageJsonPath = 'remarkConfig';
1615

1716
const config = ['package.json', '.remarkrc', '.remarkrc.json', '.remarkrc.{js,cjs,mjs}', '.remarkrc.{yml,yaml}'];
1817

19-
const resolveConfig: ResolveConfig<RemarkConfig> = config => {
18+
const resolveConfig: ResolveConfig<RemarkConfig> = (config, options) => {
2019
const plugins =
2120
config.plugins
2221
?.flatMap(plugin => {
2322
if (typeof plugin === 'string') return plugin;
2423
if (Array.isArray(plugin) && typeof plugin[0] === 'string') return plugin[0];
2524
return [];
2625
})
27-
.map(plugin => (isInternal(plugin) ? plugin : plugin.startsWith('remark-') ? plugin : `remark-${plugin}`)) ?? [];
28-
return plugins.map(id => toDeferResolve(id));
26+
.map(plugin =>
27+
// Resolve a remark plugin specifier from all possible dependency name variations.
28+
//
29+
// remark-cli configures `pluginPrefix: 'remark'`; unified-engine forwards this
30+
// to load-plugin, which prefers the prefixed name and falls back to the raw
31+
// identifier + supports scoped modules.
32+
//
33+
// https://github.com/remarkjs/remark/blob/334415d7552f2ffa359a23efc100345e7ed7a9f7/packages/remark-cli/cli.js#L36
34+
// https://github.com/unifiedjs/unified-engine/blob/6f35eaedc659e6edd5392f9ef0cf49bc51f3feab/lib/configuration.js#L423-L426
35+
// https://github.com/wooorm/load-plugin/blob/4a1b7231e20f64be52625ff3469bbf111dda5949/lib/index.js#L91-L101
36+
resolveLoadPluginStylePluginName('remark-', plugin, options.manifest)
37+
) ?? [];
38+
return plugins;
2939
};
3040

3141
const plugin: Plugin = {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import assert from 'node:assert/strict';
2+
import test from 'node:test';
3+
import { main } from '../../src/index.ts';
4+
import baseCounters from '../helpers/baseCounters.ts';
5+
import { createOptions } from '../helpers/create-options.ts';
6+
import { resolve } from '../helpers/resolve.ts';
7+
8+
const cwd = resolve('fixtures/plugins/remark-fallback-scoped');
9+
10+
test('Find dependencies with the Remark plugin (fallback scoped candidate)', async () => {
11+
const options = await createOptions({ cwd });
12+
const { issues, counters } = await main(options);
13+
14+
assert(issues.devDependencies['package.json']['remark-cli']);
15+
assert(issues.binaries['package.json']['remark']);
16+
assert.equal(issues.unlisted['package.json']?.['@scope/remark-pkg-b'], undefined);
17+
assert.equal(issues.unresolved['package.json']?.['@scope/remark-pkg-b'], undefined);
18+
assert.equal(issues.unlisted['package.json']?.['@scope/pkg-b'], undefined);
19+
assert.equal(issues.unresolved['package.json']?.['@scope/pkg-b'], undefined);
20+
21+
assert.deepEqual(counters, {
22+
...baseCounters,
23+
binaries: 1,
24+
devDependencies: 1,
25+
processed: 0,
26+
total: 0,
27+
});
28+
});

0 commit comments

Comments
 (0)