Skip to content

Commit 63a038c

Browse files
committed
fix(linter): only apply workspace:* to workspace packages in peerDepsVersionStrategy
1 parent 33c3174 commit 63a038c

File tree

3 files changed

+281
-13
lines changed

3 files changed

+281
-13
lines changed

astro-docs/src/content/docs/technologies/eslint/eslint-plugin/Guides/dependency-checks.mdoc

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,15 @@ export default [
155155

156156
## Options
157157

158-
| Property | Type | Default | Description |
159-
| ------------------------------------- | --------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
160-
| buildTargets | _Array<string>_ | _["build"]_ | List of build target names |
161-
| checkMissingDependencies | _boolean_ | _true_ | Disable to skip checking for missing dependencies |
162-
| checkObsoleteDependencies | _boolean_ | _true_ | Disable to skip checking for unused dependencies |
163-
| checkVersionMismatches | _boolean_ | _true_ | Disable to skip checking if version specifier matches installed version |
164-
| ignoredDependencies | _Array<string>_ | _[]_ | List of dependencies to ignore for checks |
165-
| ignoredFiles | _Array<string>_ | N/A | List of files to ignore when collecting dependencies. The default value will be set based on the selected executor during the generation. |
166-
| includeTransitiveDependencies | _boolean_ | _false_ | Enable to collect dependencies of children projects |
167-
| useLocalPathsForWorkspaceDependencies | _boolean_ | _false_ | Set workspace dependencies as relative file:// paths. Useful for monorepos that link via file:// in package.json files. |
168-
| peerDepsVersionStrategy | _"installed" \| "workspace"_ | _"installed"_ | Strategy for version specifiers in peer dependencies. Use `"installed"` to use the installed version, or `"workspace"` to use `workspace:*` protocol for monorepo packages. |
169-
| runtimeHelpers | _Array<string>_ | _[]_ | List of helper packages used by the built output (e.g. `tslib` when using `tsc` and `importHelpers` is set to `true`). The rule already detects some of them in some scenarios, but this option can be used to detect them when it doesn't happen automatically. |
158+
| Property | Type | Default | Description |
159+
| ------------------------------------- | ---------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
160+
| buildTargets | _Array<string>_ | _["build"]_ | List of build target names |
161+
| checkMissingDependencies | _boolean_ | _true_ | Disable to skip checking for missing dependencies |
162+
| checkObsoleteDependencies | _boolean_ | _true_ | Disable to skip checking for unused dependencies |
163+
| checkVersionMismatches | _boolean_ | _true_ | Disable to skip checking if version specifier matches installed version |
164+
| ignoredDependencies | _Array<string>_ | _[]_ | List of dependencies to ignore for checks |
165+
| ignoredFiles | _Array<string>_ | N/A | List of files to ignore when collecting dependencies. The default value will be set based on the selected executor during the generation. |
166+
| includeTransitiveDependencies | _boolean_ | _false_ | Enable to collect dependencies of children projects |
167+
| useLocalPathsForWorkspaceDependencies | _boolean_ | _false_ | Set workspace dependencies as relative file:// paths. Useful for monorepos that link via file:// in package.json files. |
168+
| peerDepsVersionStrategy | _"installed" \| "workspace"_ | _"installed"_ | Strategy for version specifiers in peer dependencies. Use `"installed"` to use the installed version, or `"workspace"` to use `workspace:*` protocol for monorepo packages. |
169+
| runtimeHelpers | _Array<string>_ | _[]_ | List of helper packages used by the built output (e.g. `tslib` when using `tsc` and `importHelpers` is set to `true`). The rule already detects some of them in some scenarios, but this option can be used to detect them when it doesn't happen automatically. |

packages/eslint-plugin/src/rules/dependency-checks.spec.ts

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2760,6 +2760,266 @@ describe('Dependency checks (eslint)', () => {
27602760
}"
27612761
`);
27622762
});
2763+
2764+
it('should only use workspace:* for workspace packages when peerDepsVersionStrategy is workspace', () => {
2765+
const packageJson = {
2766+
name: '@mycompany/liba',
2767+
peerDependencies: {},
2768+
};
2769+
2770+
const fileSys = {
2771+
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
2772+
'./libs/liba/src/index.ts': '',
2773+
'./libs/libb/package.json': JSON.stringify(
2774+
{ name: '@mycompany/libb' },
2775+
null,
2776+
2
2777+
),
2778+
'./package.json': JSON.stringify(rootPackageJson, null, 2),
2779+
};
2780+
vol.fromJSON(fileSys, '/root');
2781+
2782+
const failures = runRule(
2783+
{ peerDepsVersionStrategy: 'workspace' },
2784+
`/root/libs/liba/package.json`,
2785+
JSON.stringify(packageJson, null, 2),
2786+
{
2787+
nodes: {
2788+
liba: {
2789+
name: 'liba',
2790+
type: 'lib',
2791+
data: {
2792+
root: 'libs/liba',
2793+
targets: {
2794+
build: {},
2795+
},
2796+
},
2797+
},
2798+
libb: {
2799+
name: 'libb',
2800+
type: 'lib',
2801+
data: {
2802+
root: 'libs/libb',
2803+
targets: {
2804+
build: {},
2805+
},
2806+
},
2807+
},
2808+
},
2809+
externalNodes,
2810+
dependencies: {
2811+
liba: [
2812+
{ source: 'liba', target: 'libb', type: 'static' },
2813+
{ source: 'liba', target: 'npm:external1', type: 'static' },
2814+
],
2815+
},
2816+
},
2817+
{
2818+
liba: [
2819+
createFile(`libs/liba/src/main.ts`, ['libb', 'npm:external1']),
2820+
],
2821+
}
2822+
);
2823+
2824+
expect(failures.length).toEqual(1);
2825+
2826+
// Apply fix
2827+
const content = JSON.stringify(packageJson, null, 2);
2828+
const result =
2829+
content.slice(0, failures[0].fix.range[0]) +
2830+
failures[0].fix.text +
2831+
content.slice(failures[0].fix.range[1]);
2832+
2833+
// Workspace package (libb) should use workspace:*
2834+
// External package (external1) should use installed version
2835+
const resultObj = JSON.parse(result);
2836+
expect(resultObj.peerDependencies['@mycompany/libb']).toBe('workspace:*');
2837+
expect(resultObj.peerDependencies.external1).toBe('~16.1.2');
2838+
});
2839+
2840+
it('should not use workspace:* for external packages even when peerDepsVersionStrategy is workspace', () => {
2841+
const packageJson = {
2842+
name: '@mycompany/liba',
2843+
peerDependencies: {},
2844+
};
2845+
2846+
const fileSys = {
2847+
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
2848+
'./libs/liba/src/index.ts': '',
2849+
'./package.json': JSON.stringify(rootPackageJson, null, 2),
2850+
};
2851+
vol.fromJSON(fileSys, '/root');
2852+
2853+
const failures = runRule(
2854+
{ peerDepsVersionStrategy: 'workspace' },
2855+
`/root/libs/liba/package.json`,
2856+
JSON.stringify(packageJson, null, 2),
2857+
{
2858+
nodes: {
2859+
liba: {
2860+
name: 'liba',
2861+
type: 'lib',
2862+
data: {
2863+
root: 'libs/liba',
2864+
targets: {
2865+
build: {},
2866+
},
2867+
},
2868+
},
2869+
},
2870+
externalNodes,
2871+
dependencies: {
2872+
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
2873+
},
2874+
},
2875+
{
2876+
liba: [createFile(`libs/liba/src/main.ts`, ['npm:external1'])],
2877+
}
2878+
);
2879+
2880+
expect(failures.length).toEqual(1);
2881+
2882+
// Apply fix
2883+
const content = JSON.stringify(packageJson, null, 2);
2884+
const result =
2885+
content.slice(0, failures[0].fix.range[0]) +
2886+
failures[0].fix.text +
2887+
content.slice(failures[0].fix.range[1]);
2888+
2889+
// External package should NOT use workspace:*, should use installed version
2890+
expect(result).toMatchInlineSnapshot(`
2891+
"{
2892+
"name": "@mycompany/liba",
2893+
"peerDependencies": {
2894+
"external1": "~16.1.2"
2895+
}
2896+
}"
2897+
`);
2898+
});
2899+
2900+
it('should not report version mismatch for external packages when peerDepsVersionStrategy is workspace', () => {
2901+
const packageJson = {
2902+
name: '@mycompany/liba',
2903+
peerDependencies: {
2904+
external1: '~16.1.2',
2905+
},
2906+
};
2907+
2908+
const fileSys = {
2909+
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
2910+
'./libs/liba/src/index.ts': '',
2911+
'./package.json': JSON.stringify(rootPackageJson, null, 2),
2912+
};
2913+
vol.fromJSON(fileSys, '/root');
2914+
2915+
const failures = runRule(
2916+
{ peerDepsVersionStrategy: 'workspace' },
2917+
`/root/libs/liba/package.json`,
2918+
JSON.stringify(packageJson, null, 2),
2919+
{
2920+
nodes: {
2921+
liba: {
2922+
name: 'liba',
2923+
type: 'lib',
2924+
data: {
2925+
root: 'libs/liba',
2926+
targets: {
2927+
build: {},
2928+
},
2929+
},
2930+
},
2931+
},
2932+
externalNodes,
2933+
dependencies: {
2934+
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
2935+
},
2936+
},
2937+
{
2938+
liba: [createFile(`libs/liba/src/main.ts`, ['npm:external1'])],
2939+
}
2940+
);
2941+
2942+
// Should not report any error for external packages with correct version
2943+
expect(failures.length).toEqual(0);
2944+
});
2945+
2946+
it('should report version mismatch for workspace packages when peerDepsVersionStrategy is workspace', () => {
2947+
const packageJson = {
2948+
name: '@mycompany/liba',
2949+
peerDependencies: {
2950+
'@mycompany/libb': '^1.0.0',
2951+
},
2952+
};
2953+
2954+
const fileSys = {
2955+
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
2956+
'./libs/liba/src/index.ts': '',
2957+
'./libs/libb/package.json': JSON.stringify(
2958+
{ name: '@mycompany/libb' },
2959+
null,
2960+
2
2961+
),
2962+
'./package.json': JSON.stringify(rootPackageJson, null, 2),
2963+
};
2964+
vol.fromJSON(fileSys, '/root');
2965+
2966+
const failures = runRule(
2967+
{ peerDepsVersionStrategy: 'workspace' },
2968+
`/root/libs/liba/package.json`,
2969+
JSON.stringify(packageJson, null, 2),
2970+
{
2971+
nodes: {
2972+
liba: {
2973+
name: 'liba',
2974+
type: 'lib',
2975+
data: {
2976+
root: 'libs/liba',
2977+
targets: {
2978+
build: {},
2979+
},
2980+
},
2981+
},
2982+
libb: {
2983+
name: 'libb',
2984+
type: 'lib',
2985+
data: {
2986+
root: 'libs/libb',
2987+
targets: {
2988+
build: {},
2989+
},
2990+
},
2991+
},
2992+
},
2993+
externalNodes,
2994+
dependencies: {
2995+
liba: [{ source: 'liba', target: 'libb', type: 'static' }],
2996+
},
2997+
},
2998+
{
2999+
liba: [createFile(`libs/liba/src/main.ts`, ['libb'])],
3000+
}
3001+
);
3002+
3003+
expect(failures.length).toEqual(1);
3004+
expect(failures[0].message).toContain('workspace:*');
3005+
3006+
// Apply fix
3007+
const content = JSON.stringify(packageJson, null, 2);
3008+
const result =
3009+
content.slice(0, failures[0].fix.range[0]) +
3010+
failures[0].fix.text +
3011+
content.slice(failures[0].fix.range[1]);
3012+
3013+
// Workspace package should use workspace:*
3014+
expect(result).toMatchInlineSnapshot(`
3015+
"{
3016+
"name": "@mycompany/liba",
3017+
"peerDependencies": {
3018+
"@mycompany/libb": "workspace:*"
3019+
}
3020+
}"
3021+
`);
3022+
});
27633023
});
27643024
});
27653025

packages/eslint-plugin/src/rules/dependency-checks.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ export default ESLintUtils.RuleCreator(
173173

174174
const rootPackageJsonDeps = getAllDependencies(rootPackageJson);
175175

176+
function isWorkspacePackage(packageName: string): boolean {
177+
return Object.values(projectGraph.nodes).some(
178+
(node) => node.data.name === packageName
179+
);
180+
}
181+
176182
function getDependencySection(node: AST.JSONProperty): string | undefined {
177183
// Check if this node is a dependency section itself
178184
const directSection = (node.key as JSONLiteral)?.value as string;
@@ -214,7 +220,8 @@ export default ESLintUtils.RuleCreator(
214220
missingDeps.forEach((d) => {
215221
if (
216222
dependencySection === 'peerDependencies' &&
217-
peerDepsVersionStrategy === 'workspace'
223+
peerDepsVersionStrategy === 'workspace' &&
224+
isWorkspacePackage(d)
218225
) {
219226
projPackageJsonDeps[d] = WORKSPACE_VERSION_WILDCARD;
220227
} else {
@@ -288,6 +295,7 @@ export default ESLintUtils.RuleCreator(
288295
if (
289296
dependencySection === 'peerDependencies' &&
290297
peerDepsVersionStrategy === 'workspace' &&
298+
isWorkspacePackage(packageName) &&
291299
packageRange !== WORKSPACE_VERSION_WILDCARD
292300
) {
293301
context.report({

0 commit comments

Comments
 (0)