Skip to content

Commit cde68da

Browse files
authored
feat(require-author): add new require-author rule (#851)
<!-- πŸ‘‹ Hi, thanks for sending a PR to eslint-plugin-package-json! πŸ’–. Please fill out all fields below and make sure each item is true and [x] checked. Otherwise we may not be able to review your PR. --> ## PR Checklist - [x] Addresses an existing open issue: fixes #795 - [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22) - [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/eslint-plugin-package-json/blob/main/.github/CONTRIBUTING.md) were taken ## Overview This change adds the first or our new `require-` rules, and creates some foundational plumbing to make it super easy to add new require rules. I've only done author in this PR. Assuming this all looks good, we can quickly knock all the rest of the require rules in one shot.
1 parent f4666be commit cde68da

File tree

7 files changed

+153
-1
lines changed

7 files changed

+153
-1
lines changed

β€ŽREADME.md

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ The default settings don't conflict, and Prettier plugins can quickly fix up ord
126126
| [no-redundant-files](docs/rules/no-redundant-files.md) | Prevents adding unnecessary / redundant files. | | | πŸ’‘ | |
127127
| [order-properties](docs/rules/order-properties.md) | Package properties must be declared in standard order | βœ… | πŸ”§ | | |
128128
| [repository-shorthand](docs/rules/repository-shorthand.md) | Enforce either object or shorthand declaration for repository. | βœ… | πŸ”§ | | |
129+
| [require-author](docs/rules/require-author.md) | Requires the `author` property to be present. | | | | |
129130
| [sort-collections](docs/rules/sort-collections.md) | Dependencies, scripts, and configuration values must be declared in alphabetical order. | βœ… | πŸ”§ | | |
130131
| [unique-dependencies](docs/rules/unique-dependencies.md) | Checks a dependency isn't specified more than once (i.e. in `dependencies` and `devDependencies`) | βœ… | | πŸ’‘ | |
131132
| [valid-local-dependency](docs/rules/valid-local-dependency.md) | Checks existence of local dependencies in the package.json | βœ… | | | |

β€Ždocs/rules/require-author.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# require-author
2+
3+
<!-- end auto-generated rule header -->
4+
5+
This rule checks for the existence of the `"author"` property in a package.json,
6+
and reports a violation if it doesn't exist.
7+
8+
Example of **incorrect** code for this rule:
9+
10+
```json
11+
{
12+
"name": "thee-silver-mt-zion",
13+
"version": "13.0.0"
14+
}
15+
```
16+
17+
Example of **correct** code for this rule:
18+
19+
```json
20+
{
21+
"name": "thee-silver-mt-zion",
22+
"version": "13.0.0",
23+
"author": "Jessica Moss"
24+
}
25+
```

β€Žpackage.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"build": "tsup",
4040
"format": "prettier \"**/*\" --ignore-unknown",
4141
"lint": "eslint . --max-warnings 0",
42-
"lint:eslint-docs": "npm run update:eslint-docs -- --check",
42+
"lint:eslint-docs": "pnpm update:eslint-docs --check",
4343
"lint:knip": "knip",
4444
"lint:md": "markdownlint \"**/*.md\" \".github/**/*.md\"",
4545
"lint:packages": "pnpm dedupe --check",

β€Žsrc/plugin.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { rule as noEmptyFields } from "./rules/no-empty-fields.js";
66
import { rule as noRedundantFiles } from "./rules/no-redundant-files.js";
77
import { rule as orderProperties } from "./rules/order-properties.js";
88
import { rule as preferRepositoryShorthand } from "./rules/repository-shorthand.js";
9+
import { rules as requireRules } from "./rules/require-properties.js";
910
import { rule as sortCollections } from "./rules/sort-collections.js";
1011
import { rule as uniqueDependencies } from "./rules/unique-dependencies.js";
1112
import { rule as validLocalDependency } from "./rules/valid-local-dependency.js";
@@ -25,6 +26,7 @@ const rules: Record<string, PackageJsonRuleModule> = {
2526
"no-empty-fields": noEmptyFields,
2627
"no-redundant-files": noRedundantFiles,
2728
"order-properties": orderProperties,
29+
...requireRules,
2830
"repository-shorthand": preferRepositoryShorthand,
2931
"sort-collections": sortCollections,
3032
"unique-dependencies": uniqueDependencies,

β€Žsrc/rules/require-properties.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { PackageJsonRuleModule } from "../createRule.js";
2+
3+
import { createRequirePropertyRule } from "../utils/createRequirePropertyRule.js";
4+
5+
// List of all properties we want to create require- rules for,
6+
// in the format [propertyName, isRecommended]
7+
const properties = [
8+
["author", false],
9+
// TODO: More to come!
10+
] satisfies [string, boolean][];
11+
12+
/** All require- flavor rules */
13+
export const rules = properties.reduce<Record<string, PackageJsonRuleModule>>(
14+
(acc, [propertyName, isRecommended]) => {
15+
acc[`require-${propertyName}`] = createRequirePropertyRule(
16+
propertyName,
17+
isRecommended,
18+
);
19+
return acc;
20+
},
21+
{},
22+
);
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { rules } from "../../rules/require-properties.js";
2+
import { ruleTester } from "./ruleTester.js";
3+
4+
ruleTester.run("require-author", rules["require-author"], {
5+
invalid: [
6+
{
7+
code: "{}",
8+
errors: [
9+
{
10+
data: { property: "author" },
11+
line: 1,
12+
messageId: "missing",
13+
},
14+
],
15+
},
16+
{
17+
code: `{
18+
"name": "foo",
19+
"version": "1.0.0"
20+
}
21+
`,
22+
errors: [
23+
{
24+
data: { property: "author" },
25+
line: 1,
26+
messageId: "missing",
27+
},
28+
],
29+
},
30+
{
31+
code: `{
32+
"name": "foo",
33+
"version": "1.0.0",
34+
"bin": {
35+
"author": "./cli.js"
36+
}
37+
}
38+
`,
39+
errors: [
40+
{
41+
data: { property: "author" },
42+
line: 1,
43+
messageId: "missing",
44+
},
45+
],
46+
},
47+
],
48+
valid: [
49+
`{ "main": "./index.js", "author": "Sophie Trudeau" }`,
50+
`{ "author": "Jessica Moss" }`,
51+
`{ "author": 123 }`,
52+
`{ "author": { "name": "Jessica Moss" } }`,
53+
],
54+
});
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { AST as JsonAST } from "jsonc-eslint-parser";
2+
3+
import { createRule } from "../createRule.js";
4+
import { isJSONStringLiteral } from "./predicates.js";
5+
6+
/**
7+
* Given a top-level property name, create a rule that requires that property to be present.
8+
* Optionally, include it in the recommended config.
9+
*/
10+
export const createRequirePropertyRule = (
11+
propertyName: string,
12+
isRecommended = false,
13+
) => {
14+
return createRule({
15+
create(context) {
16+
return {
17+
"Program > JSONExpressionStatement > JSONObjectExpression"(
18+
node: JsonAST.JSONObjectExpression,
19+
) {
20+
if (
21+
!node.properties.some(
22+
(property) =>
23+
isJSONStringLiteral(property.key) &&
24+
property.key.value === propertyName,
25+
)
26+
) {
27+
context.report({
28+
data: { property: propertyName },
29+
messageId: "missing",
30+
node: context.sourceCode.ast,
31+
});
32+
}
33+
},
34+
};
35+
},
36+
meta: {
37+
docs: {
38+
description: `Requires the \`${propertyName}\` property to be present.`,
39+
recommended: isRecommended,
40+
},
41+
messages: {
42+
missing: "Property '{{property}}' is required.",
43+
},
44+
schema: [],
45+
type: "suggestion",
46+
},
47+
});
48+
};

0 commit comments

Comments
Β (0)