mirror of
https://github.com/pezkuwichain/pezkuwi-dev.git
synced 2026-11-02 23:01:06 +00:00
Remove an unused disable directive together with its line
eslint core replaces an unused directive comment with a single space, so --fix left a blank line wherever the comment sat on a line of its own. A processor now widens that fix: the whole line when the comment is alone on it, otherwise the comment and the space beside it. Fixes with a rule id, and the removal of one rule name out of several, are unchanged.
This commit is contained in:
1 parent
e972491c5e
commit
90b10558a4
3 files changed
+176
No files matched your search
@@ -0,0 +1,78 @@
|
||||
// Copyright 2017-2026 @pezkuwi/dev authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
const sources = new Map();
|
||||
|
||||
/**
|
||||
* Widens the fix eslint core gives an unused disable directive.
|
||||
*
|
||||
* Core removes only the comment and puts a single space in its place
|
||||
* (lib/linter/apply-disable-directives.js), so --fix turns
|
||||
*
|
||||
* const a = 1;
|
||||
* // eslint-disable-next-line no-console
|
||||
* const b = 2;
|
||||
*
|
||||
* into a whitespace-only line between the two statements, which the layout
|
||||
* rules then leave behind as a blank line. Measured on the consumers after the
|
||||
* eslint 10 move: 23 such lines in api alone, each removed by hand.
|
||||
*
|
||||
* Only a fix that removes a whole directive comment is changed: alone on its
|
||||
* line, the line goes; after code, the space before it goes; before code, the
|
||||
* space after it goes. Removing one rule name out of several is left as core
|
||||
* makes it, as is every message with a rule id.
|
||||
*
|
||||
* @param {import('eslint').Linter.LintMessage} message
|
||||
* @param {string} text the source the message was produced from
|
||||
* @returns {import('eslint').Linter.LintMessage}
|
||||
*/
|
||||
function widenDirectiveFix (message, text) {
|
||||
if (message.ruleId !== null || message.fix?.text.trim() !== '') {
|
||||
return message;
|
||||
}
|
||||
|
||||
let [start, end] = message.fix.range;
|
||||
|
||||
if (!/^\/[/*]/.test(text.slice(start, end))) {
|
||||
return message;
|
||||
}
|
||||
|
||||
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
||||
const newline = text.indexOf('\n', end);
|
||||
const lineEnd = newline === -1 ? text.length : newline;
|
||||
const aloneBefore = text.slice(lineStart, start).trim() === '';
|
||||
const aloneAfter = text.slice(end, lineEnd).trim() === '';
|
||||
|
||||
if (aloneBefore && aloneAfter) {
|
||||
return { ...message, fix: { range: [lineStart, newline === -1 ? text.length : newline + 1], text: '' } };
|
||||
} else if (aloneBefore) {
|
||||
while (end < lineEnd && /[ \t]/.test(text[end] ?? '')) {
|
||||
end++;
|
||||
}
|
||||
} else {
|
||||
while (start > lineStart && /[ \t]/.test(text[start - 1] ?? '')) {
|
||||
start--;
|
||||
}
|
||||
}
|
||||
|
||||
return { ...message, fix: { range: [start, end], text: '' } };
|
||||
}
|
||||
|
||||
/** @type {import('eslint').Linter.Processor} */
|
||||
export const directiveLines = {
|
||||
meta: { name: '@pezkuwi/dev/directive-lines' },
|
||||
postprocess (lists, filename) {
|
||||
const text = sources.get(filename) ?? '';
|
||||
|
||||
sources.delete(filename);
|
||||
|
||||
return lists.flat().map((m) => widenDirectiveFix(m, text));
|
||||
},
|
||||
preprocess (text, filename) {
|
||||
sources.set(filename, text);
|
||||
|
||||
return [text];
|
||||
},
|
||||
supportsAutofix: true
|
||||
};
|
||||
@@ -22,6 +22,7 @@ import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort';
|
||||
import sortDestructureKeysPlugin from 'eslint-plugin-sort-destructure-keys';
|
||||
import globals from 'globals';
|
||||
|
||||
import { directiveLines } from './eslint.directives.js';
|
||||
import { overrideAll, overrideJs, overrideJsx, overrideSpec } from './eslint.rules.js';
|
||||
import { standardRules } from './eslint.standard.js';
|
||||
import { withoutContextualAssertions } from './eslint.typeAssertion.js';
|
||||
@@ -127,6 +128,8 @@ export default [
|
||||
},
|
||||
{
|
||||
files: extsToGlobs(EXT_ALL),
|
||||
// see eslint.directives.js
|
||||
processor: directiveLines,
|
||||
rules: {
|
||||
...eslintJs.configs.recommended.rules,
|
||||
...standardRules,
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2017-2026 @pezkuwi/dev authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import { ESLint } from 'eslint';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
// Loaded by path: the config directory is plain JavaScript outside this
|
||||
// package's TypeScript root.
|
||||
const PROCESSOR = path.join(process.cwd(), 'packages/dev/config/eslint.directives.js');
|
||||
|
||||
const SOURCE = `const a = 1;
|
||||
// eslint-disable-next-line no-console
|
||||
const b = 2;
|
||||
|
||||
function f () {
|
||||
// eslint-disable-next-line no-console
|
||||
return a + b;
|
||||
}
|
||||
|
||||
f(); // eslint-disable-line no-console
|
||||
/* eslint-disable-next-line no-console */ export const c = 3;
|
||||
// eslint-disable-next-line no-alert, no-console
|
||||
console.log(c);
|
||||
`;
|
||||
|
||||
const FIXED = `const a = 1;
|
||||
const b = 2;
|
||||
|
||||
function f () {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
f();
|
||||
export const c = 3;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(c);
|
||||
`;
|
||||
|
||||
async function fix (processor?: Linter.Processor): Promise<string> {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'directives-'));
|
||||
|
||||
try {
|
||||
fs.writeFileSync(path.join(dir, 'sample.js'), SOURCE);
|
||||
|
||||
const eslint = new ESLint({
|
||||
cwd: dir,
|
||||
fix: true,
|
||||
overrideConfig: [{
|
||||
files: ['**/*.js'],
|
||||
linterOptions: { reportUnusedDisableDirectives: 'error' },
|
||||
...(processor ? { processor } : {}),
|
||||
rules: { 'no-alert': 'error', 'no-console': 'error' }
|
||||
}],
|
||||
overrideConfigFile: true
|
||||
});
|
||||
const [result] = await eslint.lintFiles(['sample.js']);
|
||||
|
||||
return result.output ?? SOURCE;
|
||||
} finally {
|
||||
fs.rmSync(dir, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe('directiveLines', (): void => {
|
||||
it('removes an unused directive with its line or the space around it', async (): Promise<void> => {
|
||||
const { directiveLines } = await import(PROCESSOR) as { directiveLines: Linter.Processor };
|
||||
|
||||
expect(await fix(directiveLines)).toEqual(FIXED);
|
||||
});
|
||||
|
||||
it('is needed: core leaves a space where each comment was', async (): Promise<void> => {
|
||||
expect((await fix()).split('\n')).toEqual([
|
||||
'const a = 1;',
|
||||
' ',
|
||||
'const b = 2;',
|
||||
'',
|
||||
'function f () {',
|
||||
' ',
|
||||
' return a + b;',
|
||||
'}',
|
||||
'',
|
||||
'f(); ',
|
||||
' export const c = 3;',
|
||||
'// eslint-disable-next-line no-console',
|
||||
'console.log(c);',
|
||||
''
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user