eslint-plugin-package-json 1.2.0 → 1.4.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 +2 -1
- package/lib/plugin.mjs +30 -28
- package/lib/rules/no-local-dependencies.d.mts +17 -0
- package/lib/rules/no-local-dependencies.mjs +47 -0
- package/lib/rules/no-redundant-files.mjs +8 -9
- package/lib/rules/order-properties.mjs +2 -2
- package/lib/rules/require-attribution.mjs +1 -1
- package/lib/rules/restrict-dependency-ranges.mjs +1 -1
- package/lib/rules/restrict-private-properties.mjs +1 -1
- package/lib/rules/sort-collections.d.mts +26 -3
- package/lib/rules/sort-collections.mjs +52 -13
- package/lib/rules/valid-repository-directory.mjs +16 -12
- package/lib/utils/createSimpleRequirePropertyRule.mjs +1 -1
- package/package.json +29 -27
package/README.md
CHANGED
|
@@ -43,9 +43,10 @@ See [Getting Started](https://eslint-plugin-package-json.dev/getting-started) fo
|
|
|
43
43
|
| [bin-name-casing](https://eslint-plugin-package-json.dev/rules/bin-name-casing) | Enforce that names for bin properties are in kebab case. | 🎨 | | 💡 |
|
|
44
44
|
| [exports-subpaths-style](https://eslint-plugin-package-json.dev/rules/exports-subpaths-style) | Enforce consistent format for the exports field (implicit or explicit subpaths). | 🎨 | 🔧 | |
|
|
45
45
|
| [no-empty-fields](https://eslint-plugin-package-json.dev/rules/no-empty-fields) | Reports on unnecessary empty arrays and objects. | ✅ 📦 | | 💡 |
|
|
46
|
+
| [no-local-dependencies](https://eslint-plugin-package-json.dev/rules/no-local-dependencies) | Requires that dependencies do not use local file paths, which will likely result in errors when installing from a registry. | | | |
|
|
46
47
|
| [no-redundant-files](https://eslint-plugin-package-json.dev/rules/no-redundant-files) | Prevents adding unnecessary / redundant files. | ✅ 📦 | | 💡 |
|
|
47
48
|
| [no-redundant-publishConfig](https://eslint-plugin-package-json.dev/rules/no-redundant-publishConfig) | Warns when publishConfig.access is used in unscoped packages. | ✅ 📦 | | 💡 |
|
|
48
|
-
| [order-properties](https://eslint-plugin-package-json.dev/rules/order-properties) |
|
|
49
|
+
| [order-properties](https://eslint-plugin-package-json.dev/rules/order-properties) | Enforces that package properties are declared in a consistent order. | 🎨 | 🔧 | |
|
|
49
50
|
| [repository-shorthand](https://eslint-plugin-package-json.dev/rules/repository-shorthand) | Enforce either object or shorthand declaration for repository. | ✅ 📦 | 🔧 | |
|
|
50
51
|
| [require-attribution](https://eslint-plugin-package-json.dev/rules/require-attribution) | Ensures that proper attribution is included, requiring that either `author` or `contributors` is defined, and that if `contributors` is present, it should include at least one contributor. | ✅ 📦 | | 💡 |
|
|
51
52
|
| [restrict-dependency-ranges](https://eslint-plugin-package-json.dev/rules/restrict-dependency-ranges) | Restricts the range of dependencies to allow or disallow specific types of ranges. | | | 💡 |
|
package/lib/plugin.mjs
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { rule } from "./rules/bin-name-casing.mjs";
|
|
2
2
|
import { rule as rule$1 } from "./rules/exports-subpaths-style.mjs";
|
|
3
3
|
import { rule as rule$2 } from "./rules/no-empty-fields.mjs";
|
|
4
|
-
import { rule as rule$3 } from "./rules/no-
|
|
5
|
-
import { rule as rule$4 } from "./rules/no-redundant-
|
|
6
|
-
import { rule as rule$5 } from "./rules/
|
|
7
|
-
import { rule as rule$6 } from "./rules/
|
|
8
|
-
import { rule as rule$7 } from "./rules/
|
|
4
|
+
import { rule as rule$3 } from "./rules/no-local-dependencies.mjs";
|
|
5
|
+
import { rule as rule$4 } from "./rules/no-redundant-files.mjs";
|
|
6
|
+
import { rule as rule$5 } from "./rules/no-redundant-publishConfig.mjs";
|
|
7
|
+
import { rule as rule$6 } from "./rules/order-properties.mjs";
|
|
8
|
+
import { rule as rule$7 } from "./rules/repository-shorthand.mjs";
|
|
9
|
+
import { rule as rule$8 } from "./rules/require-attribution.mjs";
|
|
9
10
|
import { rules as rules$1 } from "./rules/require-properties.mjs";
|
|
10
|
-
import { rule as rule$
|
|
11
|
-
import { rule as rule$
|
|
12
|
-
import { rule as rule$
|
|
13
|
-
import { rule as rule$
|
|
14
|
-
import { rule as rule$
|
|
15
|
-
import { rule as rule$
|
|
16
|
-
import { rule as rule$
|
|
17
|
-
import { rule as rule$
|
|
11
|
+
import { rule as rule$9 } from "./rules/restrict-dependency-ranges.mjs";
|
|
12
|
+
import { rule as rule$10 } from "./rules/restrict-private-properties.mjs";
|
|
13
|
+
import { rule as rule$11 } from "./rules/restrict-top-level-properties.mjs";
|
|
14
|
+
import { rule as rule$12 } from "./rules/scripts-name-casing.mjs";
|
|
15
|
+
import { rule as rule$13 } from "./rules/sort-collections.mjs";
|
|
16
|
+
import { rule as rule$14 } from "./rules/specify-peers-locally.mjs";
|
|
17
|
+
import { rule as rule$15 } from "./rules/unique-dependencies.mjs";
|
|
18
|
+
import { rule as rule$16 } from "./rules/valid-peerDependenciesMeta-relationship.mjs";
|
|
18
19
|
import { rules as rules$2 } from "./rules/valid-properties.mjs";
|
|
19
|
-
import { rule as rule$
|
|
20
|
+
import { rule as rule$17 } from "./rules/valid-repository-directory.mjs";
|
|
20
21
|
import { createRequire } from "node:module";
|
|
21
22
|
import * as parserJsonc from "jsonc-eslint-parser";
|
|
22
23
|
//#region src/plugin.ts
|
|
@@ -25,22 +26,23 @@ const rules = {
|
|
|
25
26
|
"bin-name-casing": rule,
|
|
26
27
|
"exports-subpaths-style": rule$1,
|
|
27
28
|
"no-empty-fields": rule$2,
|
|
28
|
-
"no-
|
|
29
|
-
"no-redundant-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
29
|
+
"no-local-dependencies": rule$3,
|
|
30
|
+
"no-redundant-files": rule$4,
|
|
31
|
+
"no-redundant-publishConfig": rule$5,
|
|
32
|
+
"order-properties": rule$6,
|
|
33
|
+
"require-attribution": rule$8,
|
|
32
34
|
...rules$1,
|
|
33
|
-
"repository-shorthand": rule$
|
|
34
|
-
"restrict-dependency-ranges": rule$
|
|
35
|
-
"restrict-private-properties": rule$
|
|
36
|
-
"restrict-top-level-properties": rule$
|
|
37
|
-
"scripts-name-casing": rule$
|
|
38
|
-
"sort-collections": rule$
|
|
39
|
-
"specify-peers-locally": rule$
|
|
40
|
-
"unique-dependencies": rule$
|
|
35
|
+
"repository-shorthand": rule$7,
|
|
36
|
+
"restrict-dependency-ranges": rule$9,
|
|
37
|
+
"restrict-private-properties": rule$10,
|
|
38
|
+
"restrict-top-level-properties": rule$11,
|
|
39
|
+
"scripts-name-casing": rule$12,
|
|
40
|
+
"sort-collections": rule$13,
|
|
41
|
+
"specify-peers-locally": rule$14,
|
|
42
|
+
"unique-dependencies": rule$15,
|
|
41
43
|
...rules$2,
|
|
42
|
-
"valid-peerDependenciesMeta-relationship": rule$
|
|
43
|
-
"valid-repository-directory": rule$
|
|
44
|
+
"valid-peerDependenciesMeta-relationship": rule$16,
|
|
45
|
+
"valid-repository-directory": rule$17
|
|
44
46
|
};
|
|
45
47
|
const recommendedRules = { ...Object.fromEntries(Object.entries(rules).filter(([, rule]) => rule.meta.docs?.recommended).map(([name]) => ["package-json/" + name, "error"])) };
|
|
46
48
|
const recommendedPublishableRules = {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PackageJsonRuleModule } from "../createRule.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-local-dependencies.d.ts
|
|
4
|
+
declare const rule: PackageJsonRuleModule<[({
|
|
5
|
+
ignorePrivate?: boolean | undefined;
|
|
6
|
+
} | undefined)?], [{
|
|
7
|
+
readonly additionalProperties: false;
|
|
8
|
+
readonly properties: {
|
|
9
|
+
readonly ignorePrivate: {
|
|
10
|
+
readonly description: "Determines if this rule should be enforced when the package's `private` property is `true`.";
|
|
11
|
+
readonly type: "boolean";
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
readonly type: "object";
|
|
15
|
+
}]>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { rule };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createRule } from "../createRule.mjs";
|
|
2
|
+
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
|
+
//#region src/rules/no-local-dependencies.ts
|
|
4
|
+
const isLocalDependency = (value) => value.startsWith("file:") || value.startsWith("link:") || value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
|
|
5
|
+
const rule = createRule({
|
|
6
|
+
create(context) {
|
|
7
|
+
const ignorePrivate = context.options[0]?.ignorePrivate ?? true;
|
|
8
|
+
let isPrivate = false;
|
|
9
|
+
let dependencyNodes = [];
|
|
10
|
+
return {
|
|
11
|
+
"Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.type=JSONLiteral][value.type=JSONLiteral][key.value=private]"(node) {
|
|
12
|
+
if (node.value.value === true) isPrivate = true;
|
|
13
|
+
},
|
|
14
|
+
"Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.type=JSONLiteral][value.type=JSONObjectExpression][key.value=dependencies]"(node) {
|
|
15
|
+
dependencyNodes = node.value.properties;
|
|
16
|
+
},
|
|
17
|
+
"Program:exit"() {
|
|
18
|
+
if (ignorePrivate && isPrivate) return;
|
|
19
|
+
for (const dependencyPropertyNode of dependencyNodes) {
|
|
20
|
+
const dependencyValue = dependencyPropertyNode.value;
|
|
21
|
+
if (isJSONStringLiteral(dependencyValue) && isLocalDependency(dependencyValue.value)) context.report({
|
|
22
|
+
data: { name: dependencyValue.value },
|
|
23
|
+
messageId: "localDependencyFound",
|
|
24
|
+
node: dependencyValue
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
meta: {
|
|
31
|
+
defaultOptions: [{ ignorePrivate: true }],
|
|
32
|
+
docs: { description: "Requires that dependencies do not use local file paths, which will likely result in errors when installing from a registry." },
|
|
33
|
+
messages: { localDependencyFound: "Local dependency \"{{ name }}\" is not allowed." },
|
|
34
|
+
schema: [{
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
properties: { ignorePrivate: {
|
|
37
|
+
description: "Determines if this rule should be enforced when the package's `private` property is `true`.",
|
|
38
|
+
type: "boolean"
|
|
39
|
+
} },
|
|
40
|
+
type: "object"
|
|
41
|
+
}],
|
|
42
|
+
type: "problem"
|
|
43
|
+
},
|
|
44
|
+
name: "no-local-dependencies"
|
|
45
|
+
});
|
|
46
|
+
//#endregion
|
|
47
|
+
export { rule };
|
|
@@ -47,15 +47,14 @@ const rule = createRule({
|
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
"Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=files]"(node) {
|
|
50
|
-
if (node.value.type
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
50
|
+
if (node.value.type !== "JSONArrayExpression") return;
|
|
51
|
+
const seen = /* @__PURE__ */ new Set();
|
|
52
|
+
const elements = node.value.elements;
|
|
53
|
+
entryCache.files = elements;
|
|
54
|
+
for (const [index, element] of elements.entries()) if (isNotNullish(element) && isJSONStringLiteral(element)) {
|
|
55
|
+
if (seen.has(element.value)) report(elements, index, "duplicate");
|
|
56
|
+
else seen.add(element.value);
|
|
57
|
+
for (const defaultFile of defaultFiles) if (defaultFile.test(element.value)) report(elements, index, "unnecessaryDefault");
|
|
59
58
|
}
|
|
60
59
|
},
|
|
61
60
|
"Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=main]"(node) {
|
|
@@ -38,10 +38,10 @@ const rule = createRule({
|
|
|
38
38
|
defaultOptions: [{ order: "sort-package-json" }],
|
|
39
39
|
docs: {
|
|
40
40
|
category: "Stylistic",
|
|
41
|
-
description: "
|
|
41
|
+
description: "Enforces that package properties are declared in a consistent order."
|
|
42
42
|
},
|
|
43
43
|
fixable: "code",
|
|
44
|
-
messages: { incorrectOrder: "Top-level property
|
|
44
|
+
messages: { incorrectOrder: "Top-level property `{{property}}` is not ordered in the standard way." },
|
|
45
45
|
schema: [{
|
|
46
46
|
additionalProperties: false,
|
|
47
47
|
properties: { order: {
|
|
@@ -15,7 +15,7 @@ const rule = createRule({
|
|
|
15
15
|
"Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=contributors]"(node) {
|
|
16
16
|
contributorsPropertyNode = node;
|
|
17
17
|
const contributorsValue = node.value;
|
|
18
|
-
if (contributorsValue.type !== "JSONArrayExpression" ||
|
|
18
|
+
if (contributorsValue.type !== "JSONArrayExpression" || contributorsValue.elements.every((element) => !element)) context.report({
|
|
19
19
|
messageId: "noContributors",
|
|
20
20
|
node
|
|
21
21
|
});
|
|
@@ -7,7 +7,7 @@ const rule = createRule({
|
|
|
7
7
|
create(context) {
|
|
8
8
|
const blockedProperties = context.options[0]?.blockedProperties ?? defaultBlockedProperties;
|
|
9
9
|
return { "Program > JSONExpressionStatement > JSONObjectExpression"(node) {
|
|
10
|
-
if (
|
|
10
|
+
if (node.properties.every((property) => !(isJSONStringLiteral(property.key) && property.key.value === "private" && property.value.type === "JSONLiteral" && property.value.value === true))) return;
|
|
11
11
|
for (const property of node.properties) if (isJSONStringLiteral(property.key) && blockedProperties.includes(property.key.value)) {
|
|
12
12
|
const isEmpty = property.value.type === "JSONArrayExpression" && property.value.elements.length === 0 || property.value.type === "JSONObjectExpression" && property.value.properties.length === 0;
|
|
13
13
|
context.report({
|
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
import { PackageJsonRuleModule } from "../createRule.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/rules/sort-collections.d.ts
|
|
4
|
-
declare const rule: PackageJsonRuleModule<[(string
|
|
5
|
-
|
|
4
|
+
declare const rule: PackageJsonRuleModule<[((string | {
|
|
5
|
+
key: string;
|
|
6
|
+
order: string[];
|
|
7
|
+
})[] | undefined)?], [{
|
|
8
|
+
readonly description: "Array of package properties to require sorting. Provide a string to sort that collection lexicographically (lifecycle-aware for `scripts`), or an object to sort it by a specified order.";
|
|
6
9
|
readonly items: {
|
|
7
|
-
readonly
|
|
10
|
+
readonly anyOf: readonly [{
|
|
11
|
+
readonly type: "string";
|
|
12
|
+
}, {
|
|
13
|
+
readonly additionalProperties: false;
|
|
14
|
+
readonly properties: {
|
|
15
|
+
readonly key: {
|
|
16
|
+
readonly description: "The collection property to sort.";
|
|
17
|
+
readonly type: "string";
|
|
18
|
+
};
|
|
19
|
+
readonly order: {
|
|
20
|
+
readonly description: "The order to sort the collection by. Keys not listed are appended in lexicographical order.";
|
|
21
|
+
readonly items: {
|
|
22
|
+
readonly type: "string";
|
|
23
|
+
};
|
|
24
|
+
readonly type: "array";
|
|
25
|
+
readonly uniqueItems: true;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
readonly required: readonly ["key", "order"];
|
|
29
|
+
readonly type: "object";
|
|
30
|
+
}];
|
|
8
31
|
};
|
|
9
32
|
readonly type: "array";
|
|
10
33
|
}]>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createRule } from "../createRule.mjs";
|
|
2
|
+
import detectIndent from "detect-indent";
|
|
3
|
+
import { detectNewlineGraceful } from "detect-newline";
|
|
2
4
|
import sortPackageJson from "sort-package-json";
|
|
3
5
|
//#region src/rules/sort-collections.ts
|
|
4
6
|
const defaultCollections = new Set([
|
|
@@ -14,7 +16,7 @@ const defaultCollections = new Set([
|
|
|
14
16
|
]);
|
|
15
17
|
const rule = createRule({
|
|
16
18
|
create(context) {
|
|
17
|
-
const toSort = context.options[0]
|
|
19
|
+
const toSort = new Map((context.options[0] ?? Array.from(defaultCollections)).map((entry) => typeof entry === "string" ? [entry, null] : [entry.key, entry.order]));
|
|
18
20
|
return { "JSONProperty:exit"(node) {
|
|
19
21
|
const { key: nodeKey, value: collection } = node;
|
|
20
22
|
if (nodeKey.type !== "JSONLiteral" || collection.type !== "JSONObjectExpression") return;
|
|
@@ -22,27 +24,47 @@ const rule = createRule({
|
|
|
22
24
|
for (let currNode = node.parent; currNode; currNode = currNode.parent) if (currNode.type === "JSONProperty" && currNode.key.type === "JSONLiteral") keyPartsReversed.push(currNode.key.value);
|
|
23
25
|
else if (currNode.type === "JSONArrayExpression") return;
|
|
24
26
|
const key = keyPartsReversed.reverse().join(".");
|
|
25
|
-
|
|
27
|
+
const customOrder = toSort.get(key);
|
|
28
|
+
if (customOrder === void 0) return;
|
|
26
29
|
const currentOrder = collection.properties;
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
const isScripts = keyPartsReversed.at(-1) === "scripts";
|
|
31
|
+
let naturalCompare;
|
|
32
|
+
if (isScripts) {
|
|
29
33
|
const scriptsSource = context.sourceCode.getText(node);
|
|
30
34
|
const { scripts: sortedScripts } = sortPackageJson(JSON.parse(`{${scriptsSource}}`));
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
} else
|
|
34
|
-
|
|
35
|
+
const lifecycleIndex = new Map(Object.keys(sortedScripts).map((k, i) => [k, i]));
|
|
36
|
+
naturalCompare = (a, b) => (lifecycleIndex.get(a) ?? 0) - (lifecycleIndex.get(b) ?? 0);
|
|
37
|
+
} else naturalCompare = (a, b) => a > b ? 1 : -1;
|
|
38
|
+
const orderIndex = new Map((customOrder ?? []).map((k, i) => [k, i]));
|
|
39
|
+
const rank = (k) => orderIndex.get(k) ?? orderIndex.size;
|
|
40
|
+
const desiredOrder = currentOrder.toSorted((a, b) => {
|
|
41
|
+
const aKey = a.key.value;
|
|
42
|
+
const bKey = b.key.value;
|
|
43
|
+
const ai = rank(aKey);
|
|
44
|
+
const bi = rank(bKey);
|
|
45
|
+
if (ai !== bi) return ai - bi;
|
|
46
|
+
return naturalCompare(aKey, bKey);
|
|
35
47
|
});
|
|
36
48
|
if (currentOrder.some((property, i) => desiredOrder[i] !== property)) context.report({
|
|
37
49
|
data: { key },
|
|
38
50
|
fix(fixer) {
|
|
39
|
-
|
|
51
|
+
const { text } = context.sourceCode;
|
|
52
|
+
const { indent, type } = detectIndent(text);
|
|
53
|
+
const newline = detectNewlineGraceful(text);
|
|
54
|
+
const indentUnit = type === "tab" ? " " : indent || " ";
|
|
55
|
+
const jsonLines = JSON.stringify(desiredOrder.reduce((out, property) => {
|
|
40
56
|
out[property.key.value] = JSON.parse(context.sourceCode.getText(property.value));
|
|
41
57
|
return out;
|
|
42
|
-
}, {}), null,
|
|
58
|
+
}, {}), null, indentUnit).split("\n");
|
|
59
|
+
const collectionStartLine = collection.loc.start.line;
|
|
60
|
+
const lineText = context.sourceCode.lines[collectionStartLine - 1];
|
|
61
|
+
const leadingWhitespaceMatch = /^\s*/.exec(lineText);
|
|
62
|
+
const leadingWhitespace = leadingWhitespaceMatch ? leadingWhitespaceMatch[0] : "";
|
|
63
|
+
const result = jsonLines.map((l, i) => i === 0 ? l : leadingWhitespace + l).join(newline);
|
|
64
|
+
return fixer.replaceText(collection, result);
|
|
43
65
|
},
|
|
44
66
|
loc: collection.loc,
|
|
45
|
-
messageId:
|
|
67
|
+
messageId: customOrder ? "unsortedOrder" : isScripts ? "unsortedScripts" : "unsortedKeys",
|
|
46
68
|
node
|
|
47
69
|
});
|
|
48
70
|
} };
|
|
@@ -57,11 +79,28 @@ const rule = createRule({
|
|
|
57
79
|
fixable: "code",
|
|
58
80
|
messages: {
|
|
59
81
|
unsortedKeys: "Entries in '{{ key }}' are not in lexicographical order",
|
|
82
|
+
unsortedOrder: "Entries in '{{ key }}' are not in the specified order",
|
|
60
83
|
unsortedScripts: "Entries in 'scripts' are not in lexicographical order and grouped by lifecycles"
|
|
61
84
|
},
|
|
62
85
|
schema: [{
|
|
63
|
-
description: "Array of package properties to require sorting.",
|
|
64
|
-
items: { type: "string" },
|
|
86
|
+
description: "Array of package properties to require sorting. Provide a string to sort that collection lexicographically (lifecycle-aware for `scripts`), or an object to sort it by a specified order.",
|
|
87
|
+
items: { anyOf: [{ type: "string" }, {
|
|
88
|
+
additionalProperties: false,
|
|
89
|
+
properties: {
|
|
90
|
+
key: {
|
|
91
|
+
description: "The collection property to sort.",
|
|
92
|
+
type: "string"
|
|
93
|
+
},
|
|
94
|
+
order: {
|
|
95
|
+
description: "The order to sort the collection by. Keys not listed are appended in lexicographical order.",
|
|
96
|
+
items: { type: "string" },
|
|
97
|
+
type: "array",
|
|
98
|
+
uniqueItems: true
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
required: ["key", "order"],
|
|
102
|
+
type: "object"
|
|
103
|
+
}] },
|
|
65
104
|
type: "array"
|
|
66
105
|
}],
|
|
67
106
|
type: "layout"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRule } from "../createRule.mjs";
|
|
2
2
|
import { findPropertyWithKeyValue } from "../utils/findPropertyWithKeyValue.mjs";
|
|
3
|
-
import { findRootSync } from "@altano/repository-tools";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { sep } from "node:path/posix";
|
|
5
|
+
import { findRootSync } from "@altano/repository-tools";
|
|
6
6
|
//#region src/rules/valid-repository-directory.ts
|
|
7
7
|
/**
|
|
8
8
|
* Checks if the child path appears at the end of the parent path.
|
|
@@ -11,15 +11,16 @@ import { sep } from "node:path/posix";
|
|
|
11
11
|
* @example '/a/b/c', 'b' => false
|
|
12
12
|
* @example '/a/b/c', 'd' => false
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
const segments = parent.split(path.sep);
|
|
14
|
+
const pathEndsWith = (parent, child) => {
|
|
16
15
|
if (parent === child) return true;
|
|
16
|
+
const segments = parent.split(path.sep);
|
|
17
17
|
let pathToCheck = "";
|
|
18
18
|
return segments.reverse().some((segment) => {
|
|
19
19
|
pathToCheck = path.join(segment, pathToCheck);
|
|
20
20
|
if (pathToCheck === child) return true;
|
|
21
21
|
});
|
|
22
|
-
}
|
|
22
|
+
};
|
|
23
|
+
let repoRootCache = null;
|
|
23
24
|
const rule = createRule({
|
|
24
25
|
create(context) {
|
|
25
26
|
return { "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=repository][value.type=JSONObjectExpression]"(node) {
|
|
@@ -27,13 +28,13 @@ const rule = createRule({
|
|
|
27
28
|
if (directoryProperty?.value.type !== "JSONLiteral" || typeof directoryProperty.value.value !== "string") return;
|
|
28
29
|
const directoryValue = directoryProperty.value.value;
|
|
29
30
|
const fileDirectory = path.normalize(path.dirname(context.filename));
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
let repositoryRoot;
|
|
32
|
+
if (repoRootCache && fileDirectory.startsWith(repoRootCache)) repositoryRoot = repoRootCache;
|
|
33
|
+
else {
|
|
34
|
+
repositoryRoot = findRootSync(fileDirectory);
|
|
35
|
+
if (repositoryRoot) repoRootCache = path.normalize(repositoryRoot);
|
|
36
|
+
}
|
|
37
|
+
if (repositoryRoot) {
|
|
37
38
|
const expected = path.relative(repositoryRoot, fileDirectory).replaceAll(path.sep, sep);
|
|
38
39
|
if (expected !== directoryValue) context.report({
|
|
39
40
|
messageId: "mismatched",
|
|
@@ -46,7 +47,10 @@ const rule = createRule({
|
|
|
46
47
|
messageId: "replace"
|
|
47
48
|
}]
|
|
48
49
|
});
|
|
49
|
-
}
|
|
50
|
+
} else if (!pathEndsWith(fileDirectory, path.normalize(directoryValue))) context.report({
|
|
51
|
+
messageId: "mismatched",
|
|
52
|
+
node: directoryProperty.value
|
|
53
|
+
});
|
|
50
54
|
} };
|
|
51
55
|
},
|
|
52
56
|
meta: {
|
|
@@ -16,7 +16,7 @@ const createSimpleRequirePropertyRule = (propertyName, { category, fixValue, ign
|
|
|
16
16
|
const ignorePrivate = context.options[0]?.ignorePrivate ?? (typeof enforceForPrivate === "boolean" ? !enforceForPrivate : ignorePrivateDefault);
|
|
17
17
|
return { "Program > JSONExpressionStatement > JSONObjectExpression"(node) {
|
|
18
18
|
if (ignorePrivate && node.properties.some((property) => isJSONStringLiteral(property.key) && property.key.value === "private" && property.value.type === "JSONLiteral" && property.value.value === true)) return;
|
|
19
|
-
if (
|
|
19
|
+
if (node.properties.every((property) => !(isJSONStringLiteral(property.key) && property.key.value === propertyName))) context.report({
|
|
20
20
|
data: { property: propertyName },
|
|
21
21
|
fix: fixValue === void 0 ? void 0 : function* (fixer) {
|
|
22
22
|
yield fixer.insertTextAfterRange([0, 1], `\n "${propertyName}": ${JSON.stringify(fixValue)}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-package-json",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Rules for consistent, readable, and valid package.json files. 🗂️",
|
|
5
5
|
"homepage": "https://github.com/michaelfaith/eslint-plugin-package-json#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -53,50 +53,52 @@
|
|
|
53
53
|
"package-json-validator": "^1.5.0",
|
|
54
54
|
"semver": "^7.7.3",
|
|
55
55
|
"sort-object-keys": "^2.0.0",
|
|
56
|
-
"sort-package-json": "^
|
|
56
|
+
"sort-package-json": "^4.0.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@astrojs/
|
|
59
|
+
"@astrojs/check": "0.9.9",
|
|
60
|
+
"@astrojs/starlight": "0.40.0",
|
|
60
61
|
"@catppuccin/starlight": "2.0.1",
|
|
61
|
-
"@eslint-community/eslint-plugin-eslint-comments": "4.7.
|
|
62
|
+
"@eslint-community/eslint-plugin-eslint-comments": "4.7.2",
|
|
62
63
|
"@eslint/js": "10.0.1",
|
|
63
|
-
"@eslint/json": "
|
|
64
|
+
"@eslint/json": "2.0.0",
|
|
64
65
|
"@eslint/markdown": "8.0.1",
|
|
66
|
+
"@ianvs/prettier-plugin-sort-imports": "4.7.1",
|
|
65
67
|
"@types/estree": "1.0.8",
|
|
66
|
-
"@types/node": "24.
|
|
68
|
+
"@types/node": "24.13.0",
|
|
67
69
|
"@types/semver": "7.7.1",
|
|
68
|
-
"@vitest/coverage-v8": "4.1.
|
|
69
|
-
"@vitest/eslint-plugin": "1.6.
|
|
70
|
-
"astro": "6.
|
|
70
|
+
"@vitest/coverage-v8": "4.1.8",
|
|
71
|
+
"@vitest/eslint-plugin": "1.6.20",
|
|
72
|
+
"astro": "6.4.2",
|
|
71
73
|
"astro-og-canvas": "0.11.1",
|
|
72
74
|
"canvaskit-wasm": "0.41.1",
|
|
73
75
|
"console-fail-test": "0.6.0",
|
|
74
|
-
"eslint": "10.
|
|
76
|
+
"eslint": "10.5.0",
|
|
75
77
|
"eslint-doc-generator": "3.6.0",
|
|
76
|
-
"eslint-plugin-eslint-plugin": "7.
|
|
77
|
-
"eslint-plugin-jsdoc": "63.0.
|
|
78
|
-
"eslint-plugin-jsonc": "3.
|
|
79
|
-
"eslint-plugin-n": "18.
|
|
78
|
+
"eslint-plugin-eslint-plugin": "7.4.0",
|
|
79
|
+
"eslint-plugin-jsdoc": "63.0.2",
|
|
80
|
+
"eslint-plugin-jsonc": "3.2.0",
|
|
81
|
+
"eslint-plugin-n": "18.1.0",
|
|
80
82
|
"eslint-plugin-node-dependencies": "2.2.0",
|
|
81
83
|
"eslint-plugin-perfectionist": "5.9.0",
|
|
82
84
|
"eslint-plugin-regexp": "3.1.0",
|
|
83
|
-
"eslint-plugin-unicorn": "
|
|
84
|
-
"eslint-plugin-yml": "3.
|
|
85
|
+
"eslint-plugin-unicorn": "66.0.0",
|
|
86
|
+
"eslint-plugin-yml": "3.4.0",
|
|
85
87
|
"jiti": "2.7.0",
|
|
86
88
|
"json-schema-to-ts": "3.1.1",
|
|
87
|
-
"knip": "6.
|
|
88
|
-
"prettier": "3.8.
|
|
89
|
-
"prettier-plugin-curly": "0.4.
|
|
90
|
-
"prettier-plugin-packagejson": "3.0.
|
|
91
|
-
"prettier-plugin-sh": "0.18.
|
|
89
|
+
"knip": "6.16.0",
|
|
90
|
+
"prettier": "3.8.4",
|
|
91
|
+
"prettier-plugin-curly": "0.4.1",
|
|
92
|
+
"prettier-plugin-packagejson": "3.0.2",
|
|
93
|
+
"prettier-plugin-sh": "0.18.1",
|
|
92
94
|
"pretty-quick": "4.2.2",
|
|
93
|
-
"sharp": "0.
|
|
95
|
+
"sharp": "0.35.0",
|
|
94
96
|
"simple-git-hooks": "2.13.1",
|
|
95
97
|
"starlight-auto-sidebar": "0.4.0",
|
|
96
|
-
"tsdown": "0.22.
|
|
97
|
-
"typescript": "6.0.
|
|
98
|
-
"typescript-eslint": "8.
|
|
99
|
-
"vitest": "4.1.
|
|
98
|
+
"tsdown": "0.22.2",
|
|
99
|
+
"typescript": "6.0.3",
|
|
100
|
+
"typescript-eslint": "8.61.0",
|
|
101
|
+
"vitest": "4.1.8"
|
|
100
102
|
},
|
|
101
103
|
"peerDependencies": {
|
|
102
104
|
"@eslint/json": ">=1.0.0",
|
|
@@ -117,10 +119,10 @@
|
|
|
117
119
|
"lint": "eslint . --max-warnings 0",
|
|
118
120
|
"lint:knip": "knip",
|
|
119
121
|
"site:build": "astro build",
|
|
122
|
+
"site:check": "astro check --root site",
|
|
120
123
|
"site:dev": "astro dev",
|
|
121
124
|
"site:preview": "astro preview",
|
|
122
125
|
"site:sync": "astro sync",
|
|
123
|
-
"site:typecheck": "pnpm site:sync && tsc --project site/tsconfig.json",
|
|
124
126
|
"test": "vitest",
|
|
125
127
|
"typecheck": "tsc"
|
|
126
128
|
}
|