Skip to content

Commit

Permalink
feat(require-author): add new require-author rule (#851)
Browse files Browse the repository at this point in the history
<!-- πŸ‘‹ 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.
  • Loading branch information
michaelfaith authored Feb 3, 2025
1 parent f4666be commit cde68da
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ The default settings don't conflict, and Prettier plugins can quickly fix up ord
| [no-redundant-files](docs/rules/no-redundant-files.md) | Prevents adding unnecessary / redundant files. | | | πŸ’‘ | |
| [order-properties](docs/rules/order-properties.md) | Package properties must be declared in standard order | βœ… | πŸ”§ | | |
| [repository-shorthand](docs/rules/repository-shorthand.md) | Enforce either object or shorthand declaration for repository. | βœ… | πŸ”§ | | |
| [require-author](docs/rules/require-author.md) | Requires the `author` property to be present. | | | | |
| [sort-collections](docs/rules/sort-collections.md) | Dependencies, scripts, and configuration values must be declared in alphabetical order. | βœ… | πŸ”§ | | |
| [unique-dependencies](docs/rules/unique-dependencies.md) | Checks a dependency isn't specified more than once (i.e. in `dependencies` and `devDependencies`) | βœ… | | πŸ’‘ | |
| [valid-local-dependency](docs/rules/valid-local-dependency.md) | Checks existence of local dependencies in the package.json | βœ… | | | |
Expand Down
25 changes: 25 additions & 0 deletions docs/rules/require-author.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# require-author

<!-- end auto-generated rule header -->

This rule checks for the existence of the `"author"` property in a package.json,
and reports a violation if it doesn't exist.

Example of **incorrect** code for this rule:

```json
{
"name": "thee-silver-mt-zion",
"version": "13.0.0"
}
```

Example of **correct** code for this rule:

```json
{
"name": "thee-silver-mt-zion",
"version": "13.0.0",
"author": "Jessica Moss"
}
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"build": "tsup",
"format": "prettier \"**/*\" --ignore-unknown",
"lint": "eslint . --max-warnings 0",
"lint:eslint-docs": "npm run update:eslint-docs -- --check",
"lint:eslint-docs": "pnpm update:eslint-docs --check",
"lint:knip": "knip",
"lint:md": "markdownlint \"**/*.md\" \".github/**/*.md\"",
"lint:packages": "pnpm dedupe --check",
Expand Down
2 changes: 2 additions & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { rule as noEmptyFields } from "./rules/no-empty-fields.js";
import { rule as noRedundantFiles } from "./rules/no-redundant-files.js";
import { rule as orderProperties } from "./rules/order-properties.js";
import { rule as preferRepositoryShorthand } from "./rules/repository-shorthand.js";
import { rules as requireRules } from "./rules/require-properties.js";
import { rule as sortCollections } from "./rules/sort-collections.js";
import { rule as uniqueDependencies } from "./rules/unique-dependencies.js";
import { rule as validLocalDependency } from "./rules/valid-local-dependency.js";
Expand All @@ -25,6 +26,7 @@ const rules: Record<string, PackageJsonRuleModule> = {
"no-empty-fields": noEmptyFields,
"no-redundant-files": noRedundantFiles,
"order-properties": orderProperties,
...requireRules,
"repository-shorthand": preferRepositoryShorthand,
"sort-collections": sortCollections,
"unique-dependencies": uniqueDependencies,
Expand Down
22 changes: 22 additions & 0 deletions src/rules/require-properties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { PackageJsonRuleModule } from "../createRule.js";

import { createRequirePropertyRule } from "../utils/createRequirePropertyRule.js";

// List of all properties we want to create require- rules for,
// in the format [propertyName, isRecommended]
const properties = [
["author", false],
// TODO: More to come!
] satisfies [string, boolean][];

/** All require- flavor rules */
export const rules = properties.reduce<Record<string, PackageJsonRuleModule>>(
(acc, [propertyName, isRecommended]) => {
acc[`require-${propertyName}`] = createRequirePropertyRule(
propertyName,
isRecommended,
);
return acc;
},
{},
);
54 changes: 54 additions & 0 deletions src/tests/rules/require-author.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { rules } from "../../rules/require-properties.js";
import { ruleTester } from "./ruleTester.js";

ruleTester.run("require-author", rules["require-author"], {
invalid: [
{
code: "{}",
errors: [
{
data: { property: "author" },
line: 1,
messageId: "missing",
},
],
},
{
code: `{
"name": "foo",
"version": "1.0.0"
}
`,
errors: [
{
data: { property: "author" },
line: 1,
messageId: "missing",
},
],
},
{
code: `{
"name": "foo",
"version": "1.0.0",
"bin": {
"author": "./cli.js"
}
}
`,
errors: [
{
data: { property: "author" },
line: 1,
messageId: "missing",
},
],
},
],
valid: [
`{ "main": "./index.js", "author": "Sophie Trudeau" }`,
`{ "author": "Jessica Moss" }`,
`{ "author": 123 }`,
`{ "author": { "name": "Jessica Moss" } }`,
],
});
48 changes: 48 additions & 0 deletions src/utils/createRequirePropertyRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { AST as JsonAST } from "jsonc-eslint-parser";

import { createRule } from "../createRule.js";
import { isJSONStringLiteral } from "./predicates.js";

/**
* Given a top-level property name, create a rule that requires that property to be present.
* Optionally, include it in the recommended config.
*/
export const createRequirePropertyRule = (
propertyName: string,
isRecommended = false,
) => {
return createRule({
create(context) {
return {
"Program > JSONExpressionStatement > JSONObjectExpression"(
node: JsonAST.JSONObjectExpression,
) {
if (
!node.properties.some(
(property) =>
isJSONStringLiteral(property.key) &&
property.key.value === propertyName,
)
) {
context.report({
data: { property: propertyName },
messageId: "missing",
node: context.sourceCode.ast,
});
}
},
};
},
meta: {
docs: {
description: `Requires the \`${propertyName}\` property to be present.`,
recommended: isRecommended,
},
messages: {
missing: "Property '{{property}}' is required.",
},
schema: [],
type: "suggestion",
},
});
};

0 comments on commit cde68da

Please sign in to comment.