Skip to content

fix: sync rule type header comments automatically #19276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
update only new and modified rule types on commit hook, update all ru…
…le types on release
  • Loading branch information
fasttime committed Jan 17, 2025
commit c89f9057ed515a633d62246908618a46c7fa8f8b
22 changes: 20 additions & 2 deletions Makefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,21 @@ function updateVersions(oldVersion, newVersion) {
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 4)}\n`);
}

/**
* Updates TSDoc header comments of all rule types.
* @returns {void}
*/
function updateRuleTypeHeaders() {
const { execFileSync } = require("node:child_process");

// We don't need the stack trace of execFileSync if the command fails.
try {
execFileSync(process.execPath, ["tools/update-rule-type-headers.js"], { stdio: "inherit" });
} catch {
exit(1);
}
}

/**
* Updates the changelog, bumps the version number in package.json, creates a local git commit and tag,
* and generates the site in an adjacent `website` folder.
Expand Down Expand Up @@ -356,8 +371,11 @@ function generateRelease({ prereleaseId, packageTag }) {
updateVersions(oldVersion, releaseInfo.version);
}

echo("Updating commit with docs data");
exec("git add docs/ && git commit --amend --no-edit");
echo("Updating rule type header comments");
updateRuleTypeHeaders();

echo("Updating commit with docs data and rule types");
exec("git add lib/types/rules/ docs/ && git commit --amend --no-edit");
exec(`git tag -a -f v${releaseInfo.version} -m ${releaseInfo.version}`);
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"git add docs/src/_data/further_reading_links.json"
],
"docs/**/*.svg": "trunk check --fix --filter=svgo",
"docs/src/_data/{rules_meta,rule_versions}.json": [
"{lib/rules/*.js,docs/src/_data/rule_versions.json}": [
"node tools/update-rule-type-headers.js",
"git add lib/types/rules/*.ts"
]
Expand Down
88 changes: 62 additions & 26 deletions tools/update-rule-type-headers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/**
* @fileoverview Script to update the TSDoc header comments of rule types.
* This script should be run after `docs/src/_data/rules_meta.json` and `docs/src/_data/rule_versions.json` have been updated.
* If this script is run with command-line arguments, it will only update rule types that match the specified rule files.
* E.g. running with CLI arguments `"./lib/rules/no-unused-vars.js"`
* will only update the TSDoc header comment for the `no-unused-vars` rule.
* If this script is run without arguments, it will update the TSDoc header comments for all rules.
*
* @author Francesco Trotta
*/
Expand All @@ -12,9 +15,9 @@
//-----------------------------------------------------------------------------

const { readdir, readFile, writeFile } = require("node:fs/promises");
const { extname, join } = require("node:path");
const rulesMeta = require("../docs/src/_data/rules_meta.json");
const { basename, dirname, extname, join, resolve } = require("node:path");
const { added } = require("../docs/src/_data/rule_versions.json");
const rules = require("../lib/rules");
const ts = require("typescript");

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -46,7 +49,7 @@ function createDeprecationNotice(tsDoc, ruleMeta) {

if (replacedBy && replacedBy.length === 1) {
const replacement = replacedBy[0];
const replacementURL = rulesMeta[replacement].docs.url;
const replacementURL = rules.get(replacement).meta.docs.url;

return `please use [\`${replacement}\`](${replacementURL}).`;
}
Expand Down Expand Up @@ -116,14 +119,41 @@ function formatTSDoc(lines) {
return formattedLines.join("\n");
}

/**
* Returns the names of the rules whose paths were specified in the command line.
* If no rule paths were specified, the names of all built-in rules are returned.
* @returns {Set<string>} The names of the rules to be considered for the current run.
*/
function getConsideredRuleIds() {
let ruleIds;
const args = process.argv.slice(2);

if (args.length) {
const ruleDir = join(__dirname, "../lib/rules");
const ruleIndexFile = join(ruleDir, "index.js");

ruleIds = args
.filter(arg => {
const file = resolve(arg);

return dirname(file) === ruleDir && file !== ruleIndexFile;
})
.map(ruleFile => basename(ruleFile, ".js"));
} else {
ruleIds = rules.keys();
}
return new Set(ruleIds);
}

