eslint-plugin-node-dependencies 0.6.0 → 0.9.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/README.md +11 -1
- package/dist/configs/recommended.js +1 -0
- package/dist/rules/absolute-version.js +159 -0
- package/dist/rules/compat-engines.js +1 -1
- package/dist/rules/no-dupe-deps.js +88 -0
- package/dist/rules/no-restricted-deps.js +259 -0
- package/dist/rules/prefer-caret-range-version.js +99 -0
- package/dist/rules/prefer-tilde-range-version.js +100 -0
- package/dist/rules/valid-semver.js +1 -1
- package/dist/utils/meta.js +103 -96
- package/dist/utils/package-json/index.js +5 -0
- package/dist/utils/package-json/worker.js +18 -0
- package/dist/utils/regexp.js +21 -0
- package/dist/utils/rules.js +10 -0
- package/dist/utils/semver-range.js +38 -0
- package/dist/utils/semver.js +6 -2
- package/package.json +101 -98
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ npm install --save-dev eslint eslint-plugin-node-dependencies
|
|
|
33
33
|
> **Requirements**
|
|
34
34
|
>
|
|
35
35
|
> - ESLint v6.0.0 and above
|
|
36
|
-
> - Node.js
|
|
36
|
+
> - Node.js v14.16.0 and above
|
|
37
37
|
|
|
38
38
|
<!--DOCS_IGNORE_END-->
|
|
39
39
|
|
|
@@ -119,13 +119,23 @@ The rules with the following star :star: are included in the `plugin:node-depend
|
|
|
119
119
|
| Rule ID | Description | |
|
|
120
120
|
|:--------|:------------|:---|
|
|
121
121
|
| [node-dependencies/compat-engines](https://ota-meshi.github.io/eslint-plugin-node-dependencies/rules/compat-engines.html) | enforce the versions of the engines of the dependencies to be compatible. | :star: |
|
|
122
|
+
| [node-dependencies/no-dupe-deps](https://ota-meshi.github.io/eslint-plugin-node-dependencies/rules/no-dupe-deps.html) | disallow duplicate dependencies. | :star: |
|
|
122
123
|
| [node-dependencies/valid-semver](https://ota-meshi.github.io/eslint-plugin-node-dependencies/rules/valid-semver.html) | enforce versions that is valid as a semantic version. | :star: |
|
|
123
124
|
|
|
124
125
|
### Best Practices
|
|
125
126
|
|
|
126
127
|
| Rule ID | Description | |
|
|
127
128
|
|:--------|:------------|:---|
|
|
129
|
+
| [node-dependencies/absolute-version](https://ota-meshi.github.io/eslint-plugin-node-dependencies/rules/absolute-version.html) | require or disallow absolute version of dependency. | |
|
|
128
130
|
| [node-dependencies/no-deprecated](https://ota-meshi.github.io/eslint-plugin-node-dependencies/rules/no-deprecated.html) | disallow having dependencies on deprecate packages. | |
|
|
131
|
+
| [node-dependencies/no-restricted-deps](https://ota-meshi.github.io/eslint-plugin-node-dependencies/rules/no-restricted-deps.html) | Disallows dependence on the specified package. | |
|
|
132
|
+
|
|
133
|
+
### Stylistic Issues
|
|
134
|
+
|
|
135
|
+
| Rule ID | Description | |
|
|
136
|
+
|:--------|:------------|:---|
|
|
137
|
+
| [node-dependencies/prefer-caret-range-version](https://ota-meshi.github.io/eslint-plugin-node-dependencies/rules/prefer-caret-range-version.html) | require caret(`^`) version instead of range version. | :wrench: |
|
|
138
|
+
| [node-dependencies/prefer-tilde-range-version](https://ota-meshi.github.io/eslint-plugin-node-dependencies/rules/prefer-tilde-range-version.html) | require tilde(`~`) version instead of range version. | :wrench: |
|
|
129
139
|
|
|
130
140
|
### Deprecated
|
|
131
141
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsonc_eslint_parser_1 = require("jsonc-eslint-parser");
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const ast_utils_1 = require("../utils/ast-utils");
|
|
6
|
+
const regexp_1 = require("../utils/regexp");
|
|
7
|
+
const semver_1 = require("../utils/semver");
|
|
8
|
+
const PREFERS = ["always", "never", "ignore"];
|
|
9
|
+
const SCHEMA_FOR_DEPS_PROPERTIES = {
|
|
10
|
+
dependencies: { enum: PREFERS },
|
|
11
|
+
peerDependencies: { enum: PREFERS },
|
|
12
|
+
optionalDependencies: { enum: PREFERS },
|
|
13
|
+
devDependencies: { enum: PREFERS },
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT = {
|
|
16
|
+
dependencies: "ignore",
|
|
17
|
+
peerDependencies: "ignore",
|
|
18
|
+
optionalDependencies: "ignore",
|
|
19
|
+
devDependencies: "always",
|
|
20
|
+
};
|
|
21
|
+
function stringToOption(option) {
|
|
22
|
+
return {
|
|
23
|
+
dependencies: option,
|
|
24
|
+
peerDependencies: option,
|
|
25
|
+
optionalDependencies: option,
|
|
26
|
+
devDependencies: option,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function objectToOption(option, defaults) {
|
|
30
|
+
return {
|
|
31
|
+
dependencies: option.dependencies || defaults.dependencies,
|
|
32
|
+
peerDependencies: option.peerDependencies || defaults.peerDependencies,
|
|
33
|
+
optionalDependencies: option.optionalDependencies || defaults.optionalDependencies,
|
|
34
|
+
devDependencies: option.devDependencies || defaults.devDependencies,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function parseOption(option) {
|
|
38
|
+
if (!option) {
|
|
39
|
+
return () => DEFAULT;
|
|
40
|
+
}
|
|
41
|
+
if (typeof option === "string") {
|
|
42
|
+
const objectOption = stringToOption(option);
|
|
43
|
+
return () => objectOption;
|
|
44
|
+
}
|
|
45
|
+
const baseOption = objectToOption(option, DEFAULT);
|
|
46
|
+
if (!option.overridePackages) {
|
|
47
|
+
return () => baseOption;
|
|
48
|
+
}
|
|
49
|
+
const overridePackages = Object.entries(option.overridePackages).map(([packageName, opt]) => {
|
|
50
|
+
const regexp = (0, regexp_1.toRegExp)(packageName);
|
|
51
|
+
return Object.assign({ test: (s) => regexp.test(s) }, (typeof opt === "string"
|
|
52
|
+
? stringToOption(opt)
|
|
53
|
+
: objectToOption(opt, baseOption)));
|
|
54
|
+
});
|
|
55
|
+
return (name) => {
|
|
56
|
+
for (const overridePackage of overridePackages) {
|
|
57
|
+
if (overridePackage.test(name)) {
|
|
58
|
+
return overridePackage;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return baseOption;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
exports.default = (0, utils_1.createRule)("absolute-version", {
|
|
65
|
+
meta: {
|
|
66
|
+
docs: {
|
|
67
|
+
description: "require or disallow absolute version of dependency.",
|
|
68
|
+
category: "Best Practices",
|
|
69
|
+
recommended: false,
|
|
70
|
+
},
|
|
71
|
+
schema: [
|
|
72
|
+
{
|
|
73
|
+
oneOf: [
|
|
74
|
+
{ enum: PREFERS.filter((p) => p !== "ignore") },
|
|
75
|
+
{
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: Object.assign(Object.assign({}, SCHEMA_FOR_DEPS_PROPERTIES), { overridePackages: {
|
|
78
|
+
type: "object",
|
|
79
|
+
patternProperties: {
|
|
80
|
+
"^(?:\\S+)$": {
|
|
81
|
+
oneOf: [
|
|
82
|
+
{ enum: PREFERS },
|
|
83
|
+
{
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: SCHEMA_FOR_DEPS_PROPERTIES,
|
|
86
|
+
additionalProperties: false,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
minProperties: 1,
|
|
92
|
+
additionalProperties: false,
|
|
93
|
+
} }),
|
|
94
|
+
additionalProperties: false,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
messages: {},
|
|
100
|
+
type: "suggestion",
|
|
101
|
+
},
|
|
102
|
+
create(context) {
|
|
103
|
+
if (!context.parserServices.isJSON) {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
const getOption = parseOption(context.options[0]);
|
|
107
|
+
function defineVisitor(visitName) {
|
|
108
|
+
return (node) => {
|
|
109
|
+
const ver = (0, jsonc_eslint_parser_1.getStaticJSONValue)(node.value);
|
|
110
|
+
if (typeof ver !== "string" || ver == null) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const name = String((0, ast_utils_1.getKeyFromJSONProperty)(node));
|
|
114
|
+
const option = getOption(name)[visitName];
|
|
115
|
+
const semver = (0, semver_1.getSemverRange)(ver);
|
|
116
|
+
if (semver == null) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (option === "always") {
|
|
120
|
+
if (isAbsoluteVersion(semver)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
context.report({
|
|
124
|
+
loc: node.value.loc,
|
|
125
|
+
message: "Use the absolute version instead.",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else if (option === "never") {
|
|
129
|
+
if (!isAbsoluteVersion(semver)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
context.report({
|
|
133
|
+
loc: node.value.loc,
|
|
134
|
+
message: "Do not use the absolute version.",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return (0, utils_1.defineJsonVisitor)({
|
|
140
|
+
dependencies: defineVisitor("dependencies"),
|
|
141
|
+
peerDependencies: defineVisitor("peerDependencies"),
|
|
142
|
+
optionalDependencies: defineVisitor("optionalDependencies"),
|
|
143
|
+
devDependencies: defineVisitor("devDependencies"),
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
function isAbsoluteVersion(semver) {
|
|
148
|
+
for (const comparators of semver.set) {
|
|
149
|
+
for (const comparator of comparators) {
|
|
150
|
+
if ((0, semver_1.isAnyComparator)(comparator)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (comparator.operator !== "=" && comparator.operator !== "") {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
@@ -160,7 +160,7 @@ exports.default = (0, utils_1.createRule)("compat-engines", {
|
|
|
160
160
|
}
|
|
161
161
|
function processDependencyModule(ctx, name, ver, modules, node) {
|
|
162
162
|
const currModules = [...modules, `${name}@${ver}`];
|
|
163
|
-
processMeta(ctx, (0, meta_1.getMetaFromNodeModules)(name, ver, context));
|
|
163
|
+
processMeta(ctx, (0, meta_1.getMetaFromNodeModules)(name, ver, { context }));
|
|
164
164
|
if (!ctx.hasInvalid() && ctx.isAllProcessed()) {
|
|
165
165
|
return;
|
|
166
166
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
const ast_utils_1 = require("../utils/ast-utils");
|
|
5
|
+
const DEPS = [
|
|
6
|
+
"dependencies",
|
|
7
|
+
"peerDependencies",
|
|
8
|
+
"optionalDependencies",
|
|
9
|
+
"devDependencies",
|
|
10
|
+
];
|
|
11
|
+
class AllowDuplicates {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.edges = [];
|
|
14
|
+
}
|
|
15
|
+
add(d1, d2) {
|
|
16
|
+
this.edges.push([d1, d2]);
|
|
17
|
+
}
|
|
18
|
+
isAllowedDuplicate(dep1, dep2) {
|
|
19
|
+
return this.edges.some(([d1, d2]) => (d1 === dep1 && d2 === dep2) || (d2 === dep1 && d1 === dep2));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.default = (0, utils_1.createRule)("no-dupe-deps", {
|
|
23
|
+
meta: {
|
|
24
|
+
docs: {
|
|
25
|
+
description: "disallow duplicate dependencies.",
|
|
26
|
+
category: "Possible Errors",
|
|
27
|
+
recommended: true,
|
|
28
|
+
},
|
|
29
|
+
schema: [],
|
|
30
|
+
messages: {
|
|
31
|
+
duplicated: "Duplicated dependency '{{name}}'.",
|
|
32
|
+
},
|
|
33
|
+
type: "problem",
|
|
34
|
+
},
|
|
35
|
+
create(context) {
|
|
36
|
+
if (!context.parserServices.isJSON) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
const allowDuplicates = new AllowDuplicates();
|
|
40
|
+
allowDuplicates.add("devDependencies", "peerDependencies");
|
|
41
|
+
allowDuplicates.add("devDependencies", "optionalDependencies");
|
|
42
|
+
const maps = {
|
|
43
|
+
dependencies: new Map(),
|
|
44
|
+
peerDependencies: new Map(),
|
|
45
|
+
optionalDependencies: new Map(),
|
|
46
|
+
devDependencies: new Map(),
|
|
47
|
+
};
|
|
48
|
+
const reported = new Set();
|
|
49
|
+
function report(name, node) {
|
|
50
|
+
if (reported.has(node)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
reported.add(node);
|
|
54
|
+
context.report({
|
|
55
|
+
loc: node.key.loc,
|
|
56
|
+
messageId: "duplicated",
|
|
57
|
+
data: {
|
|
58
|
+
name,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function verify(depsName, name, node) {
|
|
63
|
+
for (const dep of DEPS) {
|
|
64
|
+
if (allowDuplicates.isAllowedDuplicate(dep, depsName)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const dupeNode = maps[dep].get(name);
|
|
68
|
+
if (dupeNode) {
|
|
69
|
+
report(name, dupeNode);
|
|
70
|
+
report(name, node);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function defineVisitor(depsName) {
|
|
75
|
+
return (node) => {
|
|
76
|
+
const name = String((0, ast_utils_1.getKeyFromJSONProperty)(node));
|
|
77
|
+
verify(depsName, name, node);
|
|
78
|
+
maps[depsName].set(name, node);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return (0, utils_1.defineJsonVisitor)({
|
|
82
|
+
dependencies: defineVisitor("dependencies"),
|
|
83
|
+
peerDependencies: defineVisitor("peerDependencies"),
|
|
84
|
+
optionalDependencies: defineVisitor("optionalDependencies"),
|
|
85
|
+
devDependencies: defineVisitor("devDependencies"),
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
});
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
const regexp_1 = require("../utils/regexp");
|
|
5
|
+
const semver_1 = require("semver");
|
|
6
|
+
const semver_2 = require("../utils/semver");
|
|
7
|
+
const ast_utils_1 = require("../utils/ast-utils");
|
|
8
|
+
const jsonc_eslint_parser_1 = require("jsonc-eslint-parser");
|
|
9
|
+
const meta_1 = require("../utils/meta");
|
|
10
|
+
const DEPS = [
|
|
11
|
+
"dependencies",
|
|
12
|
+
"peerDependencies",
|
|
13
|
+
"optionalDependencies",
|
|
14
|
+
];
|
|
15
|
+
class Deps {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.map = Object.create(null);
|
|
18
|
+
this.list = [];
|
|
19
|
+
}
|
|
20
|
+
push(name, ver, ownerPackageJsonPath) {
|
|
21
|
+
const range = (0, semver_2.getSemverRange)(ver);
|
|
22
|
+
if (range) {
|
|
23
|
+
const data = this.map[name];
|
|
24
|
+
if (data) {
|
|
25
|
+
const newRange = (0, semver_2.normalizeSemverRange)(data.range, range);
|
|
26
|
+
this.map[name] = {
|
|
27
|
+
range: newRange,
|
|
28
|
+
ownerPackageJsonPath: ownerPackageJsonPath || data.ownerPackageJsonPath,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
this.map[name] = {
|
|
33
|
+
range,
|
|
34
|
+
ownerPackageJsonPath,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.list.push({ name, ver, ownerPackageJsonPath });
|
|
40
|
+
}
|
|
41
|
+
pop() {
|
|
42
|
+
const resultForList = this.list.shift();
|
|
43
|
+
if (resultForList) {
|
|
44
|
+
return resultForList;
|
|
45
|
+
}
|
|
46
|
+
const [key] = Object.keys(this.map);
|
|
47
|
+
if (key) {
|
|
48
|
+
const data = this.map[key];
|
|
49
|
+
delete this.map[key];
|
|
50
|
+
return {
|
|
51
|
+
name: key,
|
|
52
|
+
ver: (0, semver_2.normalizeVer)(data.range),
|
|
53
|
+
ownerPackageJsonPath: data.ownerPackageJsonPath,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
class DeepValidateContext {
|
|
60
|
+
constructor(context, validator) {
|
|
61
|
+
this.deepValidatedCache = new Map();
|
|
62
|
+
this.context = context;
|
|
63
|
+
this.validator = validator;
|
|
64
|
+
}
|
|
65
|
+
buildDeepValidator(deepOption) {
|
|
66
|
+
return (n, v) => this.deepDepsValidate(n, v, deepOption);
|
|
67
|
+
}
|
|
68
|
+
deepDepsValidate(packageName, version, deepOption) {
|
|
69
|
+
const { validator: validate, context } = this;
|
|
70
|
+
const depsQueue = new Deps();
|
|
71
|
+
depsQueue.push(packageName, version);
|
|
72
|
+
let dep;
|
|
73
|
+
while ((dep = depsQueue.pop())) {
|
|
74
|
+
const key = `${dep.name}@${dep.ver}`;
|
|
75
|
+
if (this.deepValidatedCache.has(key)) {
|
|
76
|
+
const result = this.deepValidatedCache.get(key);
|
|
77
|
+
if (result) {
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
const result = validateWithoutCache(dep.name, dep.ver, dep.ownerPackageJsonPath);
|
|
83
|
+
this.deepValidatedCache.set(key, result);
|
|
84
|
+
if (result) {
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
function validateWithoutCache(name, ver, ownerPackageJsonPath) {
|
|
91
|
+
const result = validate(name, ver);
|
|
92
|
+
if (result) {
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
for (const { name: n, ver: v, packageJsonPath } of iterateDeps(name, ver, ownerPackageJsonPath)) {
|
|
96
|
+
const r = validate(n, v);
|
|
97
|
+
if (r) {
|
|
98
|
+
return r;
|
|
99
|
+
}
|
|
100
|
+
depsQueue.push(n, v, packageJsonPath);
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function* iterateDeps(name, ver, ownerPackageJsonPath) {
|
|
105
|
+
yield* iterateDepsForMeta((0, meta_1.getMetaFromNodeModules)(name, ver, {
|
|
106
|
+
context,
|
|
107
|
+
ownerPackageJsonPath,
|
|
108
|
+
}));
|
|
109
|
+
if (deepOption === "server") {
|
|
110
|
+
const metaData = (0, meta_1.getMetaFromNpm)(name, ver);
|
|
111
|
+
for (const meta of metaData.cache) {
|
|
112
|
+
yield* iterateDepsForMeta(meta);
|
|
113
|
+
}
|
|
114
|
+
const metaList = metaData.get();
|
|
115
|
+
if (metaList) {
|
|
116
|
+
for (const meta of metaList) {
|
|
117
|
+
yield* iterateDepsForMeta(meta);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function* iterateDepsForMeta(meta) {
|
|
123
|
+
if (!meta) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
for (const depName of DEPS) {
|
|
127
|
+
const deps = meta[depName];
|
|
128
|
+
if (!deps) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
for (const [n, v] of Object.entries(deps)) {
|
|
132
|
+
if (typeof v === "string") {
|
|
133
|
+
yield { name: n, ver: v, packageJsonPath: meta._where };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.default = (0, utils_1.createRule)("no-restricted-deps", {
|
|
141
|
+
meta: {
|
|
142
|
+
docs: {
|
|
143
|
+
description: "Disallows dependence on the specified package.",
|
|
144
|
+
category: "Best Practices",
|
|
145
|
+
recommended: false,
|
|
146
|
+
},
|
|
147
|
+
schema: {
|
|
148
|
+
type: "array",
|
|
149
|
+
items: {
|
|
150
|
+
oneOf: [
|
|
151
|
+
{
|
|
152
|
+
type: "string",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
package: { type: "string" },
|
|
158
|
+
version: { type: "string" },
|
|
159
|
+
message: { type: "string" },
|
|
160
|
+
deep: { enum: ["local", "server"] },
|
|
161
|
+
},
|
|
162
|
+
required: ["package"],
|
|
163
|
+
additionalProperties: false,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
uniqueItems: true,
|
|
168
|
+
minItems: 0,
|
|
169
|
+
},
|
|
170
|
+
messages: {
|
|
171
|
+
restricted: "{{message}}",
|
|
172
|
+
},
|
|
173
|
+
type: "suggestion",
|
|
174
|
+
},
|
|
175
|
+
create(context) {
|
|
176
|
+
if (!context.parserServices.isJSON) {
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
179
|
+
const validateForPackage = parseOptions(context.options);
|
|
180
|
+
function defineVisitor(_depsName) {
|
|
181
|
+
return (node) => {
|
|
182
|
+
const name = String((0, ast_utils_1.getKeyFromJSONProperty)(node));
|
|
183
|
+
const ver = String((0, jsonc_eslint_parser_1.getStaticJSONValue)(node.value));
|
|
184
|
+
const result = validateForPackage(name, ver);
|
|
185
|
+
if (!result) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
context.report({
|
|
189
|
+
loc: node.loc,
|
|
190
|
+
messageId: "restricted",
|
|
191
|
+
data: result,
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return (0, utils_1.defineJsonVisitor)({
|
|
196
|
+
dependencies: defineVisitor("dependencies"),
|
|
197
|
+
peerDependencies: defineVisitor("peerDependencies"),
|
|
198
|
+
optionalDependencies: defineVisitor("optionalDependencies"),
|
|
199
|
+
devDependencies: defineVisitor("devDependencies"),
|
|
200
|
+
});
|
|
201
|
+
function matchVersions(version, optionVersion) {
|
|
202
|
+
if (!optionVersion) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
const range = (0, semver_2.getSemverRange)(version);
|
|
206
|
+
return Boolean(range && (0, semver_1.intersects)(range, optionVersion));
|
|
207
|
+
}
|
|
208
|
+
function parseOption(option) {
|
|
209
|
+
if (typeof option === "string") {
|
|
210
|
+
const regexp = (0, regexp_1.toRegExp)(option);
|
|
211
|
+
return (n, _v) => {
|
|
212
|
+
if (regexp.test(n)) {
|
|
213
|
+
return {
|
|
214
|
+
message: `Depend on '${option}' is not allowed.`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const regexp = (0, regexp_1.toRegExp)(option.package);
|
|
221
|
+
const version = option.version
|
|
222
|
+
? (0, semver_2.getSemverRange)(option.version)
|
|
223
|
+
: null;
|
|
224
|
+
const validator = (n, v) => {
|
|
225
|
+
if (regexp.test(n) && matchVersions(v, version)) {
|
|
226
|
+
return {
|
|
227
|
+
message: option.message || buildDefaultMessage(option),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
};
|
|
232
|
+
if (!option.deep) {
|
|
233
|
+
return validator;
|
|
234
|
+
}
|
|
235
|
+
const deepValidateContext = new DeepValidateContext(context, validator);
|
|
236
|
+
return deepValidateContext.buildDeepValidator(option.deep);
|
|
237
|
+
function buildDefaultMessage(objectOption) {
|
|
238
|
+
const versionForMessage = objectOption.version
|
|
239
|
+
? `@${objectOption.version}`
|
|
240
|
+
: "";
|
|
241
|
+
if (objectOption.package.startsWith("/") && versionForMessage) {
|
|
242
|
+
return `Depend on '${objectOption.package} ${versionForMessage}' is not allowed.`;
|
|
243
|
+
}
|
|
244
|
+
return `Depend on '${objectOption.package}${versionForMessage}' is not allowed.`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function parseOptions(options) {
|
|
248
|
+
const validators = options.map(parseOption);
|
|
249
|
+
return (packageName, version) => {
|
|
250
|
+
for (const validator of validators) {
|
|
251
|
+
const result = validator(packageName, version);
|
|
252
|
+
if (result)
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const semver_1 = require("semver");
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const semver_2 = require("../utils/semver");
|
|
6
|
+
const semver_range_1 = require("../utils/semver-range");
|
|
7
|
+
exports.default = (0, utils_1.createRule)("prefer-caret-range-version", {
|
|
8
|
+
meta: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: "require caret(`^`) version instead of range version.",
|
|
11
|
+
category: "Stylistic Issues",
|
|
12
|
+
recommended: false,
|
|
13
|
+
},
|
|
14
|
+
fixable: "code",
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {},
|
|
17
|
+
type: "suggestion",
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
if (!context.parserServices.isJSON) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
const sourceCode = context.getSourceCode();
|
|
24
|
+
function convertToUseCaret(range) {
|
|
25
|
+
if (range.comparators.length !== 2) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const start = range.comparators.find((c) => c.operator === ">=");
|
|
29
|
+
const end = range.comparators.find((c) => c.operator === "<");
|
|
30
|
+
if (!start || !end) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const caretRangeText = `^${start.semver.version}`;
|
|
34
|
+
const caretRange = (0, semver_2.getSemverRange)(caretRangeText);
|
|
35
|
+
if (!caretRange) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const caretEnd = caretRange.set[0].find((c) => c.operator === "<");
|
|
39
|
+
if (!caretEnd) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (caretEnd.semver.compare(end.semver) !== 0) {
|
|
43
|
+
const endPre = new semver_1.SemVer(`${end.semver.version}-0`);
|
|
44
|
+
if (caretEnd.semver.compare(endPre) !== 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return caretRangeText;
|
|
49
|
+
}
|
|
50
|
+
function verifyRange(range, node) {
|
|
51
|
+
const fixedRange = convertToUseCaret(range);
|
|
52
|
+
if (!fixedRange) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
context.report({
|
|
56
|
+
loc: {
|
|
57
|
+
start: sourceCode.getLocFromIndex(node.range[0] + range.range[0]),
|
|
58
|
+
end: sourceCode.getLocFromIndex(node.range[0] + range.range[1]),
|
|
59
|
+
},
|
|
60
|
+
message: `Use '${fixedRange}' syntax instead.`,
|
|
61
|
+
fix(fixer) {
|
|
62
|
+
return fixer.replaceTextRange(node.range, JSON.stringify(node.value.slice(0, range.range[0]) +
|
|
63
|
+
fixedRange +
|
|
64
|
+
node.value.slice(range.range[1])));
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function verifyVersion(node) {
|
|
69
|
+
if (maybeDepId(node.value)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const range of (0, semver_range_1.iterateSemverRanges)(node.value)) {
|
|
73
|
+
const lowerRange = range.value.toLowerCase();
|
|
74
|
+
if (!lowerRange.startsWith("~")) {
|
|
75
|
+
if (lowerRange.startsWith("^") ||
|
|
76
|
+
/^\d+$/u.test(range.value) ||
|
|
77
|
+
((range.value.endsWith(".x") ||
|
|
78
|
+
range.value.endsWith(".*")) &&
|
|
79
|
+
!/\s+-\s+/u.test(range.value)))
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
verifyRange(range, node);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return (0, utils_1.defineJsonVisitor)({
|
|
86
|
+
"engines, dependencies, peerDependencies, devDependencies, optionalDependencies"(node) {
|
|
87
|
+
if (isJSONStringLiteral(node.value)) {
|
|
88
|
+
verifyVersion(node.value);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
function isJSONStringLiteral(node) {
|
|
95
|
+
return node.type === "JSONLiteral" && typeof node.value === "string";
|
|
96
|
+
}
|
|
97
|
+
function maybeDepId(ver) {
|
|
98
|
+
return ver.includes("/") || ver.includes(":") || /^[-a-z]+$/.test(ver);
|
|
99
|
+
}
|