mirror of
https://github.com/pezkuwichain/pezkuwi-dev.git
synced 2026-10-31 06:39:50 +00:00
pezkuwi-dev-migrate-eslint10: rename rule keys in eslint.config.* too
A repository's own eslint config turns rules on and off by name, and a
renamed rule there is the same trap as in a disable comment: measured on
pezkuwi-extension, 'import/extensions': 'off' and, for specs,
'deprecation/deprecation': 'off' silently stopped applying, which surfaced
17 import-x/extensions and 57 deprecation findings the repository had
deliberately turned off.
The tool now renames rule keys (quoted or not) in eslint.config.{js,mjs,cjs,
ts,mts,cts}; other keys are left alone. A rule that became several
(@typescript-eslint/ban-types) needs its value repeated for each, so it is
reported for a hand edit and the tool exits 1.
This commit is contained in:
1 parent
5f2729fe34
commit
f111daff27
2 files changed
+95
-10
No files matched your search
@@ -14,7 +14,9 @@
|
|||||||
// pezkuwi-dev-migrate-eslint10 --check # report, change nothing (exit 1 if anything would change)
|
// pezkuwi-dev-migrate-eslint10 --check # report, change nothing (exit 1 if anything would change)
|
||||||
//
|
//
|
||||||
// It also rewrites such comments inside string literals, which is how code
|
// It also rewrites such comments inside string literals, which is how code
|
||||||
// generators emit them.
|
// generators emit them, and the rule keys of the repository's own
|
||||||
|
// eslint.config.* ('import/extensions': 'off'). A rule that became several
|
||||||
|
// is reported for a hand edit there, and the tool then exits 1.
|
||||||
|
|
||||||
import stylisticPlugin from '@stylistic/eslint-plugin';
|
import stylisticPlugin from '@stylistic/eslint-plugin';
|
||||||
import { builtinRules } from 'eslint/use-at-your-own-risk';
|
import { builtinRules } from 'eslint/use-at-your-own-risk';
|
||||||
@@ -96,6 +98,38 @@ const files = execFileSync('git', ['ls-files', '-z', '--', '*.ts', '*.tsx', '*.j
|
|||||||
const check = process.argv.includes('--check');
|
const check = process.argv.includes('--check');
|
||||||
let changedFiles = 0;
|
let changedFiles = 0;
|
||||||
let changedComments = 0;
|
let changedComments = 0;
|
||||||
|
let changedKeys = 0;
|
||||||
|
/** @type {string[]} */
|
||||||
|
const manual = [];
|
||||||
|
|
||||||
|
// Rule keys in a repository's own eslint config ('import/extensions': 'off'),
|
||||||
|
// quoted or not. Only in eslint.config.* files, where such a key is a rule.
|
||||||
|
const CONFIG_FILE = /(^|\/)eslint\.config\.[cm]?[jt]s$/;
|
||||||
|
const CONFIG_KEY = /(^|[{,\s])(['"]?)([@a-z][\w@/-]*)\2(\s*:)/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} file
|
||||||
|
* @param {string} src
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function renameConfigKeys (file, src) {
|
||||||
|
return src.replace(CONFIG_KEY, (/** @type {string} */ all, /** @type {string} */ pre, /** @type {string} */ _quote, /** @type {string} */ name, /** @type {string} */ colon) => {
|
||||||
|
const next = rename(name);
|
||||||
|
|
||||||
|
if (next.length === 1 && next[0] === name) {
|
||||||
|
return all;
|
||||||
|
} else if (next.length !== 1) {
|
||||||
|
// one rule became several: the value has to be repeated for each, by hand
|
||||||
|
manual.push(`${file}: '${name}' is now ${next.join(', ')}`);
|
||||||
|
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
changedKeys++;
|
||||||
|
|
||||||
|
return `${pre}'${next[0]}'${colon}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const src = fs.readFileSync(file, 'utf-8');
|
const src = fs.readFileSync(file, 'utf-8');
|
||||||
@@ -116,19 +150,27 @@ for (const file of files) {
|
|||||||
return `${kind}${gap}${next.join(', ')}${tail}`;
|
return `${kind}${gap}${next.join(', ')}${tail}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (out !== src) {
|
const outKeys = CONFIG_FILE.test(file)
|
||||||
|
? renameConfigKeys(file, out)
|
||||||
|
: out;
|
||||||
|
|
||||||
|
if (outKeys !== src) {
|
||||||
changedFiles++;
|
changedFiles++;
|
||||||
|
|
||||||
if (check) {
|
if (check) {
|
||||||
console.log(`would change: ${file}`);
|
console.log(`would change: ${file}`);
|
||||||
} else {
|
} else {
|
||||||
fs.writeFileSync(file, out);
|
fs.writeFileSync(file, outKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`${check ? 'would rewrite' : 'rewrote'} ${changedComments} directive(s) in ${changedFiles} file(s)`);
|
for (const line of manual) {
|
||||||
|
console.log(`by hand: ${line}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (check && changedFiles) {
|
console.log(`${check ? 'would rewrite' : 'rewrote'} ${changedComments} directive(s) and ${changedKeys} config key(s) in ${changedFiles} file(s)`);
|
||||||
|
|
||||||
|
if ((check && changedFiles) || manual.length) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -38,14 +38,40 @@ const header = '/* eslint-disable @stylistic/quotes */';
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function inRepo (content: string, args: string[] = []): { out: string; code: number; file: string } {
|
const CONFIG_BEFORE = `export default [
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
rules: {
|
||||||
|
'deprecation/deprecation': 'off',
|
||||||
|
"import/extensions": 'off',
|
||||||
|
indent: 'off',
|
||||||
|
'no-console': 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CONFIG_AFTER = `export default [
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-deprecated': 'off',
|
||||||
|
'import-x/extensions': 'off',
|
||||||
|
'@stylistic/indent': 'off',
|
||||||
|
'no-console': 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
`;
|
||||||
|
|
||||||
|
function inRepo (content: string, args: string[] = [], name = 'sample.ts'): { out: string; code: number; file: string } {
|
||||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'migrate-eslint10-'));
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'migrate-eslint10-'));
|
||||||
const file = path.join(dir, 'sample.ts');
|
const file = path.join(dir, name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(file, content);
|
fs.writeFileSync(file, content);
|
||||||
execFileSync('git', ['init', '-q'], { cwd: dir });
|
execFileSync('git', ['init', '-q'], { cwd: dir });
|
||||||
execFileSync('git', ['add', 'sample.ts'], { cwd: dir });
|
execFileSync('git', ['add', name], { cwd: dir });
|
||||||
|
|
||||||
let code = 0;
|
let code = 0;
|
||||||
let out = '';
|
let out = '';
|
||||||
@@ -68,14 +94,31 @@ describe('pezkuwi-dev-migrate-eslint10', (): void => {
|
|||||||
const { file, out } = inRepo(BEFORE);
|
const { file, out } = inRepo(BEFORE);
|
||||||
|
|
||||||
expect(file).toEqual(AFTER);
|
expect(file).toEqual(AFTER);
|
||||||
expect(out.includes('rewrote 8 directive(s) in 1 file(s)')).toEqual(true);
|
expect(out.includes('rewrote 8 directive(s) and 0 config key(s) in 1 file(s)')).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('leaves current rule names and bare disables alone', (): void => {
|
it('leaves current rule names and bare disables alone', (): void => {
|
||||||
const { file, out } = inRepo(AFTER);
|
const { file, out } = inRepo(AFTER);
|
||||||
|
|
||||||
expect(file).toEqual(AFTER);
|
expect(file).toEqual(AFTER);
|
||||||
expect(out.includes('rewrote 0 directive(s) in 0 file(s)')).toEqual(true);
|
expect(out.includes('rewrote 0 directive(s) and 0 config key(s) in 0 file(s)')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renames rule keys in eslint.config.*, and only rule keys', (): void => {
|
||||||
|
const { code, file, out } = inRepo(CONFIG_BEFORE, [], 'eslint.config.js');
|
||||||
|
|
||||||
|
expect(file).toEqual(CONFIG_AFTER);
|
||||||
|
expect(code).toEqual(0);
|
||||||
|
expect(out.includes('rewrote 0 directive(s) and 3 config key(s) in 1 file(s)')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves a rule that became several to a hand edit, and exits 1', (): void => {
|
||||||
|
const config = "export default [{ rules: { '@typescript-eslint/ban-types': 'off' } }];\n";
|
||||||
|
const { code, file, out } = inRepo(config, [], 'eslint.config.mjs');
|
||||||
|
|
||||||
|
expect(file).toEqual(config);
|
||||||
|
expect(code).toEqual(1);
|
||||||
|
expect(out.includes("by hand: eslint.config.mjs: '@typescript-eslint/ban-types' is now")).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('--check reports and changes nothing, exiting 1 when there is work', (): void => {
|
it('--check reports and changes nothing, exiting 1 when there is work', (): void => {
|
||||||
|
|||||||
Reference in new issue
Block a user