eslint-plugin-package-json 0.62.0 → 0.64.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/CHANGELOG.md CHANGED
@@ -1,11 +1,23 @@
1
1
  # Changelog
2
2
 
3
- # [0.62.0](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/compare/v0.61.0...v0.62.0) (2025-11-03)
3
+ # [0.64.0](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/compare/v0.63.0...v0.64.0) (2025-11-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * **bin-name-casing:** add new rule ([#1343](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/issues/1343)) ([0e10e87](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/commit/0e10e875eb4cc5f680e154e6ea2333e5902d4c4c)), closes [#1346](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/issues/1346)
4
9
 
10
+ # [0.63.0](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/compare/v0.62.0...v0.63.0) (2025-11-04)
11
+
12
+ ### Features
13
+
14
+ - **restrict-private-properties:** add new rule ([#1336](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/issues/1336)) ([e1225cf](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/commit/e1225cf991be2f3829cdddcae8b06cef8196f070)), closes [#1323](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/issues/1323)
15
+
16
+ # [0.62.0](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/compare/v0.61.0...v0.62.0) (2025-11-03)
5
17
 
6
18
  ### Features
7
19
 
8
- * **scripts-name-casing:** add new rule ([#1344](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/issues/1344)) ([e735595](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/commit/e735595537e6857c771eac12a1efcd55cb2d3564)), closes [#61](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/issues/61)
20
+ - **scripts-name-casing:** add new rule ([#1344](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/issues/1344)) ([e735595](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/commit/e735595537e6857c771eac12a1efcd55cb2d3564)), closes [#61](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/issues/61)
9
21
 
10
22
  # [0.61.0](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/compare/v0.60.0...v0.61.0) (2025-11-03)
11
23
 
package/README.md CHANGED
@@ -183,6 +183,7 @@ The default settings don't conflict, and Prettier plugins can quickly fix up ord
183
183
 
184
184
  | Name                         | Description | 💼 | 🔧 | 💡 | ❌ |
185
185
  | :------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | :--- | :- | :- | :- |
186
+ | [bin-name-casing](docs/rules/bin-name-casing.md) | Enforce that names for bin properties are in kebab case. | 🎨 | | 💡 | |
186
187
  | [exports-subpaths-style](docs/rules/exports-subpaths-style.md) | Enforce consistent format for the exports field (implicit or explicit subpaths). | 🎨 | 🔧 | | |
187
188
  | [no-empty-fields](docs/rules/no-empty-fields.md) | Reports on unnecessary empty arrays and objects. | ✔️ ✅ | | 💡 | |
188
189
  | [no-redundant-files](docs/rules/no-redundant-files.md) | Prevents adding unnecessary / redundant files. | ✔️ ✅ | | 💡 | |
@@ -205,11 +206,12 @@ The default settings don't conflict, and Prettier plugins can quickly fix up ord
205
206
  | [require-types](docs/rules/require-types.md) | Requires the `types` property to be present. | | | | |
206
207
  | [require-version](docs/rules/require-version.md) | Requires the `version` property to be present. | ✔️ ✅ | | | |
207
208
  | [restrict-dependency-ranges](docs/rules/restrict-dependency-ranges.md) | Restricts the range of dependencies to allow or disallow specific types of ranges. | | | 💡 | |
209
+ | [restrict-private-properties](docs/rules/restrict-private-properties.md) | Disallows unnecessary properties in private packages. | | 🔧 | 💡 | |
208
210
  | [scripts-name-casing](docs/rules/scripts-name-casing.md) | Enforce that names for `scripts` are in kebab case (optionally separated by colons). | 🎨 | | 💡 | |
209
211
  | [sort-collections](docs/rules/sort-collections.md) | Selected collections must be in a consistent order (lexicographical for most; lifecycle-aware for scripts). | ✔️ ✅ | 🔧 | | |
210
212
  | [unique-dependencies](docs/rules/unique-dependencies.md) | Checks a dependency isn't specified more than once (i.e. in `dependencies` and `devDependencies`) | ✔️ ✅ | | 💡 | |
211
213
  | [valid-author](docs/rules/valid-author.md) | Enforce that the `author` property is valid. | ✔️ ✅ | | | |
212
- | [valid-bin](docs/rules/valid-bin.md) | Enforce that the `bin` property is valid. | ✔️ ✅ | | 💡 | |
214
+ | [valid-bin](docs/rules/valid-bin.md) | Enforce that the `bin` property is valid. | ✔️ ✅ | | | |
213
215
  | [valid-bundleDependencies](docs/rules/valid-bundleDependencies.md) | Enforce that the `bundleDependencies` (also: `bundledDependencies`) property is valid. | ✔️ ✅ | | | |
214
216
  | [valid-config](docs/rules/valid-config.md) | Enforce that the `config` property is valid. | ✔️ ✅ | | | |
215
217
  | [valid-cpu](docs/rules/valid-cpu.md) | Enforce that the `cpu` property is valid. | ✔️ ✅ | | | |
package/lib/plugin.js CHANGED
@@ -1,52 +1,54 @@
1
- import { rule } from "./rules/exports-subpaths-style.js";
2
- import { rule as rule$1 } from "./rules/no-empty-fields.js";
3
- import { rule as rule$2 } from "./rules/no-redundant-files.js";
4
- import { rule as rule$3 } from "./rules/order-properties.js";
5
- import { rule as rule$4 } from "./rules/repository-shorthand.js";
1
+ import { rule } from "./rules/bin-name-casing.js";
2
+ import { rule as rule$1 } from "./rules/exports-subpaths-style.js";
3
+ import { rule as rule$2 } from "./rules/no-empty-fields.js";
4
+ import { rule as rule$3 } from "./rules/no-redundant-files.js";
5
+ import { rule as rule$4 } from "./rules/order-properties.js";
6
+ import { rule as rule$5 } from "./rules/repository-shorthand.js";
6
7
  import { rules } from "./rules/require-properties.js";
7
- import { rule as rule$5 } from "./rules/restrict-dependency-ranges.js";
8
- import { rule as rule$6 } from "./rules/scripts-name-casing.js";
9
- import { rule as rule$7 } from "./rules/sort-collections.js";
10
- import { rule as rule$8 } from "./rules/unique-dependencies.js";
11
- import { rule as rule$9 } from "./rules/valid-author.js";
12
- import { rule as rule$10 } from "./rules/valid-bin.js";
13
- import { rule as rule$11 } from "./rules/valid-local-dependency.js";
14
- import { rule as rule$12 } from "./rules/valid-name.js";
15
- import { rule as rule$13 } from "./rules/valid-package-definition.js";
8
+ import { rule as rule$6 } from "./rules/restrict-dependency-ranges.js";
9
+ import { rule as rule$7 } from "./rules/restrict-private-properties.js";
10
+ import { rule as rule$8 } from "./rules/scripts-name-casing.js";
11
+ import { rule as rule$9 } from "./rules/sort-collections.js";
12
+ import { rule as rule$10 } from "./rules/unique-dependencies.js";
13
+ import { rule as rule$11 } from "./rules/valid-author.js";
14
+ import { rule as rule$12 } from "./rules/valid-local-dependency.js";
15
+ import { rule as rule$13 } from "./rules/valid-name.js";
16
+ import { rule as rule$14 } from "./rules/valid-package-definition.js";
16
17
  import { rules as rules$1 } from "./rules/valid-properties.js";
17
- import { rule as rule$14 } from "./rules/valid-repository-directory.js";
18
- import { rule as rule$15 } from "./rules/valid-version.js";
18
+ import { rule as rule$15 } from "./rules/valid-repository-directory.js";
19
+ import { rule as rule$16 } from "./rules/valid-version.js";
19
20
  import { createRequire } from "node:module";
20
21
  import * as parserJsonc from "jsonc-eslint-parser";
21
22
 
22
23
  //#region src/plugin.ts
23
24
  const { name, version } = createRequire(import.meta.url)("../package.json");
24
25
  const rules$2 = {
25
- "exports-subpaths-style": rule,
26
- "no-empty-fields": rule$1,
27
- "no-redundant-files": rule$2,
28
- "order-properties": rule$3,
26
+ "bin-name-casing": rule,
27
+ "exports-subpaths-style": rule$1,
28
+ "no-empty-fields": rule$2,
29
+ "no-redundant-files": rule$3,
30
+ "order-properties": rule$4,
29
31
  ...rules,
30
- "repository-shorthand": rule$4,
31
- "restrict-dependency-ranges": rule$5,
32
- "scripts-name-casing": rule$6,
33
- "sort-collections": rule$7,
34
- "unique-dependencies": rule$8,
32
+ "repository-shorthand": rule$5,
33
+ "restrict-dependency-ranges": rule$6,
34
+ "restrict-private-properties": rule$7,
35
+ "scripts-name-casing": rule$8,
36
+ "sort-collections": rule$9,
37
+ "unique-dependencies": rule$10,
35
38
  ...rules$1,
36
- "valid-author": rule$9,
37
- "valid-bin": rule$10,
38
- "valid-local-dependency": rule$11,
39
- "valid-name": rule$12,
40
- "valid-package-definition": rule$13,
41
- "valid-repository-directory": rule$14,
42
- "valid-version": rule$15
39
+ "valid-author": rule$11,
40
+ "valid-local-dependency": rule$12,
41
+ "valid-name": rule$13,
42
+ "valid-package-definition": rule$14,
43
+ "valid-repository-directory": rule$15,
44
+ "valid-version": rule$16
43
45
  };
44
- const baseRecommendedRules = { ...Object.fromEntries(Object.entries(rules$2).filter(([, rule$16]) => rule$16.meta.docs?.recommended).map(([name$1]) => ["package-json/" + name$1, "error"])) };
46
+ const baseRecommendedRules = { ...Object.fromEntries(Object.entries(rules$2).filter(([, rule$17]) => rule$17.meta.docs?.recommended).map(([name$1]) => ["package-json/" + name$1, "error"])) };
45
47
  const recommendedRules = {
46
48
  ...baseRecommendedRules,
47
49
  "package-json/valid-package-definition": ["error", { ignoreProperties: Object.entries(baseRecommendedRules).filter(([name$1]) => name$1.startsWith("package-json/valid-") && name$1 !== "package-json/valid-package-definition").map(([name$1]) => name$1.replace("package-json/valid-", "")) }]
48
50
  };
49
- const stylisticRules = { ...Object.fromEntries(Object.entries(rules$2).filter(([, rule$16]) => rule$16.meta.docs?.category === "Stylistic").map(([name$1]) => ["package-json/" + name$1, "error"])) };
51
+ const stylisticRules = { ...Object.fromEntries(Object.entries(rules$2).filter(([, rule$17]) => rule$17.meta.docs?.category === "Stylistic").map(([name$1]) => ["package-json/" + name$1, "error"])) };
50
52
  const plugin = {
51
53
  configs: {
52
54
  "legacy-recommended": {
@@ -0,0 +1,6 @@
1
+ import { PackageJsonRuleModule } from "../createRule.js";
2
+
3
+ //#region src/rules/bin-name-casing.d.ts
4
+ declare const rule: PackageJsonRuleModule<[], []>;
5
+ //#endregion
6
+ export { rule };
@@ -0,0 +1,43 @@
1
+ import { createRule } from "../createRule.js";
2
+ import { kebabCase } from "change-case";
3
+
4
+ //#region src/rules/bin-name-casing.ts
5
+ const rule = createRule({
6
+ create(context) {
7
+ return { "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=bin]"(node) {
8
+ if (node.value.type === "JSONObjectExpression") for (const property of node.value.properties) {
9
+ const key = property.key;
10
+ const kebabCaseKey = kebabCase(key.value);
11
+ if (kebabCaseKey !== key.value) context.report({
12
+ data: { property: key.value },
13
+ messageId: "invalidCase",
14
+ node: key,
15
+ suggest: [{
16
+ data: { property: key.value },
17
+ fix: (fixer) => {
18
+ return fixer.replaceText(key, JSON.stringify(kebabCaseKey));
19
+ },
20
+ messageId: "convertToKebabCase"
21
+ }]
22
+ });
23
+ }
24
+ } };
25
+ },
26
+ meta: {
27
+ docs: {
28
+ category: "Stylistic",
29
+ description: "Enforce that names for bin properties are in kebab case."
30
+ },
31
+ hasSuggestions: true,
32
+ messages: {
33
+ convertToKebabCase: "Convert {{ property }} to kebab case.",
34
+ invalidCase: "Command name {{ property }} should be in kebab case."
35
+ },
36
+ schema: [],
37
+ type: "suggestion"
38
+ },
39
+ name: "bin-name-casing"
40
+ });
41
+
42
+ //#endregion
43
+ export { rule };
@@ -0,0 +1,20 @@
1
+ import { PackageJsonRuleModule } from "../createRule.js";
2
+
3
+ //#region src/rules/restrict-private-properties.d.ts
4
+ declare const rule: PackageJsonRuleModule<[({
5
+ blockedProperties?: string[] | undefined;
6
+ } | undefined)?], [{
7
+ readonly additionalProperties: false;
8
+ readonly properties: {
9
+ readonly blockedProperties: {
10
+ readonly description: "Array of property names to disallow in private packages.";
11
+ readonly items: {
12
+ readonly type: "string";
13
+ };
14
+ readonly type: "array";
15
+ };
16
+ };
17
+ readonly type: "object";
18
+ }]>;
19
+ //#endregion
20
+ export { rule };
@@ -0,0 +1,55 @@
1
+ import { createRule } from "../createRule.js";
2
+ import { isJSONStringLiteral } from "../utils/predicates.js";
3
+ import { fixRemoveObjectProperty } from "eslint-fix-utils";
4
+
5
+ //#region src/rules/restrict-private-properties.ts
6
+ const defaultBlockedProperties = ["files", "publishConfig"];
7
+ const rule = createRule({
8
+ create(context) {
9
+ const blockedProperties = context.options[0]?.blockedProperties ?? defaultBlockedProperties;
10
+ return { "Program > JSONExpressionStatement > JSONObjectExpression"(node) {
11
+ if (!node.properties.some((property) => isJSONStringLiteral(property.key) && property.key.value === "private" && property.value.type === "JSONLiteral" && property.value.value === true)) return;
12
+ for (const property of node.properties) if (isJSONStringLiteral(property.key) && blockedProperties.includes(property.key.value)) {
13
+ const isEmpty = property.value.type === "JSONArrayExpression" && property.value.elements.length === 0 || property.value.type === "JSONObjectExpression" && property.value.properties.length === 0;
14
+ context.report({
15
+ data: { property: property.key.value },
16
+ messageId: "unnecessaryProperty",
17
+ node: property,
18
+ ...isEmpty ? { fix: fixRemoveObjectProperty(context, property) } : { suggest: [{
19
+ data: { property: property.key.value },
20
+ fix: fixRemoveObjectProperty(context, property),
21
+ messageId: "removePropertySuggestion"
22
+ }] }
23
+ });
24
+ }
25
+ } };
26
+ },
27
+ meta: {
28
+ defaultOptions: [{ blockedProperties: defaultBlockedProperties }],
29
+ docs: {
30
+ category: "Best Practices",
31
+ description: "Disallows unnecessary properties in private packages.",
32
+ recommended: false
33
+ },
34
+ fixable: "code",
35
+ hasSuggestions: true,
36
+ messages: {
37
+ removePropertySuggestion: "Remove the '{{property}}' field.",
38
+ unnecessaryProperty: "The '{{property}}' field is unnecessary in private packages and can be removed."
39
+ },
40
+ schema: [{
41
+ additionalProperties: false,
42
+ properties: { blockedProperties: {
43
+ description: "Array of property names to disallow in private packages.",
44
+ items: { type: "string" },
45
+ type: "array"
46
+ } },
47
+ type: "object"
48
+ }],
49
+ type: "suggestion"
50
+ },
51
+ name: "restrict-private-properties"
52
+ });
53
+
54
+ //#endregion
55
+ export { rule };
@@ -1,8 +1,9 @@
1
1
  import { createSimpleValidPropertyRule } from "../utils/createSimpleValidPropertyRule.js";
2
- import { validateBundleDependencies, validateConfig, validateCpu, validateDependencies, validateDescription, validateDirectories, validateExports, validateLicense, validateScripts, validateType } from "package-json-validator";
2
+ import { validateBin, validateBundleDependencies, validateConfig, validateCpu, validateDependencies, validateDescription, validateDirectories, validateExports, validateLicense, validateScripts, validateType } from "package-json-validator";
3
3
 
4
4
  //#region src/rules/valid-properties.ts
5
5
  const properties = [
6
+ ["bin", validateBin],
6
7
  ["bundleDependencies", {
7
8
  aliases: ["bundledDependencies"],
8
9
  validator: validateBundleDependencies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-package-json",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "description": "Rules for consistent, readable, and valid package.json files. 🗂️",
5
5
  "homepage": "https://github.com/JoshuaKGoldberg/eslint-plugin-package-json#readme",
6
6
  "bugs": {
@@ -59,7 +59,7 @@
59
59
  "@eslint/js": "9.38.0",
60
60
  "@release-it/conventional-changelog": "10.0.0",
61
61
  "@types/estree": "1.0.7",
62
- "@types/node": "24.9.1",
62
+ "@types/node": "24.10.0",
63
63
  "@types/semver": "7.7.0",
64
64
  "@types/validate-npm-package-name": "4.0.2",
65
65
  "@vitest/coverage-v8": "4.0.4",
@@ -97,7 +97,7 @@
97
97
  "eslint": ">=8.0.0",
98
98
  "jsonc-eslint-parser": "^2.0.0"
99
99
  },
100
- "packageManager": "pnpm@10.19.0",
100
+ "packageManager": "pnpm@10.20.0",
101
101
  "engines": {
102
102
  "node": "^20.19.0 || >=22.12.0"
103
103
  },
@@ -1,17 +0,0 @@
1
- import { PackageJsonRuleModule } from "../createRule.js";
2
-
3
- //#region src/rules/valid-bin.d.ts
4
- declare const rule: PackageJsonRuleModule<[({
5
- enforceCase?: boolean | undefined;
6
- } | undefined)?], [{
7
- readonly additionalProperties: false;
8
- readonly properties: {
9
- readonly enforceCase: {
10
- readonly description: "Enforce that the bin's keys should be in kebab case.";
11
- readonly type: "boolean";
12
- };
13
- };
14
- readonly type: "object";
15
- }]>;
16
- //#endregion
17
- export { rule };
@@ -1,62 +0,0 @@
1
- import { createRule } from "../createRule.js";
2
- import { formatErrors } from "../utils/formatErrors.js";
3
- import { kebabCase } from "change-case";
4
- import { validateBin } from "package-json-validator";
5
-
6
- //#region src/rules/valid-bin.ts
7
- const rule = createRule({
8
- create(context) {
9
- const shouldEnforceCase = !!context.options[0]?.enforceCase;
10
- return { "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=bin]"(node) {
11
- const binValueNode = node.value;
12
- const errors = validateBin(JSON.parse(context.sourceCode.getText(binValueNode)));
13
- if (errors.length) context.report({
14
- data: { errors: formatErrors(errors) },
15
- messageId: "validationError",
16
- node: binValueNode
17
- });
18
- if (shouldEnforceCase && node.value.type === "JSONObjectExpression") for (const property of node.value.properties) {
19
- const key = property.key;
20
- const kebabCaseKey = kebabCase(key.value);
21
- if (kebabCaseKey !== key.value) context.report({
22
- data: { property: key.value },
23
- messageId: "invalidCase",
24
- node: key,
25
- suggest: [{
26
- fix: (fixer) => {
27
- return fixer.replaceText(key, JSON.stringify(kebabCaseKey));
28
- },
29
- messageId: "convertToKebabCase"
30
- }]
31
- });
32
- }
33
- } };
34
- },
35
- meta: {
36
- defaultOptions: [{ enforceCase: false }],
37
- docs: {
38
- category: "Best Practices",
39
- description: "Enforce that the `bin` property is valid.",
40
- recommended: true
41
- },
42
- hasSuggestions: true,
43
- messages: {
44
- convertToKebabCase: "Convert command name to kebab case.",
45
- invalidCase: "Command name {{ property }} should be in kebab case.",
46
- validationError: "Invalid bin: {{ errors }}"
47
- },
48
- schema: [{
49
- additionalProperties: false,
50
- properties: { enforceCase: {
51
- description: "Enforce that the bin's keys should be in kebab case.",
52
- type: "boolean"
53
- } },
54
- type: "object"
55
- }],
56
- type: "problem"
57
- },
58
- name: "valid-bin"
59
- });
60
-
61
- //#endregion
62
- export { rule };