Skip to content

Commit

Permalink
docs: update rules table during build (#274)
Browse files Browse the repository at this point in the history
* docs: update rules table during build

* update rules table before commit

* assume `meta.docs` is always set on rules
  • Loading branch information
fasttime committed Aug 30, 2024
1 parent 7040a2e commit 8196064
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 11 deletions.
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,19 @@ export default [

### Rules

| **Rule Name** | **Description** |
|---------------|-----------------|
| [`fenced-code-language`](./docs/rules/fenced-code-language.md) | Enforce fenced code blocks to specify a language. |
| [`heading-increment`](./docs/rules/heading-increment.md) | Enforce heading levels increment by one. |
| [`no-duplicate-headings`](./docs/rules/no-duplicate-headings.md) | Disallow duplicate headings in the same document. |
| [`no-empty-links`](./docs/rules/no-empty-links.md) | Disallow empty links. |
| [`no-html`](./docs/rules/no-html.md) | Enforce fenced code blocks to specify a language. |
| [`no-invalid-label-refs`](./docs/rules/no-invalid-label-refs.md) | Disallow invalid label references. |
| [`no-missing-label-refs`](./docs/rules/no-missing-label-refs.md) | Disallow missing label references. |
<!-- NOTE: The following table is autogenerated. Do not manually edit. -->

<!-- Rule Table Start -->
| **Rule Name** | **Description** | **Recommended** |
| :- | :- | :-: |
| [`fenced-code-language`](./docs/rules/fenced-code-language.md) | Require languages for fenced code blocks. | yes |
| [`heading-increment`](./docs/rules/heading-increment.md) | Enforce heading levels increment by one. | yes |
| [`no-duplicate-headings`](./docs/rules/no-duplicate-headings.md) | Disallow duplicate headings in the same document. | no |
| [`no-empty-links`](./docs/rules/no-empty-links.md) | Disallow empty links. | yes |
| [`no-html`](./docs/rules/no-html.md) | Disallow HTML tags. | no |
| [`no-invalid-label-refs`](./docs/rules/no-invalid-label-refs.md) | Disallow invalid label references. | yes |
| [`no-missing-label-refs`](./docs/rules/no-missing-label-refs.md) | Disallow missing label references. | yes |
<!-- Rule Table End -->

**Note:** This plugin does not provide formatting rules. We recommend using a source code formatter such as [Prettier](https://prettier.io) for that purpose.

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"eslint --fix",
"prettier --write"
],
"!(*.{js,md})": "prettier --write --ignore-unknown"
"!(*.{js,md})": "prettier --write --ignore-unknown",
"{src/rules/*.js,tools/update-rules-docs.js,README.md}": "npm run build:update-rules-docs"
},
"scripts": {
"lint": "eslint . && eslint -c eslint.config-content.js .",
Expand All @@ -50,7 +51,8 @@
"fmt:check": "prettier --check .",
"build:dedupe-types": "node tools/dedupe-types.js dist/esm/index.js",
"build:rules": "node tools/build-rules.js",
"build": "npm run build:rules && rollup -c && npm run build:dedupe-types && tsc -p tsconfig.esm.json",
"build:update-rules-docs": "node tools/update-rules-docs.js",
"build": "npm run build:rules && rollup -c && npm run build:dedupe-types && tsc -p tsconfig.esm.json && npm run build:update-rules-docs",
"prepare": "node ./npm-prepare.cjs && npm run build",
"test": "c8 mocha \"tests/**/*.test.js\" --timeout 30000",
"test:jsr": "npx jsr@latest publish --dry-run"
Expand Down
102 changes: 102 additions & 0 deletions tools/update-rules-docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* @fileoverview Updates the rules table in README.md with rule names,
* descriptions, and whether the rules are recommended or not.
*
* Usage:
* node tools/update-rules-docs.js
*
* @author Francesco Trotta
*/

//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------

import { fromMarkdown } from "mdast-util-from-markdown";
import fs from "node:fs/promises";
import path from "node:path";

//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------

/** @typedef {import("eslint").AST.Range} Range */

//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------

const docsFileURL = new URL("../README.md", import.meta.url);
const rulesDirURL = new URL("../src/rules/", import.meta.url);

/**
* Formats a table row from a rule filename.
* @param {string} ruleFilename The filename of the rule module without directory.
* @returns {Promise<string>} The formatted markdown text of the table row.
*/
async function formatTableRowFromFilename(ruleFilename) {
const ruleURL = new URL(ruleFilename, rulesDirURL);
const { default: rule } = await import(ruleURL);
const ruleName = path.parse(ruleFilename).name;
const { description, recommended } = rule.meta.docs;
const ruleLink = `[\`${ruleName}\`](./docs/rules/${ruleName}.md)`;
const recommendedText = recommended ? "yes" : "no";

return `| ${ruleLink} | ${description} | ${recommendedText} |`;
}

/**
* Generates the markdown text for the rules table.
* @returns {Promise<string>} The formatted markdown text of the rules table.
*/
async function createRulesTableText() {
const filenames = await fs.readdir(rulesDirURL);
const ruleFilenames = filenames.filter(
filename => path.extname(filename) === ".js",
);
const text = [
"| **Rule Name** | **Description** | **Recommended** |",
"| :- | :- | :-: |",
...(await Promise.all(ruleFilenames.map(formatTableRowFromFilename))),
].join("\n");

return text;
}

/**
* Returns start and end offset of the rules table as indicated by "Rule Table Start" and
* "Rule Table End" HTML comments in the markdown text.
* @param {string} text The markdown text.
* @returns {Range | null} The offset range of the rules table, or `null`.
*/
function getRulesTableRange(text) {
const tree = fromMarkdown(text);
const htmlNodes = tree.children.filter(({ type }) => type === "html");
const startComment = htmlNodes.find(
({ value }) => value === "<!-- Rule Table Start -->",
);
const endComment = htmlNodes.find(
({ value }) => value === "<!-- Rule Table End -->",
);

return startComment && endComment
? [startComment.position.end.offset, endComment.position.start.offset]
: null;
}

//-----------------------------------------------------------------------------
// Main
//-----------------------------------------------------------------------------

let docsText = await fs.readFile(docsFileURL, "utf-8");
const rulesTableRange = getRulesTableRange(docsText);

if (!rulesTableRange) {
throw Error("Rule Table Start/End comments not found, unable to update.");
}

const tableText = await createRulesTableText();

docsText = `${docsText.slice(0, rulesTableRange[0])}\n${tableText}\n${docsText.slice(rulesTableRange[1])}`;

await fs.writeFile(docsFileURL, docsText);

0 comments on commit 8196064

Please sign in to comment.