@nx/eslint-plugin 16.4.0-beta.9 → 16.5.0-beta.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/eslint-plugin",
3
- "version": "16.4.0-beta.9",
3
+ "version": "16.5.0-beta.0",
4
4
  "private": false,
5
5
  "description": "The eslint-plugin package is an ESLint plugin that contains a collection of recommended ESLint rule configurations which you can extend from in your own ESLint configs, as well as an Nx-specific lint rule called enforce-module-boundaries.",
6
6
  "repository": {
@@ -33,14 +33,15 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "@nrwl/eslint-plugin-nx": "16.4.0-beta.9",
37
- "@nx/devkit": "16.4.0-beta.9",
38
- "@nx/js": "16.4.0-beta.9",
36
+ "@nrwl/eslint-plugin-nx": "16.5.0-beta.0",
37
+ "@nx/devkit": "16.5.0-beta.0",
38
+ "@nx/js": "16.5.0-beta.0",
39
39
  "@typescript-eslint/type-utils": "^5.58.0",
40
40
  "@typescript-eslint/utils": "^5.58.0",
41
41
  "chalk": "^4.1.0",
42
42
  "confusing-browser-globals": "^1.0.9",
43
- "semver": "7.3.4"
43
+ "jsonc-eslint-parser": "^2.1.0",
44
+ "semver": "7.5.3"
44
45
  },
45
46
  "publishConfig": {
46
47
  "access": "public"
@@ -49,5 +50,5 @@
49
50
  "migrations": "./migrations.json"
50
51
  },
51
52
  "types": "./src/index.d.ts",
52
- "gitHead": "b7fbc25d5be1789d42805e2254cf4a6070889157"
53
+ "gitHead": "376d656cfc7f6390ff1cd6d9de348baf7bccbf33"
53
54
  }
package/src/index.js CHANGED
@@ -10,6 +10,7 @@ const angular_1 = require("./configs/angular");
10
10
  const angular_template_1 = require("./configs/angular-template");
11
11
  const enforce_module_boundaries_1 = require("./rules/enforce-module-boundaries");
12
12
  const nx_plugin_checks_1 = require("./rules/nx-plugin-checks");
13
+ const dependency_checks_1 = require("./rules/dependency-checks");
13
14
  // Resolve any custom rules that might exist in the current workspace
14
15
  const resolve_workspace_rules_1 = require("./resolve-workspace-rules");
15
16
  module.exports = {
@@ -23,5 +24,5 @@ module.exports = {
23
24
  angular: angular_1.default,
24
25
  'angular-template': angular_template_1.default,
25
26
  },
26
- rules: Object.assign({ [enforce_module_boundaries_1.RULE_NAME]: enforce_module_boundaries_1.default, [nx_plugin_checks_1.RULE_NAME]: nx_plugin_checks_1.default }, resolve_workspace_rules_1.workspaceRules),
27
+ rules: Object.assign({ [enforce_module_boundaries_1.RULE_NAME]: enforce_module_boundaries_1.default, [nx_plugin_checks_1.RULE_NAME]: nx_plugin_checks_1.default, [dependency_checks_1.RULE_NAME]: dependency_checks_1.default }, resolve_workspace_rules_1.workspaceRules),
27
28
  };
