Skip to content

Commit

Permalink
Merge branch 'main' into eslint-fix-utils
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaKGoldberg committed Jan 23, 2025
2 parents 5cacb93 + e57765b commit 964420f
Show file tree
Hide file tree
Showing 5 changed files with 469 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ The default settings don't conflict, and Prettier plugins can quickly fix up ord

| Name                       | Description | 💼 | 🔧 | 💡 ||
| :--------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | :- | :- | :- | :- |
| [no-empty-fields](docs/rules/no-empty-fields.md) | Reports on unnecessary empty arrays and objects. || | 💡 | |
| [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. || 🔧 | | |
Expand Down
37 changes: 37 additions & 0 deletions docs/rules/no-empty-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# no-empty-fields

💼 This rule is enabled in the ✅ `recommended` config.

💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).

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

This rule flags all empty arrays and objects in a `package.json`,
as such empty expressions do nothing, and are often the result of a mistake.
It will report both named properties that are empty, as well as nested arrays and objects
that are empty.

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

```json
{
"main": "lib/index.js",
"scripts": {},
"files": [],
"simple-git-hooks": {
"pre-commit": "pnpm exec nano-staged --allow-empty",
"preserveUnused": []
}
}
```

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

```json
{
"main": "lib/index.js",
"simple-git-hooks": {
"pre-commit": "pnpm exec nano-staged --allow-empty"
}
}
```
2 changes: 2 additions & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createRequire } from "node:module";

import type { PackageJsonRuleModule } from "./createRule.js";

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";
Expand All @@ -21,6 +22,7 @@ const { name, version } = require("../package.json") as {
};

const rules: Record<string, PackageJsonRuleModule> = {
"no-empty-fields": noEmptyFields,
"no-redundant-files": noRedundantFiles,
"order-properties": orderProperties,
"repository-shorthand": preferRepositoryShorthand,
Expand Down
122 changes: 122 additions & 0 deletions src/rules/no-empty-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import type { AST as JsonAST } from "jsonc-eslint-parser";

import * as ESTree from "estree";

import { createRule, PackageJsonRuleContext } from "../createRule";

const getDataAndMessageId = (
node:
| JsonAST.JSONArrayExpression
| JsonAST.JSONObjectExpression
| JsonAST.JSONProperty,
): {
data: Record<string, string>;
messageId: "emptyExpression" | "emptyFields";
} => {
switch (node.type) {
case "JSONArrayExpression":
return {
data: {
expressionType: "array",
},
messageId: "emptyExpression",
};
case "JSONObjectExpression":
return {
data: {
expressionType: "object",
},
messageId: "emptyExpression",
};
case "JSONProperty":
return {
data: {
field: (node.key as JsonAST.JSONStringLiteral).value,
},
messageId: "emptyFields",
};
}
};

const report = (
context: PackageJsonRuleContext,
node:
| JsonAST.JSONArrayExpression
| JsonAST.JSONObjectExpression
| JsonAST.JSONProperty,
) => {
const { data, messageId } = getDataAndMessageId(node);
context.report({
data,
messageId,
node: node as unknown as ESTree.Node,
suggest: [
{
*fix(fixer) {
yield fixer.remove(node as unknown as ESTree.Node);

const tokenFromCurrentLine =
context.sourceCode.getTokenAfter(
node as unknown as ESTree.Node,
);
const tokenFromPreviousLine =
context.sourceCode.getTokenBefore(
node as unknown as ESTree.Node,
);

if (
tokenFromPreviousLine?.value === "," &&
tokenFromCurrentLine?.value !== ","
) {
yield fixer.remove(tokenFromPreviousLine);
}

if (tokenFromCurrentLine?.value === ",") {
yield fixer.remove(tokenFromCurrentLine);
}
},
messageId: "remove",
},
],
});
};

const getNode = (
node: JsonAST.JSONArrayExpression | JsonAST.JSONObjectExpression,
) => {
return node.parent.type === "JSONProperty" ? node.parent : node;
};

export const rule = createRule({
create(context) {
return {
JSONArrayExpression(node: JsonAST.JSONArrayExpression) {
if (!node.elements.length) {
report(context, getNode(node));
}
},
JSONObjectExpression(node: JsonAST.JSONObjectExpression) {
if (!node.properties.length) {
report(context, getNode(node));
}
},
};
},
meta: {
docs: {
category: "Best Practices",
description: "Reports on unnecessary empty arrays and objects.",
recommended: true,
},
hasSuggestions: true,
messages: {
emptyExpression:
"This {{expressionType}} does nothing and can be removed.",
emptyFields:
"The field '{{field}}' does nothing and can be removed.",
remove: "Remove this empty field.",
},
schema: [],
type: "suggestion",
},
});
Loading

0 comments on commit 964420f

Please sign in to comment.