/**
* Returns the locations of the TSDoc header comments for each rule in a type declaration file.
* If a rule has no header comment the location returned is the start position of the rule name.
* @param {string} sourceText The source text of the type declaration file.
* @param {Set<string>} consideredRuleIds The names of the rules to be considered for the current run.
* @returns {Map<string, Range>} A map of rule names to ranges.
* The ranges indicate the locations of the TSDoc header comments in the source.
*/
function getTSDocRangeMap(sourceText) {
function getTSDocRangeMap(sourceText, consideredRuleIds) {
const tsDocRangeMap = new Map();
const ast = ts.createSourceFile("", sourceText);

Expand All @@ -134,22 +164,25 @@ function getTSDocRangeMap(sourceText) {
for (const member of members) {
if (member.kind === ts.SyntaxKind.PropertySignature) {
const ruleId = member.name.text;
let tsDocRange;

// Only the last TSDoc comment is regarded.
const tsDoc = member.jsDoc?.at(-1);
if (consideredRuleIds.has(ruleId)) {
let tsDocRange;

if (tsDoc) {
tsDocRange = [tsDoc.pos, tsDoc.end];
} else {
const regExp = /\S/gu;
// Only the last TSDoc comment is regarded.
const tsDoc = member.jsDoc?.at(-1);

if (tsDoc) {
tsDocRange = [tsDoc.pos, tsDoc.end];
} else {
const regExp = /\S/gu;

regExp.lastIndex = member.pos;
const { index } = regExp.exec(sourceText);
regExp.lastIndex = member.pos;
const { index } = regExp.exec(sourceText);

tsDocRange = [index, index];
tsDocRange = [index, index];
}
tsDocRangeMap.set(ruleId, tsDocRange);
}
tsDocRangeMap.set(ruleId, tsDocRange);
}
}
}
Expand Down Expand Up @@ -185,7 +218,7 @@ function paraphraseDescription(description) {
* @returns {string} The updated TSDoc comment.
*/
function createTSDoc(ruleId, currentTSDoc) {
const ruleMeta = rulesMeta[ruleId];
const ruleMeta = rules.get(ruleId).meta;
const ruleDocs = ruleMeta.docs;
const since = added[ruleId];
const lines = [escapeForMultilineComment(paraphraseDescription(ruleDocs.description)), ""];
Expand Down Expand Up @@ -213,12 +246,13 @@ function createTSDoc(ruleId, currentTSDoc) {

/**
* Updates rule header comments in a `.d.ts` file.
* @param {string} filePath Pathname of the `.d.ts` file.
* @param {string} ruleTypeFile Pathname of the `.d.ts` file.
* @param {Set<string>} consideredRuleIds The names of the rules to be considered for the current run.
* @returns {Promise<Iterable<string>>} The names of the rules found in the `.d.ts` file.
*/
async function updateTypeDeclaration(filePath) {
const sourceText = await readFile(filePath, "utf-8");
const tsDocRangeMap = getTSDocRangeMap(sourceText);
async function updateTypeDeclaration(ruleTypeFile, consideredRuleIds) {
const sourceText = await readFile(ruleTypeFile, "utf-8");
const tsDocRangeMap = getTSDocRangeMap(sourceText, consideredRuleIds);
const chunks = [];
let lastPos = 0;

Expand All @@ -236,7 +270,7 @@ async function updateTypeDeclaration(filePath) {
chunks.push(sourceText.slice(Math.max(0, lastPos)));
const newText = chunks.join("");

await writeFile(filePath, newText);
await writeFile(ruleTypeFile, newText);
return tsDocRangeMap.keys();
}

Expand All @@ -245,16 +279,18 @@ async function updateTypeDeclaration(filePath) {
//-----------------------------------------------------------------------------

(async () => {
const dirPath = join(__dirname, "../lib/types/rules");
const fileNames = (await readdir(dirPath)).filter(fileName => extname(fileName) === ".ts");
const consideredRuleIds = getConsideredRuleIds();
const ruleTypeDir = join(__dirname, "../lib/types/rules");
const fileNames = (await readdir(ruleTypeDir)).filter(fileName => extname(fileName) === ".ts");
const typedRuleIds = new Set();
const repeatedRuleIds = new Set();
const untypedRuleIds = [];

console.log(`Considering ${consideredRuleIds.size} rule(s).`);
await Promise.all(
fileNames.map(
async fileName => {
const ruleIds = await updateTypeDeclaration(join(dirPath, fileName));
const ruleIds = await updateTypeDeclaration(join(ruleTypeDir, fileName), consideredRuleIds);

for (const ruleId of ruleIds) {
if (typedRuleIds.has(ruleId)) {
Expand All @@ -266,7 +302,7 @@ async function updateTypeDeclaration(filePath) {
}
)
);
for (const ruleId of Object.keys(rulesMeta)) {
for (const ruleId of consideredRuleIds) {
if (!typedRuleIds.has(ruleId)) {
untypedRuleIds.push(ruleId);
}
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy