feat(linter): add runtimeHelpers option to @nx/dependency-checks rule (#29954)
This commit is contained in:
parent
9672949f82
commit
713b9fbaaf
@ -79,13 +79,14 @@ Sometimes we intentionally want to add or remove a dependency from our `package.
|
|||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
| Property | Type | Default | Description |
|
||||||
| ------------------------------------- | --------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------------- | --------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| buildTargets | _Array<string>_ | _["build"]_ | List of build target names |
|
| buildTargets | _Array<string>_ | _["build"]_ | List of build target names |
|
||||||
| checkMissingDependencies | _boolean_ | _true_ | Disable to skip checking for missing dependencies |
|
| checkMissingDependencies | _boolean_ | _true_ | Disable to skip checking for missing dependencies |
|
||||||
| checkObsoleteDependencies | _boolean_ | _true_ | Disable to skip checking for unused dependencies |
|
| checkObsoleteDependencies | _boolean_ | _true_ | Disable to skip checking for unused dependencies |
|
||||||
| checkVersionMismatches | _boolean_ | _true_ | Disable to skip checking if version specifier matches installed version |
|
| checkVersionMismatches | _boolean_ | _true_ | Disable to skip checking if version specifier matches installed version |
|
||||||
| ignoredDependencies | _Array<string>_ | _[]_ | List of dependencies to ignore for checks |
|
| ignoredDependencies | _Array<string>_ | _[]_ | List of dependencies to ignore for checks |
|
||||||
| 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. |
|
| 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. |
|
||||||
| includeTransitiveDependencies | _boolean_ | _false_ | Enable to collect dependencies of children projects |
|
| includeTransitiveDependencies | _boolean_ | _false_ | Enable to collect dependencies of children projects |
|
||||||
| useLocalPathsForWorkspaceDependencies | _boolean_ | _false_ | Set workspace dependencies as relative file:// paths. Useful for monorepos that link via file:// in package.json files. |
|
| useLocalPathsForWorkspaceDependencies | _boolean_ | _false_ | Set workspace dependencies as relative file:// paths. Useful for monorepos that link via file:// in package.json files. |
|
||||||
|
| 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. |
|
||||||
|
|||||||
@ -79,13 +79,14 @@ Sometimes we intentionally want to add or remove a dependency from our `package.
|
|||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
| Property | Type | Default | Description |
|
||||||
| ------------------------------------- | --------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------------- | --------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| buildTargets | _Array<string>_ | _["build"]_ | List of build target names |
|
| buildTargets | _Array<string>_ | _["build"]_ | List of build target names |
|
||||||
| checkMissingDependencies | _boolean_ | _true_ | Disable to skip checking for missing dependencies |
|
| checkMissingDependencies | _boolean_ | _true_ | Disable to skip checking for missing dependencies |
|
||||||
| checkObsoleteDependencies | _boolean_ | _true_ | Disable to skip checking for unused dependencies |
|
| checkObsoleteDependencies | _boolean_ | _true_ | Disable to skip checking for unused dependencies |
|
||||||
| checkVersionMismatches | _boolean_ | _true_ | Disable to skip checking if version specifier matches installed version |
|
| checkVersionMismatches | _boolean_ | _true_ | Disable to skip checking if version specifier matches installed version |
|
||||||
| ignoredDependencies | _Array<string>_ | _[]_ | List of dependencies to ignore for checks |
|
| ignoredDependencies | _Array<string>_ | _[]_ | List of dependencies to ignore for checks |
|
||||||
| 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. |
|
| 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. |
|
||||||
| includeTransitiveDependencies | _boolean_ | _false_ | Enable to collect dependencies of children projects |
|
| includeTransitiveDependencies | _boolean_ | _false_ | Enable to collect dependencies of children projects |
|
||||||
| useLocalPathsForWorkspaceDependencies | _boolean_ | _false_ | Set workspace dependencies as relative file:// paths. Useful for monorepos that link via file:// in package.json files. |
|
| useLocalPathsForWorkspaceDependencies | _boolean_ | _false_ | Set workspace dependencies as relative file:// paths. Useful for monorepos that link via file:// in package.json files. |
|
||||||
|
| 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. |
|
||||||
|
|||||||
@ -1699,6 +1699,113 @@ describe('Dependency checks (eslint)', () => {
|
|||||||
`);
|
`);
|
||||||
expect(failures[0].line).toEqual(3);
|
expect(failures[0].line).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should require packages in runtimeHelpers', () => {
|
||||||
|
const packageJson = {
|
||||||
|
name: '@mycompany/liba',
|
||||||
|
dependencies: { external1: '^16.0.0' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const swcrc = { jsc: { externalHelpers: true } };
|
||||||
|
|
||||||
|
const fileSys = {
|
||||||
|
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
||||||
|
'./libs/liba/src/index.ts': '',
|
||||||
|
'./libs/liba/.swcrc': JSON.stringify(swcrc, null, 2),
|
||||||
|
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
||||||
|
};
|
||||||
|
vol.fromJSON(fileSys, '/root');
|
||||||
|
|
||||||
|
const failures = runRule(
|
||||||
|
{ runtimeHelpers: ['@swc/helpers'] },
|
||||||
|
`/root/libs/liba/package.json`,
|
||||||
|
JSON.stringify(packageJson, null, 2),
|
||||||
|
{
|
||||||
|
nodes: {
|
||||||
|
liba: {
|
||||||
|
name: 'liba',
|
||||||
|
type: 'lib',
|
||||||
|
data: {
|
||||||
|
root: 'libs/liba',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
// custom executor that the rule wouldn't know about
|
||||||
|
executor: '@my-org/some-package:build',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
externalNodes,
|
||||||
|
dependencies: {
|
||||||
|
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
liba: [
|
||||||
|
createFile(`libs/liba/src/main.ts`, ['npm:external1']),
|
||||||
|
createFile(`libs/liba/package.json`, ['npm:external1']),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(failures.length).toEqual(1);
|
||||||
|
expect(failures[0].message).toMatchInlineSnapshot(`
|
||||||
|
"The "liba" project uses the following packages, but they are missing from "dependencies":
|
||||||
|
- @swc/helpers"
|
||||||
|
`);
|
||||||
|
expect(failures[0].line).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not report unused packages when specified in runtimeHelpers', () => {
|
||||||
|
const packageJson = {
|
||||||
|
name: '@mycompany/liba',
|
||||||
|
dependencies: { '@swc/helpers': '1.2.3', external1: '^16.0.0' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const swcrc = { jsc: { externalHelpers: true } };
|
||||||
|
|
||||||
|
const fileSys = {
|
||||||
|
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
||||||
|
'./libs/liba/src/index.ts': '',
|
||||||
|
'./libs/liba/.swcrc': JSON.stringify(swcrc, null, 2),
|
||||||
|
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
||||||
|
};
|
||||||
|
vol.fromJSON(fileSys, '/root');
|
||||||
|
|
||||||
|
const failures = runRule(
|
||||||
|
{ runtimeHelpers: ['@swc/helpers'] },
|
||||||
|
`/root/libs/liba/package.json`,
|
||||||
|
JSON.stringify(packageJson, null, 2),
|
||||||
|
{
|
||||||
|
nodes: {
|
||||||
|
liba: {
|
||||||
|
name: 'liba',
|
||||||
|
type: 'lib',
|
||||||
|
data: {
|
||||||
|
root: 'libs/liba',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
// custom executor that the rule wouldn't know about
|
||||||
|
executor: '@my-org/some-package:build',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
externalNodes,
|
||||||
|
dependencies: {
|
||||||
|
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
liba: [
|
||||||
|
createFile(`libs/liba/src/main.ts`, ['npm:external1']),
|
||||||
|
createFile(`libs/liba/package.json`, ['npm:external1']),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(failures.length).toEqual(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createFile(f: string, deps?: FileDataDependency[]): FileData {
|
function createFile(f: string, deps?: FileDataDependency[]): FileData {
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export type Options = [
|
|||||||
ignoredFiles?: string[];
|
ignoredFiles?: string[];
|
||||||
includeTransitiveDependencies?: boolean;
|
includeTransitiveDependencies?: boolean;
|
||||||
useLocalPathsForWorkspaceDependencies?: boolean;
|
useLocalPathsForWorkspaceDependencies?: boolean;
|
||||||
|
runtimeHelpers?: string[];
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -61,6 +62,7 @@ export default ESLintUtils.RuleCreator(
|
|||||||
checkVersionMismatches: { type: 'boolean' },
|
checkVersionMismatches: { type: 'boolean' },
|
||||||
includeTransitiveDependencies: { type: 'boolean' },
|
includeTransitiveDependencies: { type: 'boolean' },
|
||||||
useLocalPathsForWorkspaceDependencies: { type: 'boolean' },
|
useLocalPathsForWorkspaceDependencies: { type: 'boolean' },
|
||||||
|
runtimeHelpers: { type: 'array', items: { type: 'string' } },
|
||||||
},
|
},
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
@ -82,6 +84,7 @@ export default ESLintUtils.RuleCreator(
|
|||||||
ignoredFiles: [],
|
ignoredFiles: [],
|
||||||
includeTransitiveDependencies: false,
|
includeTransitiveDependencies: false,
|
||||||
useLocalPathsForWorkspaceDependencies: false,
|
useLocalPathsForWorkspaceDependencies: false,
|
||||||
|
runtimeHelpers: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
create(
|
create(
|
||||||
@ -96,6 +99,7 @@ export default ESLintUtils.RuleCreator(
|
|||||||
checkVersionMismatches,
|
checkVersionMismatches,
|
||||||
includeTransitiveDependencies,
|
includeTransitiveDependencies,
|
||||||
useLocalPathsForWorkspaceDependencies,
|
useLocalPathsForWorkspaceDependencies,
|
||||||
|
runtimeHelpers,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
) {
|
) {
|
||||||
@ -147,6 +151,7 @@ export default ESLintUtils.RuleCreator(
|
|||||||
includeTransitiveDependencies,
|
includeTransitiveDependencies,
|
||||||
ignoredFiles,
|
ignoredFiles,
|
||||||
useLocalPathsForWorkspaceDependencies,
|
useLocalPathsForWorkspaceDependencies,
|
||||||
|
runtimeHelpers,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const expectedDependencyNames = Object.keys(npmDependencies);
|
const expectedDependencyNames = Object.keys(npmDependencies);
|
||||||
|
|||||||
@ -374,6 +374,104 @@ describe('findNpmDependencies', () => {
|
|||||||
expect(results).toEqual({});
|
expect(results).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should pick up helper npm dependencies from runtimeHelpers', () => {
|
||||||
|
const libWithUnknownExecutor = {
|
||||||
|
name: 'my-lib',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/my-lib',
|
||||||
|
targets: {
|
||||||
|
build: { executor: '@my-org/some-package:build' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const projectGraph = {
|
||||||
|
nodes: {
|
||||||
|
'my-lib': libWithUnknownExecutor,
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:tslib': {
|
||||||
|
name: 'npm:tslib' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: { packageName: 'tslib', version: '2.6.0' },
|
||||||
|
},
|
||||||
|
'npm:@swc/helpers': {
|
||||||
|
name: 'npm:@swc/helpers' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: { packageName: '@swc/helpers', version: '0.5.0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
};
|
||||||
|
const projectFileMap = { 'my-lib': [] };
|
||||||
|
|
||||||
|
const results = findNpmDependencies(
|
||||||
|
'/root',
|
||||||
|
libWithUnknownExecutor,
|
||||||
|
projectGraph,
|
||||||
|
projectFileMap,
|
||||||
|
'build',
|
||||||
|
{ runtimeHelpers: ['tslib'] }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(results).toStrictEqual({ tslib: '2.6.0' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not duplicate helper npm dependencies when additionally set in runtimeHelpers', () => {
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'./nx.json': JSON.stringify(nxJson),
|
||||||
|
'./libs/my-lib/tsconfig.json': JSON.stringify({
|
||||||
|
compilerOptions: { importHelpers: true },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
'/root'
|
||||||
|
);
|
||||||
|
const libWithUnknownExecutor = {
|
||||||
|
name: 'my-lib',
|
||||||
|
type: 'lib' as const,
|
||||||
|
data: {
|
||||||
|
root: 'libs/my-lib',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: '@nx/js:tsc',
|
||||||
|
options: { tsConfig: 'libs/my-lib/tsconfig.json' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const projectGraph = {
|
||||||
|
nodes: {
|
||||||
|
'my-lib': libWithUnknownExecutor,
|
||||||
|
},
|
||||||
|
externalNodes: {
|
||||||
|
'npm:tslib': {
|
||||||
|
name: 'npm:tslib' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: { packageName: 'tslib', version: '2.6.0' },
|
||||||
|
},
|
||||||
|
'npm:@swc/helpers': {
|
||||||
|
name: 'npm:@swc/helpers' as const,
|
||||||
|
type: 'npm' as const,
|
||||||
|
data: { packageName: '@swc/helpers', version: '0.5.0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
};
|
||||||
|
const projectFileMap = { 'my-lib': [] };
|
||||||
|
|
||||||
|
const results = findNpmDependencies(
|
||||||
|
'/root',
|
||||||
|
libWithUnknownExecutor,
|
||||||
|
projectGraph,
|
||||||
|
projectFileMap,
|
||||||
|
'build',
|
||||||
|
{ runtimeHelpers: ['tslib'] }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(results).toStrictEqual({ tslib: '2.6.0' });
|
||||||
|
});
|
||||||
|
|
||||||
it('should support recursive collection of dependencies', () => {
|
it('should support recursive collection of dependencies', () => {
|
||||||
vol.fromJSON(
|
vol.fromJSON(
|
||||||
{
|
{
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export function findNpmDependencies(
|
|||||||
includeTransitiveDependencies?: boolean;
|
includeTransitiveDependencies?: boolean;
|
||||||
ignoredFiles?: string[];
|
ignoredFiles?: string[];
|
||||||
useLocalPathsForWorkspaceDependencies?: boolean;
|
useLocalPathsForWorkspaceDependencies?: boolean;
|
||||||
|
runtimeHelpers?: string[];
|
||||||
} = {}
|
} = {}
|
||||||
): Record<string, string> {
|
): Record<string, string> {
|
||||||
let seen: null | Set<string> = null;
|
let seen: null | Set<string> = null;
|
||||||
@ -61,6 +62,7 @@ export function findNpmDependencies(
|
|||||||
currentProject,
|
currentProject,
|
||||||
projectGraph,
|
projectGraph,
|
||||||
buildTarget,
|
buildTarget,
|
||||||
|
options.runtimeHelpers,
|
||||||
collectedDeps
|
collectedDeps
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -194,8 +196,23 @@ function collectHelperDependencies(
|
|||||||
sourceProject: ProjectGraphProjectNode,
|
sourceProject: ProjectGraphProjectNode,
|
||||||
projectGraph: ProjectGraph,
|
projectGraph: ProjectGraph,
|
||||||
buildTarget: string,
|
buildTarget: string,
|
||||||
|
runtimeHelpers: string[] | undefined,
|
||||||
npmDeps: Record<string, string>
|
npmDeps: Record<string, string>
|
||||||
): void {
|
): void {
|
||||||
|
if (runtimeHelpers?.length > 0) {
|
||||||
|
for (const helper of runtimeHelpers) {
|
||||||
|
if (
|
||||||
|
!npmDeps[helper] &&
|
||||||
|
projectGraph.externalNodes[`npm:${helper}`]?.type === 'npm'
|
||||||
|
) {
|
||||||
|
npmDeps[helper] =
|
||||||
|
projectGraph.externalNodes[`npm:${helper}`].data.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const target = sourceProject.data.targets[buildTarget];
|
const target = sourceProject.data.targets[buildTarget];
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user