feat(linter): add rule for dependency checking (#17581)
This commit is contained in:
parent
1891addc09
commit
ef8c4ed095
@ -30,8 +30,12 @@
|
|||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["**/executors/**/schema.json", "**/generators/**/schema.json"],
|
"files": ["*.json"],
|
||||||
"parser": "jsonc-eslint-parser",
|
"parser": "jsonc-eslint-parser",
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["**/executors/**/schema.json", "**/generators/**/schema.json"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@nx/workspace/valid-schema-description": "error"
|
"@nx/workspace/valid-schema-description": "error"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
runCLI,
|
runCLI,
|
||||||
uniq,
|
uniq,
|
||||||
updateFile,
|
updateFile,
|
||||||
|
updateJson,
|
||||||
} from '@nx/e2e/utils';
|
} from '@nx/e2e/utils';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
@ -435,6 +436,92 @@ describe('Linter', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('dependency checks', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
updateJson(`libs/${mylib}/.eslintrc.json`, (json) => {
|
||||||
|
json.overrides = [
|
||||||
|
...json.overrides,
|
||||||
|
{
|
||||||
|
files: ['*.json'],
|
||||||
|
parser: 'jsonc-eslint-parser',
|
||||||
|
rules: {
|
||||||
|
'@nx/dependency-checks': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
updateJson(`libs/${mylib}/project.json`, (json) => {
|
||||||
|
json.targets.lint.options.lintFilePatterns = [
|
||||||
|
`libs/${mylib}/**/*.ts`,
|
||||||
|
`libs/${mylib}/project.json`,
|
||||||
|
`libs/${mylib}/package.json`,
|
||||||
|
];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should report dependency check issues', () => {
|
||||||
|
const rootPackageJson = readJson('package.json');
|
||||||
|
const nxVersion = rootPackageJson.devDependencies.nx;
|
||||||
|
const swcCoreVersion = rootPackageJson.devDependencies['@swc/core'];
|
||||||
|
const swcHelpersVersion = rootPackageJson.dependencies['@swc/helpers'];
|
||||||
|
|
||||||
|
let out = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||||
|
expect(out).toContain('All files pass linting');
|
||||||
|
|
||||||
|
// make an explict dependency to nx
|
||||||
|
updateFile(
|
||||||
|
`libs/${mylib}/src/lib/${mylib}.ts`,
|
||||||
|
(content) =>
|
||||||
|
`import { names } from '@nx/devkit';\n\n` +
|
||||||
|
content.replace(/return .*;/, `return names(${mylib}).className;`)
|
||||||
|
);
|
||||||
|
|
||||||
|
// output should now report missing dependencies section
|
||||||
|
out = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||||
|
expect(out).toContain(
|
||||||
|
'Dependency sections are missing from the "package.json"'
|
||||||
|
);
|
||||||
|
|
||||||
|
// should fix the missing section issue
|
||||||
|
out = runCLI(`lint ${mylib} --fix`, { silenceError: true });
|
||||||
|
expect(out).toContain(
|
||||||
|
`Successfully ran target lint for project ${mylib}`
|
||||||
|
);
|
||||||
|
const packageJson = readJson(`libs/${mylib}/package.json`);
|
||||||
|
expect(packageJson).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@nx/devkit": "${nxVersion}",
|
||||||
|
"@swc/core": "${swcCoreVersion}",
|
||||||
|
"@swc/helpers": "${swcHelpersVersion}",
|
||||||
|
"nx": "${nxVersion}",
|
||||||
|
},
|
||||||
|
"name": "@proj/${mylib}",
|
||||||
|
"type": "commonjs",
|
||||||
|
"version": "0.0.1",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
// intentionally set the invalid version
|
||||||
|
updateJson(`libs/${mylib}/package.json`, (json) => {
|
||||||
|
json.dependencies['@nx/devkit'] = '100.0.0';
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
out = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||||
|
expect(out).toContain(
|
||||||
|
`The version specifier does not contain the installed version of "@nx/devkit" package: ${nxVersion}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// should fix the version mismatch issue
|
||||||
|
out = runCLI(`lint ${mylib} --fix`, { silenceError: true });
|
||||||
|
expect(out).toContain(
|
||||||
|
`Successfully ran target lint for project ${mylib}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Root projects migration', () => {
|
describe('Root projects migration', () => {
|
||||||
|
|||||||
1
nx.json
1
nx.json
@ -105,6 +105,7 @@
|
|||||||
"{projectRoot}/generators.json",
|
"{projectRoot}/generators.json",
|
||||||
"{projectRoot}/executors.json",
|
"{projectRoot}/executors.json",
|
||||||
"{projectRoot}/package.json",
|
"{projectRoot}/package.json",
|
||||||
|
"{projectRoot}/project.json",
|
||||||
"{projectRoot}/migrations.json"
|
"{projectRoot}/migrations.json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
"@typescript-eslint/utils": "^5.58.0",
|
"@typescript-eslint/utils": "^5.58.0",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"confusing-browser-globals": "^1.0.9",
|
"confusing-browser-globals": "^1.0.9",
|
||||||
|
"jsonc-eslint-parser": "^2.1.0",
|
||||||
"semver": "7.3.4"
|
"semver": "7.3.4"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@ -15,6 +15,10 @@ import nxPluginChecksRule, {
|
|||||||
RULE_NAME as nxPluginChecksRuleName,
|
RULE_NAME as nxPluginChecksRuleName,
|
||||||
} from './rules/nx-plugin-checks';
|
} from './rules/nx-plugin-checks';
|
||||||
|
|
||||||
|
import dependencyChecks, {
|
||||||
|
RULE_NAME as dependencyChecksRuleName,
|
||||||
|
} from './rules/dependency-checks';
|
||||||
|
|
||||||
// Resolve any custom rules that might exist in the current workspace
|
// Resolve any custom rules that might exist in the current workspace
|
||||||
import { workspaceRules } from './resolve-workspace-rules';
|
import { workspaceRules } from './resolve-workspace-rules';
|
||||||
|
|
||||||
@ -32,6 +36,7 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
[enforceModuleBoundariesRuleName]: enforceModuleBoundaries,
|
[enforceModuleBoundariesRuleName]: enforceModuleBoundaries,
|
||||||
[nxPluginChecksRuleName]: nxPluginChecksRule,
|
[nxPluginChecksRuleName]: nxPluginChecksRule,
|
||||||
|
[dependencyChecksRuleName]: dependencyChecks,
|
||||||
...workspaceRules,
|
...workspaceRules,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
1458
packages/eslint-plugin/src/rules/dependency-checks.spec.ts
Normal file
1458
packages/eslint-plugin/src/rules/dependency-checks.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
364
packages/eslint-plugin/src/rules/dependency-checks.ts
Normal file
364
packages/eslint-plugin/src/rules/dependency-checks.ts
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
import { AST } from 'jsonc-eslint-parser';
|
||||||
|
import { normalizePath, workspaceRoot } from '@nx/devkit';
|
||||||
|
import { createESLintRule } from '../utils/create-eslint-rule';
|
||||||
|
import { readProjectGraph } from '../utils/project-graph-utils';
|
||||||
|
import { findProject, getSourceFilePath } from '../utils/runtime-lint-utils';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { findProjectsNpmDependencies } from '@nx/js/src/internal';
|
||||||
|
import { satisfies } from 'semver';
|
||||||
|
import { getHelperDependenciesFromProjectGraph } from '@nx/js';
|
||||||
|
import {
|
||||||
|
getAllDependencies,
|
||||||
|
removePackageJsonFromFileMap,
|
||||||
|
} from '../utils/package-json-utils';
|
||||||
|
|
||||||
|
export type Options = [
|
||||||
|
{
|
||||||
|
buildTargets?: string[];
|
||||||
|
checkMissingDependencies?: boolean;
|
||||||
|
checkObsoleteDependencies?: boolean;
|
||||||
|
checkVersionMismatches?: boolean;
|
||||||
|
checkMissingPackageJson?: boolean;
|
||||||
|
ignoredDependencies?: string[];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export type MessageIds =
|
||||||
|
| 'missingDependency'
|
||||||
|
| 'obsoleteDependency'
|
||||||
|
| 'versionMismatch'
|
||||||
|
| 'missingDependencySection';
|
||||||
|
|
||||||
|
export const RULE_NAME = 'dependency-checks';
|
||||||
|
|
||||||
|
export default createESLintRule<Options, MessageIds>({
|
||||||
|
name: RULE_NAME,
|
||||||
|
meta: {
|
||||||
|
type: 'suggestion',
|
||||||
|
docs: {
|
||||||
|
description: `Checks dependencies in project's package.json for version mismatches`,
|
||||||
|
recommended: 'error',
|
||||||
|
},
|
||||||
|
fixable: 'code',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
buildTargets: [{ type: 'string' }],
|
||||||
|
ignoreDependencies: [{ type: 'string' }],
|
||||||
|
checkMissingDependencies: { type: 'boolean' },
|
||||||
|
checkObsoleteDependencies: { type: 'boolean' },
|
||||||
|
checkVersionMismatches: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
messages: {
|
||||||
|
missingDependency: `The "{{projectName}}" uses the package "{{packageName}}", but it is missing from the project's "package.json".`,
|
||||||
|
obsoleteDependency: `The "{{packageName}}" package is not used by "{{projectName}}".`,
|
||||||
|
versionMismatch: `The version specifier does not contain the installed version of "{{packageName}}" package: {{version}}.`,
|
||||||
|
missingDependencySection: `Dependency sections are missing from the "package.json" but following dependencies were detected:{{dependencies}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultOptions: [
|
||||||
|
{
|
||||||
|
buildTargets: ['build'],
|
||||||
|
checkMissingDependencies: true,
|
||||||
|
checkObsoleteDependencies: true,
|
||||||
|
checkVersionMismatches: true,
|
||||||
|
ignoredDependencies: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
create(
|
||||||
|
context,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
buildTargets,
|
||||||
|
ignoredDependencies,
|
||||||
|
checkMissingDependencies,
|
||||||
|
checkObsoleteDependencies,
|
||||||
|
checkVersionMismatches,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
if (!(context.parserServices as any).isJSON) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const fileName = normalizePath(context.getFilename());
|
||||||
|
// support only package.json
|
||||||
|
if (!fileName.endsWith('/package.json')) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectPath = normalizePath(globalThis.projectPath || workspaceRoot);
|
||||||
|
const sourceFilePath = getSourceFilePath(fileName, projectPath);
|
||||||
|
const { projectGraph, projectRootMappings, projectFileMap } =
|
||||||
|
readProjectGraph(RULE_NAME);
|
||||||
|
|
||||||
|
if (!projectGraph) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceProject = findProject(
|
||||||
|
projectGraph,
|
||||||
|
projectRootMappings,
|
||||||
|
sourceFilePath
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if source project exists
|
||||||
|
if (!sourceProject) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if library has a build target
|
||||||
|
const buildTarget = buildTargets.find(
|
||||||
|
(t) => sourceProject.data.targets?.[t]
|
||||||
|
);
|
||||||
|
if (!buildTarget) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// gather helper dependencies for @nx/js executors
|
||||||
|
const helperDependencies = getHelperDependenciesFromProjectGraph(
|
||||||
|
workspaceRoot,
|
||||||
|
sourceProject.name,
|
||||||
|
projectGraph
|
||||||
|
);
|
||||||
|
|
||||||
|
// find all dependencies for the project
|
||||||
|
const npmDeps = findProjectsNpmDependencies(
|
||||||
|
sourceProject,
|
||||||
|
projectGraph,
|
||||||
|
buildTarget,
|
||||||
|
{
|
||||||
|
helperDependencies: helperDependencies.map((dep) => dep.target),
|
||||||
|
},
|
||||||
|
removePackageJsonFromFileMap(projectFileMap)
|
||||||
|
);
|
||||||
|
const projDependencies = {
|
||||||
|
...npmDeps.dependencies,
|
||||||
|
...npmDeps.peerDependencies,
|
||||||
|
};
|
||||||
|
const expectedDependencyNames = Object.keys(projDependencies);
|
||||||
|
|
||||||
|
const projPackageJsonPath = join(
|
||||||
|
workspaceRoot,
|
||||||
|
sourceProject.data.root,
|
||||||
|
'package.json'
|
||||||
|
);
|
||||||
|
|
||||||
|
globalThis.projPackageJsonDeps ??= getAllDependencies(projPackageJsonPath);
|
||||||
|
const projPackageJsonDeps: Record<string, string> =
|
||||||
|
globalThis.projPackageJsonDeps;
|
||||||
|
const rootPackageJsonDeps = getAllDependencies(
|
||||||
|
join(workspaceRoot, 'package.json')
|
||||||
|
);
|
||||||
|
|
||||||
|
function validateMissingDependencies(node: AST.JSONProperty) {
|
||||||
|
if (!checkMissingDependencies) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const missingDeps = expectedDependencyNames.filter(
|
||||||
|
(d) => !projPackageJsonDeps[d]
|
||||||
|
);
|
||||||
|
|
||||||
|
missingDeps.forEach((d) => {
|
||||||
|
if (!ignoredDependencies.includes(d)) {
|
||||||
|
context.report({
|
||||||
|
node: node as any,
|
||||||
|
messageId: 'missingDependency',
|
||||||
|
data: { packageName: d, projectName: sourceProject.name },
|
||||||
|
fix(fixer) {
|
||||||
|
projPackageJsonDeps[d] =
|
||||||
|
rootPackageJsonDeps[d] || projDependencies[d];
|
||||||
|
|
||||||
|
const deps = (node.value as AST.JSONObjectExpression).properties;
|
||||||
|
if (deps.length) {
|
||||||
|
return fixer.insertTextAfter(
|
||||||
|
deps[deps.length - 1] as any,
|
||||||
|
`,\n "${d}": "${projPackageJsonDeps[d]}"`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return fixer.replaceTextRange(
|
||||||
|
[node.value.range[0] + 1, node.value.range[1] - 1],
|
||||||
|
`\n "${d}": "${projPackageJsonDeps[d]}"\n `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateVersionMatchesInstalled(
|
||||||
|
node: AST.JSONProperty,
|
||||||
|
packageName: string,
|
||||||
|
packageRange: string
|
||||||
|
) {
|
||||||
|
if (!checkVersionMismatches) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
projDependencies[packageName] === '*' ||
|
||||||
|
satisfies(projDependencies[packageName], packageRange)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.report({
|
||||||
|
node: node as any,
|
||||||
|
messageId: 'versionMismatch',
|
||||||
|
data: {
|
||||||
|
packageName: packageName,
|
||||||
|
version: projDependencies[packageName],
|
||||||
|
},
|
||||||
|
fix: (fixer) =>
|
||||||
|
fixer.replaceText(
|
||||||
|
node as any,
|
||||||
|
`"${packageName}": "${
|
||||||
|
rootPackageJsonDeps[packageName] || projDependencies[packageName]
|
||||||
|
}"`
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportObsoleteDependency(
|
||||||
|
node: AST.JSONProperty,
|
||||||
|
packageName: string
|
||||||
|
) {
|
||||||
|
if (!checkObsoleteDependencies) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.report({
|
||||||
|
node: node as any,
|
||||||
|
messageId: 'obsoleteDependency',
|
||||||
|
data: { packageName: packageName, projectName: sourceProject.name },
|
||||||
|
fix: (fixer) => {
|
||||||
|
const isLastProperty =
|
||||||
|
node.parent.properties[node.parent.properties.length - 1] === node;
|
||||||
|
const index = node.parent.properties.findIndex((n) => n === node);
|
||||||
|
|
||||||
|
if (index > 0) {
|
||||||
|
const previousNode = node.parent.properties[index - 1];
|
||||||
|
return fixer.removeRange([
|
||||||
|
previousNode.range[1] + 1,
|
||||||
|
node.range[1] + (isLastProperty ? 0 : 1),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
const parent = node.parent;
|
||||||
|
|
||||||
|
// it's the only property
|
||||||
|
if (isLastProperty) {
|
||||||
|
return fixer.removeRange([
|
||||||
|
parent.range[0] + 1,
|
||||||
|
parent.range[1] - 1,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return fixer.removeRange([
|
||||||
|
parent.range[0] + 1,
|
||||||
|
node.range[1] + 1,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove 4 spaces, new line and potential comma from previous line
|
||||||
|
const shouldRemoveSiblingComma =
|
||||||
|
isLastProperty && node.parent.properties.length > 1;
|
||||||
|
const leadingChars = 5 + (shouldRemoveSiblingComma ? 1 : 0);
|
||||||
|
return fixer.removeRange([
|
||||||
|
node.range[0] - leadingChars,
|
||||||
|
node.range[1] + (isLastProperty ? 0 : 1),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDependenciesSectionExistance(
|
||||||
|
node: AST.JSONObjectExpression
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!expectedDependencyNames.length ||
|
||||||
|
!expectedDependencyNames.some((d) => !ignoredDependencies.includes(d))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!node.properties ||
|
||||||
|
!node.properties.some((p) =>
|
||||||
|
[
|
||||||
|
'dependencies',
|
||||||
|
'peerDependencies',
|
||||||
|
'devDependencies',
|
||||||
|
'optionalDependencies',
|
||||||
|
].includes((p.key as any).value)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
context.report({
|
||||||
|
node: node as any,
|
||||||
|
messageId: 'missingDependencySection',
|
||||||
|
data: {
|
||||||
|
dependencies: expectedDependencyNames
|
||||||
|
.map((d) => `\n- "${d}"`)
|
||||||
|
.join(),
|
||||||
|
},
|
||||||
|
fix: (fixer) => {
|
||||||
|
expectedDependencyNames.sort().reduce((acc, d) => {
|
||||||
|
acc[d] = rootPackageJsonDeps[d] || projDependencies[d];
|
||||||
|
return acc;
|
||||||
|
}, projPackageJsonDeps);
|
||||||
|
|
||||||
|
const dependencies = Object.keys(projPackageJsonDeps)
|
||||||
|
.map((d) => `\n "${d}": "${projPackageJsonDeps[d]}"`)
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
if (!node.properties.length) {
|
||||||
|
return fixer.replaceText(
|
||||||
|
node as any,
|
||||||
|
`{\n "dependencies": {${dependencies}\n }\n}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return fixer.insertTextAfter(
|
||||||
|
node.properties[node.properties.length - 1] as any,
|
||||||
|
`,\n "dependencies": {${dependencies}\n }`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i]'](
|
||||||
|
node: AST.JSONProperty
|
||||||
|
) {
|
||||||
|
return validateMissingDependencies(node);
|
||||||
|
},
|
||||||
|
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i] > JSONObjectExpression > JSONProperty'](
|
||||||
|
node: AST.JSONProperty
|
||||||
|
) {
|
||||||
|
const packageName = (node.key as any).value;
|
||||||
|
const packageRange = (node.value as any).value;
|
||||||
|
|
||||||
|
if (ignoredDependencies.includes(packageName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expectedDependencyNames.includes(packageName)) {
|
||||||
|
return validateVersionMatchesInstalled(
|
||||||
|
node,
|
||||||
|
packageName,
|
||||||
|
packageRange
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return reportObsoleteDependency(node, packageName);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
['JSONExpressionStatement > JSONObjectExpression'](
|
||||||
|
node: AST.JSONObjectExpression
|
||||||
|
) {
|
||||||
|
return validateDependenciesSectionExistance(node);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,11 +1,6 @@
|
|||||||
import 'nx/src/utils/testing/mock-fs';
|
import 'nx/src/utils/testing/mock-fs';
|
||||||
|
|
||||||
import type {
|
import type { FileData, ProjectFileMap, ProjectGraph } from '@nx/devkit';
|
||||||
FileData,
|
|
||||||
ProjectFileMap,
|
|
||||||
ProjectGraph,
|
|
||||||
ProjectGraphDependency,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import { DependencyType } from '@nx/devkit';
|
import { DependencyType } from '@nx/devkit';
|
||||||
import * as parser from '@typescript-eslint/parser';
|
import * as parser from '@typescript-eslint/parser';
|
||||||
import { TSESLint } from '@typescript-eslint/utils';
|
import { TSESLint } from '@typescript-eslint/utils';
|
||||||
|
|||||||
26
packages/eslint-plugin/src/utils/package-json-utils.ts
Normal file
26
packages/eslint-plugin/src/utils/package-json-utils.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { ProjectFileMap, readJsonFile } from '@nx/devkit';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
|
export function getAllDependencies(path: string): Record<string, string> {
|
||||||
|
if (existsSync(path)) {
|
||||||
|
const packageJson = readJsonFile(path);
|
||||||
|
return {
|
||||||
|
...packageJson.dependencies,
|
||||||
|
...packageJson.devDependencies,
|
||||||
|
...packageJson.peerDependencies,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removePackageJsonFromFileMap(
|
||||||
|
projectFileMap: ProjectFileMap
|
||||||
|
): ProjectFileMap {
|
||||||
|
const newFileMap = {};
|
||||||
|
Object.keys(projectFileMap).forEach((key) => {
|
||||||
|
newFileMap[key] = projectFileMap[key].filter(
|
||||||
|
(f) => !f.file.endsWith('/package.json')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return newFileMap;
|
||||||
|
}
|
||||||
@ -6,3 +6,5 @@ export {
|
|||||||
} from 'nx/src/plugins/js/utils/register';
|
} from 'nx/src/plugins/js/utils/register';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
export { TargetProjectLocator } from 'nx/src/plugins/js/project-graph/build-dependencies/target-project-locator';
|
export { TargetProjectLocator } from 'nx/src/plugins/js/project-graph/build-dependencies/target-project-locator';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
|
export { findProjectsNpmDependencies } from 'nx/src/plugins/js/package-json/create-package-json';
|
||||||
|
|||||||
@ -123,8 +123,7 @@ export function getHelperDependenciesFromProjectGraph(
|
|||||||
// we check if a dependency is part of the workspace and if it's a library
|
// we check if a dependency is part of the workspace and if it's a library
|
||||||
// because we wouldn't want to include external dependencies (npm packages)
|
// because we wouldn't want to include external dependencies (npm packages)
|
||||||
if (
|
if (
|
||||||
!dependency.target.startsWith('npm:') &&
|
projectGraph.nodes[dependency.target] &&
|
||||||
!!projectGraph.nodes[dependency.target] &&
|
|
||||||
projectGraph.nodes[dependency.target].type === 'lib'
|
projectGraph.nodes[dependency.target].type === 'lib'
|
||||||
) {
|
) {
|
||||||
const targetData = projectGraph.nodes[dependency.target].data;
|
const targetData = projectGraph.nodes[dependency.target].data;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ESLint, Linter as LinterType } from 'eslint';
|
import { Linter as LinterType } from 'eslint';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This configuration is intended to apply to all TypeScript source files.
|
* This configuration is intended to apply to all TypeScript source files.
|
||||||
@ -28,6 +28,20 @@ export const globalJavaScriptOverrides = {
|
|||||||
rules: {},
|
rules: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to apply to all JSON source files.
|
||||||
|
* See the eslint-plugin package for what is in the referenced shareable config.
|
||||||
|
*/
|
||||||
|
export const globalJsonOverrides = {
|
||||||
|
files: ['*.json'],
|
||||||
|
parser: 'jsonc-eslint-parser',
|
||||||
|
/**
|
||||||
|
* Having an empty rules object present makes it more obvious to the user where they would
|
||||||
|
* extend things from if they needed to
|
||||||
|
*/
|
||||||
|
rules: {},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This configuration is intended to apply to all "source code" (but not
|
* This configuration is intended to apply to all "source code" (but not
|
||||||
* markup like HTML, or other custom file types like GraphQL)
|
* markup like HTML, or other custom file types like GraphQL)
|
||||||
|
|||||||
@ -40,40 +40,15 @@ export function createPackageJson(
|
|||||||
} = {},
|
} = {},
|
||||||
fileMap: ProjectFileMap = null
|
fileMap: ProjectFileMap = null
|
||||||
): PackageJson {
|
): PackageJson {
|
||||||
if (fileMap == null) {
|
|
||||||
fileMap = readProjectFileMapCache()?.projectFileMap || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectNode = graph.nodes[projectName];
|
const projectNode = graph.nodes[projectName];
|
||||||
const isLibrary = projectNode.type === 'lib';
|
const isLibrary = projectNode.type === 'lib';
|
||||||
|
|
||||||
const { selfInputs, dependencyInputs } = options.target
|
const npmDeps = findProjectsNpmDependencies(
|
||||||
? getTargetInputs(readNxJson(), projectNode, options.target)
|
|
||||||
: { selfInputs: [], dependencyInputs: [] };
|
|
||||||
|
|
||||||
const npmDeps: NpmDeps = {
|
|
||||||
dependencies: {},
|
|
||||||
peerDependencies: {},
|
|
||||||
peerDependenciesMeta: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const seen = new Set<string>();
|
|
||||||
|
|
||||||
options.helperDependencies?.forEach((dep) => {
|
|
||||||
seen.add(dep);
|
|
||||||
npmDeps.dependencies[graph.externalNodes[dep].data.packageName] =
|
|
||||||
graph.externalNodes[dep].data.version;
|
|
||||||
recursivelyCollectPeerDependencies(dep, graph, npmDeps, seen);
|
|
||||||
});
|
|
||||||
|
|
||||||
findAllNpmDeps(
|
|
||||||
fileMap,
|
|
||||||
projectNode,
|
projectNode,
|
||||||
graph,
|
graph,
|
||||||
npmDeps,
|
options.target,
|
||||||
seen,
|
{ helperDependencies: options.helperDependencies },
|
||||||
dependencyInputs,
|
fileMap
|
||||||
selfInputs
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// default package.json if one does not exist
|
// default package.json if one does not exist
|
||||||
@ -200,6 +175,51 @@ export function createPackageJson(
|
|||||||
return packageJson;
|
return packageJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findProjectsNpmDependencies(
|
||||||
|
projectNode: ProjectGraphProjectNode,
|
||||||
|
graph: ProjectGraph,
|
||||||
|
target: string,
|
||||||
|
options: {
|
||||||
|
helperDependencies?: string[];
|
||||||
|
},
|
||||||
|
fileMap?: ProjectFileMap
|
||||||
|
): NpmDeps {
|
||||||
|
if (fileMap == null) {
|
||||||
|
fileMap = readProjectFileMapCache()?.projectFileMap || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { selfInputs, dependencyInputs } = target
|
||||||
|
? getTargetInputs(readNxJson(), projectNode, target)
|
||||||
|
: { selfInputs: [], dependencyInputs: [] };
|
||||||
|
|
||||||
|
const npmDeps: NpmDeps = {
|
||||||
|
dependencies: {},
|
||||||
|
peerDependencies: {},
|
||||||
|
peerDependenciesMeta: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const seen = new Set<string>();
|
||||||
|
|
||||||
|
options.helperDependencies?.forEach((dep) => {
|
||||||
|
seen.add(dep);
|
||||||
|
npmDeps.dependencies[graph.externalNodes[dep].data.packageName] =
|
||||||
|
graph.externalNodes[dep].data.version;
|
||||||
|
recursivelyCollectPeerDependencies(dep, graph, npmDeps, seen);
|
||||||
|
});
|
||||||
|
|
||||||
|
findAllNpmDeps(
|
||||||
|
fileMap,
|
||||||
|
projectNode,
|
||||||
|
graph,
|
||||||
|
npmDeps,
|
||||||
|
seen,
|
||||||
|
dependencyInputs,
|
||||||
|
selfInputs
|
||||||
|
);
|
||||||
|
|
||||||
|
return npmDeps;
|
||||||
|
}
|
||||||
|
|
||||||
function findAllNpmDeps(
|
function findAllNpmDeps(
|
||||||
projectFileMap: ProjectFileMap,
|
projectFileMap: ProjectFileMap,
|
||||||
projectNode: ProjectGraphProjectNode,
|
projectNode: ProjectGraphProjectNode,
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
addDependenciesToPackageJson,
|
|
||||||
formatFiles,
|
formatFiles,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
logger,
|
logger,
|
||||||
@ -16,9 +15,8 @@ import {
|
|||||||
|
|
||||||
import type { Linter as ESLint } from 'eslint';
|
import type { Linter as ESLint } from 'eslint';
|
||||||
|
|
||||||
import { Schema as EsLintExecutorOptions } from '@nx/linter/src/executors/eslint/schema';
|
import type { Schema as EsLintExecutorOptions } from '@nx/linter/src/executors/eslint/schema';
|
||||||
|
|
||||||
import { jsoncEslintParserVersion } from '../../utils/versions';
|
|
||||||
import { PluginLintChecksGeneratorSchema } from './schema';
|
import { PluginLintChecksGeneratorSchema } from './schema';
|
||||||
import { NX_PREFIX } from 'nx/src/utils/logger';
|
import { NX_PREFIX } from 'nx/src/utils/logger';
|
||||||
import { PackageJson, readNxMigrateConfig } from 'nx/src/utils/package-json';
|
import { PackageJson, readNxMigrateConfig } from 'nx/src/utils/package-json';
|
||||||
@ -57,17 +55,10 @@ export default async function pluginLintCheckGenerator(
|
|||||||
`${NX_PREFIX} plugin lint checks can only be added to plugins which use eslint for linting`
|
`${NX_PREFIX} plugin lint checks can only be added to plugins which use eslint for linting`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const installTask = addDependenciesToPackageJson(
|
|
||||||
host,
|
|
||||||
{},
|
|
||||||
{ 'jsonc-eslint-parser': jsoncEslintParserVersion }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!options.skipFormat) {
|
if (!options.skipFormat) {
|
||||||
await formatFiles(host);
|
await formatFiles(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => installTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addMigrationJsonChecks(
|
export function addMigrationJsonChecks(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user