eslint-plugin-package-json 1.3.0 → 1.5.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 +3 -3
- package/lib/createRule.d.mts +9 -9
- package/lib/createRule.mjs +1 -1
- package/lib/plugin.mjs +6 -4
- package/lib/rules/exports-subpaths-style.mjs +1 -1
- package/lib/rules/no-local-dependencies.mjs +1 -1
- package/lib/rules/no-redundant-files.mjs +10 -10
- package/lib/rules/no-redundant-publishConfig.mjs +1 -1
- package/lib/rules/order-properties.mjs +2 -2
- package/lib/rules/repository-shorthand.mjs +1 -1
- package/lib/rules/require-attribution.mjs +1 -1
- package/lib/rules/require-properties.mjs +2 -1
- package/lib/rules/restrict-dependency-ranges.mjs +2 -2
- package/lib/rules/restrict-private-properties.mjs +2 -2
- package/lib/rules/restrict-top-level-properties.mjs +1 -1
- package/lib/rules/sort-collections.d.mts +26 -3
- package/lib/rules/sort-collections.mjs +52 -13
- package/lib/rules/specify-peers-locally.mjs +1 -1
- package/lib/rules/unique-dependencies.mjs +2 -1
- package/lib/rules/valid-author.d.mts +6 -0
- package/lib/rules/valid-author.mjs +59 -0
- package/lib/rules/valid-peerDependenciesMeta-relationship.mjs +1 -1
- package/lib/rules/valid-properties.mjs +1 -2
- package/lib/rules/valid-repository-directory.mjs +1 -1
- package/lib/utils/createSimpleRequirePropertyRule.mjs +16 -13
- package/lib/utils/git/getGitAuthor.d.mts +8 -0
- package/lib/utils/git/getGitAuthor.mjs +29 -0
- package/lib/utils/git/index.d.mts +2 -0
- package/lib/utils/git/index.mjs +2 -0
- package/lib/utils/predicates/index.d.mts +4 -0
- package/lib/utils/predicates/index.mjs +4 -0
- package/lib/utils/predicates/isJSONStringLiteral.d.mts +6 -0
- package/lib/utils/predicates/isJSONStringLiteral.mjs +6 -0
- package/lib/utils/predicates/isNotNullish.d.mts +4 -0
- package/lib/utils/predicates/isNotNullish.mjs +6 -0
- package/lib/utils/predicates/isPackageJson.d.mts +4 -0
- package/lib/utils/{isPackageJson.mjs → predicates/isPackageJson.mjs} +1 -1
- package/package.json +24 -23
- package/lib/utils/isPackageJson.d.mts +0 -4
- package/lib/utils/predicates.d.mts +0 -7
- package/lib/utils/predicates.mjs +0 -9
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ See [Getting Started](https://eslint-plugin-package-json.dev/getting-started) fo
|
|
|
46
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. | | | |
|
|
47
47
|
| [no-redundant-files](https://eslint-plugin-package-json.dev/rules/no-redundant-files) | Prevents adding unnecessary / redundant files. | ✅ 📦 | | 💡 |
|
|
48
48
|
| [no-redundant-publishConfig](https://eslint-plugin-package-json.dev/rules/no-redundant-publishConfig) | Warns when publishConfig.access is used in unscoped packages. | ✅ 📦 | | 💡 |
|
|
49
|
-
| [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. | 🎨 | 🔧 | |
|
|
50
50
|
| [repository-shorthand](https://eslint-plugin-package-json.dev/rules/repository-shorthand) | Enforce either object or shorthand declaration for repository. | ✅ 📦 | 🔧 | |
|
|
51
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. | ✅ 📦 | | 💡 |
|
|
52
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. | | | 💡 |
|
|
@@ -65,7 +65,7 @@ This group of rules allows you to require that the associated top-level property
|
|
|
65
65
|
|
|
66
66
|
| Name | Description | 💼 | 🔧 | 💡 |
|
|
67
67
|
| :--------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------- | :---- | :-- | :-- |
|
|
68
|
-
| [require-author](https://eslint-plugin-package-json.dev/rules/require-properties/require-author) | Requires the `author` property to be present. | |
|
|
68
|
+
| [require-author](https://eslint-plugin-package-json.dev/rules/require-properties/require-author) | Requires the `author` property to be present. | | 🔧 | |
|
|
69
69
|
| [require-bin](https://eslint-plugin-package-json.dev/rules/require-properties/require-bin) | Requires the `bin` property to be present. | | | |
|
|
70
70
|
| [require-browser](https://eslint-plugin-package-json.dev/rules/require-properties/require-browser) | Requires the `browser` property to be present. | | | |
|
|
71
71
|
| [require-bugs](https://eslint-plugin-package-json.dev/rules/require-properties/require-bugs) | Requires the `bugs` property to be present. | | | |
|
|
@@ -111,7 +111,7 @@ This group of rules allows you to enforce that the value of the associated top-l
|
|
|
111
111
|
|
|
112
112
|
| Name | Description | 💼 | 🔧 | 💡 |
|
|
113
113
|
| :--------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | :---- | :-- | :-- |
|
|
114
|
-
| [valid-author](https://eslint-plugin-package-json.dev/rules/valid-properties/valid-author) | Enforce that the `author` property is valid. | ✅ 📦 |
|
|
114
|
+
| [valid-author](https://eslint-plugin-package-json.dev/rules/valid-properties/valid-author) | Enforce that the `author` property is valid. | ✅ 📦 | 🔧 | |
|
|
115
115
|
| [valid-bin](https://eslint-plugin-package-json.dev/rules/valid-properties/valid-bin) | Enforce that the `bin` property is valid. | ✅ 📦 | | |
|
|
116
116
|
| [valid-browser](https://eslint-plugin-package-json.dev/rules/valid-properties/valid-browser) | Enforce that the `browser` property is valid. | ✅ 📦 | | |
|
|
117
117
|
| [valid-bugs](https://eslint-plugin-package-json.dev/rules/valid-properties/valid-bugs) | Enforce that the `bugs` property is valid. | ✅ 📦 | | |
|
package/lib/createRule.d.mts
CHANGED
|
@@ -5,17 +5,17 @@ import { AST, RuleListener } from "jsonc-eslint-parser";
|
|
|
5
5
|
import { AST as AST$1, Rule, SourceCode } from "eslint";
|
|
6
6
|
|
|
7
7
|
//#region src/createRule.d.ts
|
|
8
|
-
type
|
|
9
|
-
properties:
|
|
8
|
+
type ASTBodyExpression = Expression & {
|
|
9
|
+
properties: ASTBodyProperty[];
|
|
10
10
|
};
|
|
11
|
-
type
|
|
11
|
+
type ASTBodyProperty = AST.JSONProperty & {
|
|
12
12
|
value: string;
|
|
13
13
|
};
|
|
14
|
-
interface
|
|
15
|
-
expression:
|
|
14
|
+
interface ASTBodyStatement extends ExpressionStatement {
|
|
15
|
+
expression: ASTBodyExpression;
|
|
16
16
|
}
|
|
17
|
-
interface
|
|
18
|
-
body: [
|
|
17
|
+
interface PackageAST extends AST$1.Program {
|
|
18
|
+
body: [ASTBodyStatement];
|
|
19
19
|
}
|
|
20
20
|
interface PackageJsonPluginSettings {
|
|
21
21
|
/**
|
|
@@ -45,7 +45,7 @@ interface PackageJsonRuleModule<Options extends unknown[] = unknown[], Schema ex
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
interface PackageJsonSourceCode extends SourceCode {
|
|
48
|
-
ast:
|
|
48
|
+
ast: PackageAST;
|
|
49
49
|
}
|
|
50
50
|
type InferJsonSchemasTupleType<T extends JSONSchema[]> = { [K in keyof T]?: FromSchema<T[K]> };
|
|
51
51
|
/**
|
|
@@ -56,4 +56,4 @@ declare function createRule<OptionsOverride extends unknown[] = never, const Sch
|
|
|
56
56
|
name: string;
|
|
57
57
|
}): PackageJsonRuleModule<_OptionsResolved, Schema>;
|
|
58
58
|
//#endregion
|
|
59
|
-
export {
|
|
59
|
+
export { ASTBodyExpression, ASTBodyProperty, ASTBodyStatement, PackageAST, PackageJsonPluginSettings, PackageJsonRuleContext, PackageJsonRuleModule, PackageJsonSourceCode, createRule };
|
package/lib/createRule.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isPackageJson } from "./utils/isPackageJson.mjs";
|
|
1
|
+
import { isPackageJson } from "./utils/predicates/isPackageJson.mjs";
|
|
2
2
|
//#region src/createRule.ts
|
|
3
3
|
/**
|
|
4
4
|
* Rule options type is inferred from the JSON schema by [json-schema-to-ts](https://www.npmjs.com/package/json-schema-to-ts).
|
package/lib/plugin.mjs
CHANGED
|
@@ -15,9 +15,10 @@ import { rule as rule$12 } from "./rules/scripts-name-casing.mjs";
|
|
|
15
15
|
import { rule as rule$13 } from "./rules/sort-collections.mjs";
|
|
16
16
|
import { rule as rule$14 } from "./rules/specify-peers-locally.mjs";
|
|
17
17
|
import { rule as rule$15 } from "./rules/unique-dependencies.mjs";
|
|
18
|
-
import { rule as rule$16 } from "./rules/valid-
|
|
18
|
+
import { rule as rule$16 } from "./rules/valid-author.mjs";
|
|
19
|
+
import { rule as rule$17 } from "./rules/valid-peerDependenciesMeta-relationship.mjs";
|
|
19
20
|
import { rules as rules$2 } from "./rules/valid-properties.mjs";
|
|
20
|
-
import { rule as rule$
|
|
21
|
+
import { rule as rule$18 } from "./rules/valid-repository-directory.mjs";
|
|
21
22
|
import { createRequire } from "node:module";
|
|
22
23
|
import * as parserJsonc from "jsonc-eslint-parser";
|
|
23
24
|
//#region src/plugin.ts
|
|
@@ -40,9 +41,10 @@ const rules = {
|
|
|
40
41
|
"sort-collections": rule$13,
|
|
41
42
|
"specify-peers-locally": rule$14,
|
|
42
43
|
"unique-dependencies": rule$15,
|
|
44
|
+
"valid-author": rule$16,
|
|
43
45
|
...rules$2,
|
|
44
|
-
"valid-peerDependenciesMeta-relationship": rule$
|
|
45
|
-
"valid-repository-directory": rule$
|
|
46
|
+
"valid-peerDependenciesMeta-relationship": rule$17,
|
|
47
|
+
"valid-repository-directory": rule$18
|
|
46
48
|
};
|
|
47
49
|
const recommendedRules = { ...Object.fromEntries(Object.entries(rules).filter(([, rule]) => rule.meta.docs?.recommended).map(([name]) => ["package-json/" + name, "error"])) };
|
|
48
50
|
const recommendedPublishableRules = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
3
|
//#region src/rules/exports-subpaths-style.ts
|
|
4
4
|
function isImplicitFormat(node) {
|
|
5
5
|
if (node.type === "JSONLiteral") return true;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
3
|
//#region src/rules/no-local-dependencies.ts
|
|
4
4
|
const isLocalDependency = (value) => value.startsWith("file:") || value.startsWith("link:") || value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
|
|
5
5
|
const rule = createRule({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
2
|
+
import { isNotNullish } from "../utils/predicates/isNotNullish.mjs";
|
|
1
3
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral, isNotNullish } from "../utils/predicates.mjs";
|
|
3
4
|
import { fixRemoveArrayElement } from "eslint-fix-utils";
|
|
4
5
|
//#region src/rules/no-redundant-files.ts
|
|
5
6
|
const defaultFiles = [
|
|
@@ -47,15 +48,14 @@ const rule = createRule({
|
|
|
47
48
|
}
|
|
48
49
|
},
|
|
49
50
|
"Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=files]"(node) {
|
|
50
|
-
if (node.value.type
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
51
|
+
if (node.value.type !== "JSONArrayExpression") return;
|
|
52
|
+
const seen = /* @__PURE__ */ new Set();
|
|
53
|
+
const elements = node.value.elements;
|
|
54
|
+
entryCache.files = elements;
|
|
55
|
+
for (const [index, element] of elements.entries()) if (isNotNullish(element) && isJSONStringLiteral(element)) {
|
|
56
|
+
if (seen.has(element.value)) report(elements, index, "duplicate");
|
|
57
|
+
else seen.add(element.value);
|
|
58
|
+
for (const defaultFile of defaultFiles) if (defaultFile.test(element.value)) report(elements, index, "unnecessaryDefault");
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
"Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=main]"(node) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
3
|
import { fixRemoveObjectProperty } from "eslint-fix-utils";
|
|
4
4
|
//#region src/rules/no-redundant-publishConfig.ts
|
|
5
5
|
const rule = createRule({
|
|
@@ -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: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
3
|
import { findPropertyWithKeyValue } from "../utils/findPropertyWithKeyValue.mjs";
|
|
4
4
|
//#region src/rules/repository-shorthand.ts
|
|
5
5
|
const providerRegexes = {
|
|
@@ -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
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createSimpleRequirePropertyRule } from "../utils/createSimpleRequirePropertyRule.mjs";
|
|
2
|
+
import { getGitAuthor } from "../utils/git/getGitAuthor.mjs";
|
|
2
3
|
//#region src/rules/require-properties.ts
|
|
3
4
|
const propertyConfig = [
|
|
4
|
-
["author"],
|
|
5
|
+
["author", { fixValue: getGitAuthor }],
|
|
5
6
|
["bin"],
|
|
6
7
|
["browser"],
|
|
7
8
|
["bugs", { ignorePrivateDefault: true }],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
3
|
import semver from "semver";
|
|
4
4
|
//#region src/rules/restrict-dependency-ranges.ts
|
|
5
5
|
const DEPENDENCY_TYPES = [
|
|
@@ -102,7 +102,7 @@ const rule = createRule({
|
|
|
102
102
|
});
|
|
103
103
|
break;
|
|
104
104
|
}
|
|
105
|
-
if (!rangeTypes.
|
|
105
|
+
if (!rangeTypes.some((rangeType) => {
|
|
106
106
|
switch (rangeType) {
|
|
107
107
|
case "caret": return isCaretRange;
|
|
108
108
|
case "pin": return isPinned;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
3
|
import { fixRemoveObjectProperty } from "eslint-fix-utils";
|
|
4
4
|
//#region src/rules/restrict-private-properties.ts
|
|
5
5
|
const defaultBlockedProperties = ["files", "publishConfig"];
|
|
@@ -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,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
3
|
import { fixRemoveObjectProperty } from "eslint-fix-utils";
|
|
4
4
|
//#region src/rules/restrict-top-level-properties.ts
|
|
5
5
|
const rule = createRule({
|
|
@@ -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
|
+
order: string[];
|
|
6
|
+
key: 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,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
3
|
//#region src/rules/specify-peers-locally.ts
|
|
4
4
|
const rule = createRule({
|
|
5
5
|
create(context) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
2
|
+
import { isNotNullish } from "../utils/predicates/isNotNullish.mjs";
|
|
1
3
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral, isNotNullish } from "../utils/predicates.mjs";
|
|
3
4
|
import { fixRemoveArrayElement, fixRemoveObjectProperty } from "eslint-fix-utils";
|
|
4
5
|
//#region src/rules/unique-dependencies.ts
|
|
5
6
|
const dependencyPropertyNames = new Set([
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
2
|
+
import { createRule } from "../createRule.mjs";
|
|
3
|
+
import { getGitAuthor } from "../utils/git/getGitAuthor.mjs";
|
|
4
|
+
import { validateAuthor } from "package-json-validator";
|
|
5
|
+
//#region src/rules/valid-author.ts
|
|
6
|
+
const rule = createRule({
|
|
7
|
+
create(context) {
|
|
8
|
+
const createFixer = (propertyName, node) => {
|
|
9
|
+
const author = getGitAuthor();
|
|
10
|
+
if (!author) return;
|
|
11
|
+
let mergedAuthor = author;
|
|
12
|
+
const originalValue = JSON.parse(context.sourceCode.getText(node));
|
|
13
|
+
if (originalValue && typeof originalValue === "object" && !Array.isArray(originalValue)) mergedAuthor = {
|
|
14
|
+
name: author.name,
|
|
15
|
+
...originalValue
|
|
16
|
+
};
|
|
17
|
+
switch (propertyName) {
|
|
18
|
+
case "author": return (fixer) => fixer.replaceText(node, JSON.stringify(mergedAuthor, null, 2).split("\n").join("\n "));
|
|
19
|
+
case "email": return (fixer) => fixer.replaceText(node, JSON.stringify(author.email));
|
|
20
|
+
case "name": return (fixer) => fixer.replaceText(node, JSON.stringify(author.name));
|
|
21
|
+
default: return;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const reportIssues = (result, node, propertyName) => {
|
|
25
|
+
if (result.errorMessages.length === 0) return;
|
|
26
|
+
if (result.issues.length) for (const issue of result.issues) context.report({
|
|
27
|
+
data: { error: issue.message },
|
|
28
|
+
fix: propertyName === void 0 ? void 0 : createFixer(propertyName, node),
|
|
29
|
+
messageId: "validationError",
|
|
30
|
+
node
|
|
31
|
+
});
|
|
32
|
+
const childrenWithIssues = result.childResults.filter((childResult) => childResult.errorMessages.length);
|
|
33
|
+
if (node.type === "JSONObjectExpression" && childrenWithIssues.length) for (const childResult of childrenWithIssues) {
|
|
34
|
+
const childNode = node.properties[childResult.index];
|
|
35
|
+
const childPropertyName = childNode.key;
|
|
36
|
+
reportIssues(childResult, childNode.value, isJSONStringLiteral(childPropertyName) ? childPropertyName.value : void 0);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return { "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=author]"(node) {
|
|
40
|
+
const valueNode = node.value;
|
|
41
|
+
reportIssues(validateAuthor(JSON.parse(context.sourceCode.getText(valueNode))), valueNode, "author");
|
|
42
|
+
} };
|
|
43
|
+
},
|
|
44
|
+
meta: {
|
|
45
|
+
docs: {
|
|
46
|
+
category: "Best Practices",
|
|
47
|
+
description: "Enforce that the `author` property is valid.",
|
|
48
|
+
recommended: true,
|
|
49
|
+
ruleGroup: "valid-properties"
|
|
50
|
+
},
|
|
51
|
+
fixable: "code",
|
|
52
|
+
messages: { validationError: `Invalid author: {{ error }}` },
|
|
53
|
+
schema: [],
|
|
54
|
+
type: "problem"
|
|
55
|
+
},
|
|
56
|
+
name: "valid-author"
|
|
57
|
+
});
|
|
58
|
+
//#endregion
|
|
59
|
+
export { rule };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "../utils/predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "../utils/predicates.mjs";
|
|
3
3
|
import { fixRemoveObjectProperty } from "eslint-fix-utils";
|
|
4
4
|
//#region src/rules/valid-peerDependenciesMeta-relationship.ts
|
|
5
5
|
const rule = createRule({
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createSimpleValidPropertyRule } from "../utils/createSimpleValidPropertyRule.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { validateBin, validateBrowser, validateBugs, validateBundleDependencies, validateConfig, validateContributors, validateCpu, validateDependencies, validateDescription, validateDevEngines, validateDirectories, validateEngines, validateExports, validateFiles, validateFunding, validateGypfile, validateHomepage, validateKeywords, validateLibc, validateLicense, validateMain, validateMan, validateName, validateOs, validatePackageManager, validatePeerDependenciesMeta, validatePrivate, validatePublishConfig, validateRepository, validateScripts, validateSideEffects, validateType, validateVersion, validateWorkspaces } from "package-json-validator";
|
|
3
3
|
/** All basic valid- flavor rules */
|
|
4
4
|
const rules = Object.fromEntries([
|
|
5
|
-
["author", validateAuthor],
|
|
6
5
|
["bin", validateBin],
|
|
7
6
|
["browser", validateBrowser],
|
|
8
7
|
["bugs", validateBugs],
|
|
@@ -12,8 +12,8 @@ import { findRootSync } from "@altano/repository-tools";
|
|
|
12
12
|
* @example '/a/b/c', 'd' => false
|
|
13
13
|
*/
|
|
14
14
|
const pathEndsWith = (parent, child) => {
|
|
15
|
-
const segments = parent.split(path.sep);
|
|
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);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isJSONStringLiteral } from "./predicates/isJSONStringLiteral.mjs";
|
|
1
2
|
import { createRule } from "../createRule.mjs";
|
|
2
|
-
import { isJSONStringLiteral } from "./predicates.mjs";
|
|
3
3
|
//#region src/utils/createSimpleRequirePropertyRule.ts
|
|
4
4
|
/**
|
|
5
5
|
* Given a top-level property name, create a rule that requires that property to be present.
|
|
@@ -16,18 +16,21 @@ 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 (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
if (node.properties.every((property) => !(isJSONStringLiteral(property.key) && property.key.value === propertyName))) {
|
|
20
|
+
const resolvedValue = typeof fixValue === "function" ? fixValue() : fixValue;
|
|
21
|
+
context.report({
|
|
22
|
+
data: { property: propertyName },
|
|
23
|
+
fix: resolvedValue === void 0 ? void 0 : function* (fixer) {
|
|
24
|
+
yield fixer.insertTextAfterRange([0, 1], `\n "${propertyName}": ${JSON.stringify(resolvedValue, null, 2).split("\n").join("\n ")}`);
|
|
25
|
+
yield node.properties.length > 0 ? fixer.insertTextAfterRange([0, 1], ",") : fixer.insertTextAfterRange([0, 1], "\n");
|
|
26
|
+
},
|
|
27
|
+
loc: {
|
|
28
|
+
column: 0,
|
|
29
|
+
line: 1
|
|
30
|
+
},
|
|
31
|
+
messageId: "missing"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
31
34
|
} };
|
|
32
35
|
},
|
|
33
36
|
meta: {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
//#region src/utils/git/getGitAuthor.ts
|
|
3
|
+
let cachedAuthor;
|
|
4
|
+
const getGitAuthor = () => {
|
|
5
|
+
if (cachedAuthor === null) return;
|
|
6
|
+
else if (cachedAuthor !== void 0) return cachedAuthor;
|
|
7
|
+
try {
|
|
8
|
+
const name = execFileSync("git", [
|
|
9
|
+
"config",
|
|
10
|
+
"--get",
|
|
11
|
+
"user.name"
|
|
12
|
+
], { encoding: "utf8" }).trim();
|
|
13
|
+
const email = execFileSync("git", [
|
|
14
|
+
"config",
|
|
15
|
+
"--get",
|
|
16
|
+
"user.email"
|
|
17
|
+
], { encoding: "utf8" }).trim();
|
|
18
|
+
cachedAuthor = {
|
|
19
|
+
name,
|
|
20
|
+
...email && { email }
|
|
21
|
+
};
|
|
22
|
+
} catch {
|
|
23
|
+
cachedAuthor = null;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
return cachedAuthor;
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { getGitAuthor };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-package-json",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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,51 +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
64
|
"@eslint/json": "2.0.0",
|
|
64
65
|
"@eslint/markdown": "8.0.1",
|
|
65
66
|
"@ianvs/prettier-plugin-sort-imports": "4.7.1",
|
|
66
67
|
"@types/estree": "1.0.8",
|
|
67
|
-
"@types/node": "24.
|
|
68
|
+
"@types/node": "24.13.0",
|
|
68
69
|
"@types/semver": "7.7.1",
|
|
69
|
-
"@vitest/coverage-v8": "4.1.
|
|
70
|
-
"@vitest/eslint-plugin": "1.6.
|
|
70
|
+
"@vitest/coverage-v8": "4.1.8",
|
|
71
|
+
"@vitest/eslint-plugin": "1.6.20",
|
|
71
72
|
"astro": "6.4.2",
|
|
72
73
|
"astro-og-canvas": "0.11.1",
|
|
73
74
|
"canvaskit-wasm": "0.41.1",
|
|
74
75
|
"console-fail-test": "0.6.0",
|
|
75
|
-
"eslint": "10.
|
|
76
|
+
"eslint": "10.5.0",
|
|
76
77
|
"eslint-doc-generator": "3.6.0",
|
|
77
|
-
"eslint-plugin-eslint-plugin": "7.
|
|
78
|
-
"eslint-plugin-jsdoc": "63.0.
|
|
78
|
+
"eslint-plugin-eslint-plugin": "7.4.0",
|
|
79
|
+
"eslint-plugin-jsdoc": "63.0.2",
|
|
79
80
|
"eslint-plugin-jsonc": "3.2.0",
|
|
80
|
-
"eslint-plugin-n": "18.
|
|
81
|
+
"eslint-plugin-n": "18.1.0",
|
|
81
82
|
"eslint-plugin-node-dependencies": "2.2.0",
|
|
82
83
|
"eslint-plugin-perfectionist": "5.9.0",
|
|
83
84
|
"eslint-plugin-regexp": "3.1.0",
|
|
84
|
-
"eslint-plugin-unicorn": "
|
|
85
|
+
"eslint-plugin-unicorn": "67.0.0",
|
|
85
86
|
"eslint-plugin-yml": "3.4.0",
|
|
86
87
|
"jiti": "2.7.0",
|
|
87
88
|
"json-schema-to-ts": "3.1.1",
|
|
88
|
-
"knip": "6.
|
|
89
|
-
"prettier": "3.8.
|
|
90
|
-
"prettier-plugin-curly": "0.4.
|
|
91
|
-
"prettier-plugin-packagejson": "3.0.
|
|
92
|
-
"prettier-plugin-sh": "0.18.
|
|
89
|
+
"knip": "6.17.1",
|
|
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",
|
|
93
94
|
"pretty-quick": "4.2.2",
|
|
94
|
-
"sharp": "0.
|
|
95
|
+
"sharp": "0.35.0",
|
|
95
96
|
"simple-git-hooks": "2.13.1",
|
|
96
97
|
"starlight-auto-sidebar": "0.4.0",
|
|
97
|
-
"tsdown": "0.22.
|
|
98
|
-
"typescript": "6.0.
|
|
99
|
-
"typescript-eslint": "8.
|
|
100
|
-
"vitest": "4.1.
|
|
98
|
+
"tsdown": "0.22.2",
|
|
99
|
+
"typescript": "6.0.3",
|
|
100
|
+
"typescript-eslint": "8.61.0",
|
|
101
|
+
"vitest": "4.1.8"
|
|
101
102
|
},
|
|
102
103
|
"peerDependencies": {
|
|
103
104
|
"@eslint/json": ">=1.0.0",
|
|
@@ -118,10 +119,10 @@
|
|
|
118
119
|
"lint": "eslint . --max-warnings 0",
|
|
119
120
|
"lint:knip": "knip",
|
|
120
121
|
"site:build": "astro build",
|
|
122
|
+
"site:check": "astro check --root site",
|
|
121
123
|
"site:dev": "astro dev",
|
|
122
124
|
"site:preview": "astro preview",
|
|
123
125
|
"site:sync": "astro sync",
|
|
124
|
-
"site:typecheck": "pnpm site:sync && tsc --project site/tsconfig.json",
|
|
125
126
|
"test": "vitest",
|
|
126
127
|
"typecheck": "tsc"
|
|
127
128
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { AST } from "jsonc-eslint-parser";
|
|
2
|
-
|
|
3
|
-
//#region src/utils/predicates.d.ts
|
|
4
|
-
declare function isJSONStringLiteral(node: AST.JSONNode): node is AST.JSONStringLiteral;
|
|
5
|
-
declare function isNotNullish<T extends NonNullable<unknown>>(value: null | T | undefined): value is T;
|
|
6
|
-
//#endregion
|
|
7
|
-
export { isJSONStringLiteral, isNotNullish };
|
package/lib/utils/predicates.mjs
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
//#region src/utils/predicates.ts
|
|
2
|
-
function isJSONStringLiteral(node) {
|
|
3
|
-
return node.type === "JSONLiteral" && typeof node.value === "string";
|
|
4
|
-
}
|
|
5
|
-
function isNotNullish(value) {
|
|
6
|
-
return value !== null && value !== void 0;
|
|
7
|
-
}
|
|
8
|
-
//#endregion
|
|
9
|
-
export { isJSONStringLiteral, isNotNullish };
|