@@ -0,0 +1,14 @@
1
+ export type Options = [
2
+ {
3
+ buildTargets?: string[];
4
+ checkMissingDependencies?: boolean;
5
+ checkObsoleteDependencies?: boolean;
6
+ checkVersionMismatches?: boolean;
7
+ checkMissingPackageJson?: boolean;
8
+ ignoredDependencies?: string[];
9
+ }
10
+ ];
11
+ export type MessageIds = 'missingDependency' | 'obsoleteDependency' | 'versionMismatch' | 'missingDependencySection';
12
+ export declare const RULE_NAME = "dependency-checks";
13
+ declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
14
+ export default _default;
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_NAME = void 0;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
6
+ const project_graph_utils_1 = require("../utils/project-graph-utils");
7
+ const runtime_lint_utils_1 = require("../utils/runtime-lint-utils");
8
+ const path_1 = require("path");
9
+ const internal_1 = require("@nx/js/src/internal");
10
+ const semver_1 = require("semver");
11
+ const js_1 = require("@nx/js");
12
+ const package_json_utils_1 = require("../utils/package-json-utils");
13
+ exports.RULE_NAME = 'dependency-checks';
14
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
15
+ name: exports.RULE_NAME,
16
+ meta: {
17
+ type: 'suggestion',
18
+ docs: {
19
+ description: `Checks dependencies in project's package.json for version mismatches`,
20
+ recommended: 'error',
21
+ },
22
+ fixable: 'code',
23
+ schema: [
24
+ {
25
+ type: 'object',
26
+ properties: {
27
+ buildTargets: [{ type: 'string' }],
28
+ ignoreDependencies: [{ type: 'string' }],
29
+ checkMissingDependencies: { type: 'boolean' },
30
+ checkObsoleteDependencies: { type: 'boolean' },
31
+ checkVersionMismatches: { type: 'boolean' },
32
+ },
33
+ additionalProperties: false,
34
+ },
35
+ ],
36
+ messages: {
37
+ missingDependency: `The "{{projectName}}" uses the package "{{packageName}}", but it is missing from the project's "package.json".`,
38
+ obsoleteDependency: `The "{{packageName}}" package is not used by "{{projectName}}".`,
39
+ versionMismatch: `The version specifier does not contain the installed version of "{{packageName}}" package: {{version}}.`,
40
+ missingDependencySection: `Dependency sections are missing from the "package.json" but following dependencies were detected:{{dependencies}}`,
41
+ },
42
+ },
43
+ defaultOptions: [
44
+ {
45
+ buildTargets: ['build'],
46
+ checkMissingDependencies: true,
47
+ checkObsoleteDependencies: true,
48
+ checkVersionMismatches: true,
49
+ ignoredDependencies: [],
50
+ },
51
+ ],
52
+ create(context, [{ buildTargets, ignoredDependencies, checkMissingDependencies, checkObsoleteDependencies, checkVersionMismatches, },]) {
53
+ var _a;
54
+ if (!context.parserServices.isJSON) {
55
+ return {};
56
+ }
57
+ const fileName = (0, devkit_1.normalizePath)(context.getFilename());
58
+ // support only package.json
59
+ if (!fileName.endsWith('/package.json')) {
60
+ return {};
61
+ }
62
+ const projectPath = (0, devkit_1.normalizePath)(globalThis.projectPath || devkit_1.workspaceRoot);
63
+ const sourceFilePath = (0, runtime_lint_utils_1.getSourceFilePath)(fileName, projectPath);
64
+ const { projectGraph, projectRootMappings, projectFileMap } = (0, project_graph_utils_1.readProjectGraph)(exports.RULE_NAME);
65
+ if (!projectGraph) {
66
+ return {};
67
+ }
68
+ const sourceProject = (0, runtime_lint_utils_1.findProject)(projectGraph, projectRootMappings, sourceFilePath);
69
+ // check if source project exists
70
+ if (!sourceProject) {
71
+ return {};
72
+ }
73
+ // check if library has a build target
74
+ const buildTarget = buildTargets.find((t) => { var _a; return (_a = sourceProject.data.targets) === null || _a === void 0 ? void 0 : _a[t]; });
75
+ if (!buildTarget) {
76
+ return {};
77
+ }
78
+ // gather helper dependencies for @nx/js executors
79
+ const helperDependencies = (0, js_1.getHelperDependenciesFromProjectGraph)(devkit_1.workspaceRoot, sourceProject.name, projectGraph);
80
+ // find all dependencies for the project
81
+ const npmDeps = (0, internal_1.findProjectsNpmDependencies)(sourceProject, projectGraph, buildTarget, {
82
+ helperDependencies: helperDependencies.map((dep) => dep.target),
83
+ }, (0, package_json_utils_1.removePackageJsonFromFileMap)(projectFileMap));
84
+ const projDependencies = Object.assign(Object.assign({}, npmDeps.dependencies), npmDeps.peerDependencies);
85
+ const expectedDependencyNames = Object.keys(projDependencies);
86
+ const projPackageJsonPath = (0, path_1.join)(devkit_1.workspaceRoot, sourceProject.data.root, 'package.json');
87
+ (_a = globalThis.projPackageJsonDeps) !== null && _a !== void 0 ? _a : (globalThis.projPackageJsonDeps = (0, package_json_utils_1.getAllDependencies)(projPackageJsonPath));
88
+ const projPackageJsonDeps = globalThis.projPackageJsonDeps;
89
+ const rootPackageJsonDeps = (0, package_json_utils_1.getAllDependencies)((0, path_1.join)(devkit_1.workspaceRoot, 'package.json'));
90
+ function validateMissingDependencies(node) {
91
+ if (!checkMissingDependencies) {
92
+ return;
93
+ }
94
+ const missingDeps = expectedDependencyNames.filter((d) => !projPackageJsonDeps[d]);
95
+ missingDeps.forEach((d) => {
96
+ if (!ignoredDependencies.includes(d)) {
97
+ context.report({
98
+ node: node,
99
+ messageId: 'missingDependency',
100
+ data: { packageName: d, projectName: sourceProject.name },
101
+ fix(fixer) {
102
+ projPackageJsonDeps[d] =
103
+ rootPackageJsonDeps[d] || projDependencies[d];
104
+ const deps = node.value.properties;
105
+ if (deps.length) {
106
+ return fixer.insertTextAfter(deps[deps.length - 1], `,\n "${d}": "${projPackageJsonDeps[d]}"`);
107
+ }
108
+ else {
109
+ return fixer.replaceTextRange([node.value.range[0] + 1, node.value.range[1] - 1], `\n "${d}": "${projPackageJsonDeps[d]}"\n `);
110
+ }
111
+ },
112
+ });
113
+ }
114
+ });
115
+ }
116
+ function validateVersionMatchesInstalled(node, packageName, packageRange) {
117
+ if (!checkVersionMismatches) {
118
+ return;
119
+ }
120
+ if (projDependencies[packageName] === '*' ||
121
+ (0, semver_1.satisfies)(projDependencies[packageName], packageRange)) {
122
+ return;
123
+ }
124
+ context.report({
125
+ node: node,
126
+ messageId: 'versionMismatch',
127
+ data: {
128
+ packageName: packageName,
129
+ version: projDependencies[packageName],
130
+ },
131
+ fix: (fixer) => fixer.replaceText(node, `"${packageName}": "${rootPackageJsonDeps[packageName] || projDependencies[packageName]}"`),
132
+ });
133
+ }
134
+ function reportObsoleteDependency(node, packageName) {
135
+ if (!checkObsoleteDependencies) {
136
+ return;
137
+ }
138
+ context.report({
139
+ node: node,
140
+ messageId: 'obsoleteDependency',
141
+ data: { packageName: packageName, projectName: sourceProject.name },
142
+ fix: (fixer) => {
143
+ const isLastProperty = node.parent.properties[node.parent.properties.length - 1] === node;
144
+ const index = node.parent.properties.findIndex((n) => n === node);
145
+ if (index > 0) {
146
+ const previousNode = node.parent.properties[index - 1];
147
+ return fixer.removeRange([
148
+ previousNode.range[1] + 1,
149
+ node.range[1] + (isLastProperty ? 0 : 1),
150
+ ]);
151
+ }
152
+ else {
153
+ const parent = node.parent;
154
+ // it's the only property
155
+ if (isLastProperty) {
156
+ return fixer.removeRange([
157
+ parent.range[0] + 1,
158
+ parent.range[1] - 1,
159
+ ]);
160
+ }
161
+ else {
162
+ return fixer.removeRange([
163
+ parent.range[0] + 1,
164
+ node.range[1] + 1,
165
+ ]);
166
+ }
167
+ }
168
+ // remove 4 spaces, new line and potential comma from previous line
169
+ const shouldRemoveSiblingComma = isLastProperty && node.parent.properties.length > 1;
170
+ const leadingChars = 5 + (shouldRemoveSiblingComma ? 1 : 0);
171
+ return fixer.removeRange([
172
+ node.range[0] - leadingChars,
173
+ node.range[1] + (isLastProperty ? 0 : 1),
174
+ ]);
175
+ },
176
+ });
177
+ }
178
+ function validateDependenciesSectionExistance(node) {
179
+ if (!expectedDependencyNames.length ||
180
+ !expectedDependencyNames.some((d) => !ignoredDependencies.includes(d))) {
181
+ return;
182
+ }
183
+ if (!node.properties ||
184
+ !node.properties.some((p) => [
185
+ 'dependencies',
186
+ 'peerDependencies',
187
+ 'devDependencies',
188
+ 'optionalDependencies',
189
+ ].includes(p.key.value))) {
190
+ context.report({
191
+ node: node,
192
+ messageId: 'missingDependencySection',
193
+ data: {
194
+ dependencies: expectedDependencyNames
195
+ .map((d) => `\n- "${d}"`)
196
+ .join(),
197
+ },
198
+ fix: (fixer) => {
199
+ expectedDependencyNames.sort().reduce((acc, d) => {
200
+ acc[d] = rootPackageJsonDeps[d] || projDependencies[d];
201
+ return acc;
202
+ }, projPackageJsonDeps);
203
+ const dependencies = Object.keys(projPackageJsonDeps)
204
+ .map((d) => `\n "${d}": "${projPackageJsonDeps[d]}"`)
205
+ .join(',');
206
+ if (!node.properties.length) {
207
+ return fixer.replaceText(node, `{\n "dependencies": {${dependencies}\n }\n}`);
208
+ }
209
+ else {
210
+ return fixer.insertTextAfter(node.properties[node.properties.length - 1], `,\n "dependencies": {${dependencies}\n }`);
211
+ }
212
+ },
213
+ });
214
+ }
215
+ }
216
+ return {
217
+ ['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i]'](node) {
218
+ return validateMissingDependencies(node);
219
+ },
220
+ ['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i] > JSONObjectExpression > JSONProperty'](node) {
221
+ const packageName = node.key.value;
222
+ const packageRange = node.value.value;
223
+ if (ignoredDependencies.includes(packageName)) {
224
+ return;
225
+ }
226
+ if (expectedDependencyNames.includes(packageName)) {
227
+ return validateVersionMatchesInstalled(node, packageName, packageRange);
228
+ }
229
+ else {
230
+ return reportObsoleteDependency(node, packageName);
231
+ }
232
+ },
233
+ ['JSONExpressionStatement > JSONObjectExpression'](node) {
234
+ return validateDependenciesSectionExistance(node);
235
+ },
236
+ };
237
+ },
238
+ });
@@ -64,7 +64,7 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
64
64
  noImportsOfApps: 'Imports of apps are forbidden',
