// Copyright 2017-2026 @pezkuwi/dev authors & contributors // SPDX-License-Identifier: Apache-2.0 /// import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; const SCRIPT = path.join(process.cwd(), 'packages/dev/scripts/pezkuwi-dev-migrate-eslint10.mjs'); // Spelt so that the tool, run on this repository, does not rewrite its own // fixture: the source never contains the directive it is testing. const D = 'eslint-' + 'disable'; const BEFORE = `/* ${D} quotes */ /* ${D} quote-props */ // ${D}-next-line deprecation/deprecation // ${D}-next-line import/export -- types and values share the name // ${D}-next-line @typescript-eslint/no-empty-interface,no-use-before-define // ${D}-next-line indent, @typescript-eslint/indent // ${D}-next-line react/jsx-max-props-per-line const header = '/* ${D} quotes */'; /* ${D} */ // ${D}-next-line @typescript-eslint/no-explicit-any `; const AFTER = `/* eslint-disable @stylistic/quotes */ /* eslint-disable @stylistic/quote-props */ // eslint-disable-next-line @typescript-eslint/no-deprecated // eslint-disable-next-line import-x/export -- types and values share the name // eslint-disable-next-line @typescript-eslint/no-empty-object-type, no-use-before-define // eslint-disable-next-line @stylistic/indent // eslint-disable-next-line @stylistic/jsx-max-props-per-line const header = '/* eslint-disable @stylistic/quotes */'; /* eslint-disable */ // eslint-disable-next-line @typescript-eslint/no-explicit-any `; 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 file = path.join(dir, name); try { fs.writeFileSync(file, content); execFileSync('git', ['init', '-q'], { cwd: dir }); execFileSync('git', ['add', name], { cwd: dir }); let code = 0; let out = ''; try { out = execFileSync(process.execPath, [SCRIPT, ...args], { cwd: dir, encoding: 'utf-8' }); } catch (error) { code = (error as { status: number }).status; out = (error as { stdout: string }).stdout; } return { code, file: fs.readFileSync(file, 'utf-8'), out }; } finally { fs.rmSync(dir, { force: true, recursive: true }); } } describe('pezkuwi-dev-migrate-eslint10', (): void => { it('renames rules in disable comments, and in strings that emit them', (): void => { const { file, out } = inRepo(BEFORE); expect(file).toEqual(AFTER); 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 => { const { file, out } = inRepo(AFTER); expect(file).toEqual(AFTER); 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 => { const { code, file } = inRepo(BEFORE, ['--check']); expect(file).toEqual(BEFORE); expect(code).toEqual(1); }); });