65
65
  noImportsOfE2e: 'Imports of e2e projects are forbidden',
66
66
  noImportOfNonBuildableLibraries: 'Buildable libraries cannot import or export from non-buildable libraries',
67
- noImportsOfLazyLoadedLibraries: `Imports of lazy-loaded libraries are forbidden`,
67
+ noImportsOfLazyLoadedLibraries: `Static imports of lazy-loaded libraries are forbidden.\n\nLibrary "{{targetProjectName}}" is lazy-loaded in these files:\n{{filePaths}}`,
68
68
  projectWithoutTagsCannotHaveDependencies: `A project without tags matching at least one constraint cannot depend on any libraries`,
69
69
  bannedExternalImportsViolation: `A project tagged with "{{sourceTag}}" is not allowed to import the "{{package}}" package`,
70
70
  nestedBannedExternalImportsViolation: `A project tagged with "{{sourceTag}}" is not allowed to import the "{{package}}" package. Nested import found at {{childProjectName}}`,
@@ -312,7 +312,14 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
312
312
  node.importKind !== 'type' &&
313
313
  !checkDynamicDependenciesExceptions.some((a) => (0, runtime_lint_utils_1.matchImportWithWildcard)(a, imp)) &&
314
314
  (0, runtime_lint_utils_1.onlyLoadChildren)(projectGraph, sourceProject.name, targetProject.name, [])) {
315
+ const filesWithLazyImports = (0, graph_utils_1.findFilesWithDynamicImports)(projectFileMap, sourceProject.name, targetProject.name);
315
316
  context.report({
317
+ data: {
318
+ filePaths: filesWithLazyImports
319
+ .map(({ file }) => `- ${file}`)
320
+ .join('\n'),
321
+ targetProjectName: targetProject.name,
322
+ },
316
323
  node,
317
324
  messageId: 'noImportsOfLazyLoadedLibraries',
318
325
  });
@@ -1,5 +1,6 @@
1
- import type { ProjectFileMap, ProjectGraph, ProjectGraphProjectNode } from '@nx/devkit';
1
+ import type { FileData, ProjectFileMap, ProjectGraph, ProjectGraphProjectNode } from '@nx/devkit';
2
2
  export declare function getPath(graph: ProjectGraph, sourceProjectName: string, targetProjectName: string): Array<ProjectGraphProjectNode>;
3
3
  export declare function pathExists(graph: ProjectGraph, sourceProjectName: string, targetProjectName: string): boolean;
4
4
  export declare function checkCircularPath(graph: ProjectGraph, sourceProject: ProjectGraphProjectNode, targetProject: ProjectGraphProjectNode): ProjectGraphProjectNode[];
5
5
  export declare function findFilesInCircularPath(projectFileMap: ProjectFileMap, circularPath: ProjectGraphProjectNode[]): Array<string[]>;
6
+ export declare function findFilesWithDynamicImports(projectFileMap: ProjectFileMap, sourceProjectName: string, targetProjectName: string): FileData[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.findFilesInCircularPath = exports.checkCircularPath = exports.pathExists = exports.getPath = void 0;
3
+ exports.findFilesWithDynamicImports = exports.findFilesInCircularPath = exports.checkCircularPath = exports.pathExists = exports.getPath = void 0;
4
4
  const project_graph_1 = require("nx/src/config/project-graph");
5
5
  const reach = {
6
6
  graph: null,
@@ -114,3 +114,16 @@ function findFilesInCircularPath(projectFileMap, circularPath) {
114
114
  return filePathChain;
115
115
  }
116
116
  exports.findFilesInCircularPath = findFilesInCircularPath;
117
+ function findFilesWithDynamicImports(projectFileMap, sourceProjectName, targetProjectName) {
118
+ const files = [];
119
+ projectFileMap[sourceProjectName].forEach((file) => {
120
+ if (!file.deps)
121
+ return;
122
+ if (file.deps.some((d) => (0, project_graph_1.fileDataDepTarget)(d) === targetProjectName &&
123
+ (0, project_graph_1.fileDataDepType)(d) === project_graph_1.DependencyType.dynamic)) {
124
+ files.push(file);
125
+ }
126
+ });
127
+ return files;
128
+ }
129
+ exports.findFilesWithDynamicImports = findFilesWithDynamicImports;
@@ -0,0 +1,3 @@
1
+ import { ProjectFileMap } from '@nx/devkit';
2
+ export declare function getAllDependencies(path: string): Record<string, string>;
3
+ export declare function removePackageJsonFromFileMap(projectFileMap: ProjectFileMap): ProjectFileMap;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removePackageJsonFromFileMap = exports.getAllDependencies = void 0;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const fs_1 = require("fs");
6
+ function getAllDependencies(path) {
7
+ if ((0, fs_1.existsSync)(path)) {
8
+ const packageJson = (0, devkit_1.readJsonFile)(path);
9
+ return Object.assign(Object.assign(Object.assign({}, packageJson.dependencies), packageJson.devDependencies), packageJson.peerDependencies);
10
+ }
11
+ return {};
12
+ }
13
+ exports.getAllDependencies = getAllDependencies;
14
+ function removePackageJsonFromFileMap(projectFileMap) {
15
+ const newFileMap = {};
16
+ Object.keys(projectFileMap).forEach((key) => {
17
+ newFileMap[key] = projectFileMap[key].filter((f) => !f.file.endsWith('/package.json'));
18
+ });
19
+ return newFileMap;
20
+ }
21
+ exports.removePackageJsonFromFileMap = removePackageJsonFromFileMap;