From 13635ea8ddd44bbf374d9c87a6e10267827582ba Mon Sep 17 00:00:00 2001 From: Boris Shuliak Date: Sat, 3 Jan 2026 12:54:28 +0100 Subject: [PATCH 1/2] feat: replace hardcoded eslint-disabling comment line with provided --disablingComment option --- src/commands/check.ts | 2 + src/commands/checkStaged.ts | 2 + src/commands/disable.ts | 8 +- src/constants/disabling-comments.ts | 1 - src/index.ts | 12 ++ .../__tests__/checkFilePaths.test.ts | 127 +++++++----------- .../core/checkFilePaths/checkFilePaths.ts | 8 +- .../checkFiles/__tests__/checkFiles.test.ts | 81 ++++------- src/utils/core/checkFiles/checkFiles.ts | 24 ++-- .../__tests__/checkStagedFiles.test.ts | 94 +++++-------- .../core/checkStagedFiles/checkStagedFiles.ts | 28 ++-- .../__tests__/disableFiles.test.ts | 41 ++++-- src/utils/core/disableFiles/disableFiles.ts | 32 ++--- 13 files changed, 200 insertions(+), 260 deletions(-) delete mode 100644 src/constants/disabling-comments.ts diff --git a/src/commands/check.ts b/src/commands/check.ts index ae6119e..0c84c71 100644 --- a/src/commands/check.ts +++ b/src/commands/check.ts @@ -5,6 +5,7 @@ import { checkFiles } from "../utils/core/checkFiles/checkFiles"; export const check = async (options: { rootDir?: string; pattern?: string[]; + disablingComment: string; }) => { try { const rootDir = options.rootDir || "./"; @@ -17,6 +18,7 @@ export const check = async (options: { await checkFiles({ rootDir, filesRegex, + disablingComment: options.disablingComment, }); console.log(SUCCESS.cleanFiles()); diff --git a/src/commands/checkStaged.ts b/src/commands/checkStaged.ts index 2aa223a..f1b9031 100644 --- a/src/commands/checkStaged.ts +++ b/src/commands/checkStaged.ts @@ -5,6 +5,7 @@ import { checkStagedFiles } from "../utils/core/checkStagedFiles/checkStagedFile export const checkStaged = async (options: { rootDir?: string; pattern?: string[]; + disablingComment: string; }) => { try { const rootDir = options.rootDir || "./"; @@ -17,6 +18,7 @@ export const checkStaged = async (options: { await checkStagedFiles({ rootDir, filesRegex, + disablingComment: options.disablingComment, onFileProcessed: (filePath) => { console.log(INFO.fileChecked(filePath)); }, diff --git a/src/commands/disable.ts b/src/commands/disable.ts index df3ff04..dc94efb 100644 --- a/src/commands/disable.ts +++ b/src/commands/disable.ts @@ -1,16 +1,18 @@ import { SUCCESS } from "../constants/messages"; import { disableFiles } from "../utils/core/disableFiles/disableFiles"; -export const disable = async (options?: { +export const disable = async (options: { rootDir?: string; pattern?: string[]; + disablingComment: string; }) => { - const processedPatterns = options?.pattern?.map( + const processedPatterns = options.pattern?.map( (pattern) => new RegExp(pattern), ); await disableFiles({ - rootDir: options?.rootDir, + rootDir: options.rootDir, filesRegex: processedPatterns, + disablingComment: options.disablingComment, onFileProcessed: (filePath) => { console.info(SUCCESS.disableFile(filePath)); }, diff --git a/src/constants/disabling-comments.ts b/src/constants/disabling-comments.ts deleted file mode 100644 index b93f607..0000000 --- a/src/constants/disabling-comments.ts +++ /dev/null @@ -1 +0,0 @@ -export const ESLINT_DISABLE_FILES = "/* eslint-disable */"; diff --git a/src/index.ts b/src/index.ts index 213c518..890d25b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,6 +28,10 @@ program "-p, --pattern ", "regex pattern to match files against (e.g. \\.[cm]?[jt]sx?$ \\.test\\.[cm]?[jt]sx?$)", ) + .option( + "-d, --disablingComment ", + "Disabling comment that will be added to the top of the file, (e.g. \/* eslint-disable *\/)", + ) .action(disable); program @@ -40,6 +44,10 @@ program "-p, --pattern ", "regex pattern to match files against (e.g. \\.[cm]?[jt]sx?$ \\.test\\.[cm]?[jt]sx?$)", ) + .option( + "-d, --disablingComment ", + "Disabling comment that will be checked in the file, (e.g. \/* eslint-disable *\/)", + ) .action(check); program @@ -52,6 +60,10 @@ program "-p, --pattern ", "regex pattern to match files against (e.g. \\.[cm]?[jt]sx?$ \\.test\\.[cm]?[jt]sx?$)", ) + .option( + "-d, --disablingComment ", + "Disabling comment that will be checked in the file, (e.g. \/* eslint-disable *\/)", + ) .action(checkStaged); program.parse(); diff --git a/src/utils/core/checkFilePaths/__tests__/checkFilePaths.test.ts b/src/utils/core/checkFilePaths/__tests__/checkFilePaths.test.ts index a097556..e825f30 100644 --- a/src/utils/core/checkFilePaths/__tests__/checkFilePaths.test.ts +++ b/src/utils/core/checkFilePaths/__tests__/checkFilePaths.test.ts @@ -1,5 +1,4 @@ import path from "node:path"; -import { ESLINT_DISABLE_FILES } from "../../../../constants/disabling-comments"; import { ERRORS } from "../../../../constants/messages"; import * as readFileStreamModule from "../../../fs/readFileStream/readFileStream"; import { checkFilePaths } from "../checkFilePaths"; @@ -8,6 +7,8 @@ vi.mock("node:path"); vi.mock("../../../fs/readFileStream/readFileStream"); describe("checkFilePaths", () => { + const MOCK_COMMENT = "/* test-disable */"; + beforeEach(() => { vi.resetAllMocks(); }); @@ -15,13 +16,17 @@ describe("checkFilePaths", () => { it("should resolve when no files to check", async () => { expect.hasAssertions(); - await expect(checkFilePaths()).resolves.toBeUndefined(); + // Passing required disablingComment even for empty checks + await expect( + checkFilePaths({ disablingComment: MOCK_COMMENT }), + ).resolves.toBeUndefined(); + await expect( - checkFilePaths({ filePathsToCheck: [] }), + checkFilePaths({ filePathsToCheck: [], disablingComment: MOCK_COMMENT }), ).resolves.toBeUndefined(); }); - it("should resolve when files do not contain eslint-disable comment", async () => { + it("should resolve when files do not contain the provided disabling comment", async () => { expect.hasAssertions(); const mockFiles = ["file1.ts", "file2.ts"]; @@ -35,15 +40,19 @@ describe("checkFilePaths", () => { ); await expect( - checkFilePaths({ filePathsToCheck: mockFiles }), + checkFilePaths({ + filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, + }), ).resolves.toBeUndefined(); }); - it("should reject when files contain eslint-disable comment", async () => { + it("should reject when files contain the provided disabling comment", async () => { expect.hasAssertions(); const mockFiles = ["file1.ts"]; - const mockContent = `${ESLINT_DISABLE_FILES}\nexport const test = true;`; + // Use MOCK_COMMENT here instead of hardcoded constant + const mockContent = `${MOCK_COMMENT}\nexport const test = true;`; vi.mocked(path.resolve).mockReturnValue("/absolute/path/file1.ts"); vi.spyOn(readFileStreamModule, "readFileStream").mockImplementation( @@ -53,15 +62,18 @@ describe("checkFilePaths", () => { ); await expect( - checkFilePaths({ filePathsToCheck: mockFiles }), + checkFilePaths({ + filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, + }), ).rejects.toThrow(ERRORS.disableFoundError("/absolute/path/file1.ts")); }); - it("should handle files with only eslint-disable comment and whitespace", async () => { + it("should handle files with only the disabling comment and whitespace", async () => { expect.hasAssertions(); const mockFiles = ["file1.ts"]; - const mockContent = ` ${ESLINT_DISABLE_FILES} \n\nexport const test = true;`; + const mockContent = ` ${MOCK_COMMENT} \n\nexport const test = true;`; vi.mocked(path.resolve).mockReturnValue("/absolute/path/file1.ts"); vi.spyOn(readFileStreamModule, "readFileStream").mockImplementation( @@ -71,7 +83,10 @@ describe("checkFilePaths", () => { ); await expect( - checkFilePaths({ filePathsToCheck: mockFiles }), + checkFilePaths({ + filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, + }), ).rejects.toThrow(ERRORS.disableFoundError("/absolute/path/file1.ts")); }); @@ -89,16 +104,19 @@ describe("checkFilePaths", () => { ); await expect( - checkFilePaths({ filePathsToCheck: mockFiles }), + checkFilePaths({ + filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, + }), ).rejects.toThrow(ERRORS.readFileError("/absolute/path/file1.ts")); }); - it("should handle multiple files with mixed results", async () => { + it("should handle multiple files with mixed results using the comment", async () => { expect.hasAssertions(); const mockFiles = ["file1.ts", "file2.ts", "file3.ts"]; const cleanContent = "export const test = true;"; - const eslintDisableContent = `${ESLINT_DISABLE_FILES}\nexport const test = true;`; + const dirtyContent = `${MOCK_COMMENT}\nexport const test = true;`; vi.mocked(path.resolve) .mockReturnValueOnce("/absolute/path/file1.ts") @@ -109,10 +127,8 @@ describe("checkFilePaths", () => { (filePath, callback) => { if (filePath === "/absolute/path/file1.ts") { callback(null, cleanContent); - } else if (filePath === "/absolute/path/file2.ts") { - callback(null, eslintDisableContent); } else { - callback(null, eslintDisableContent); + callback(null, dirtyContent); } }, ); @@ -123,59 +139,14 @@ describe("checkFilePaths", () => { ].join("\n"); await expect( - checkFilePaths({ filePathsToCheck: mockFiles }), - ).rejects.toThrow(expectedError); - }); - - it("should handle multiple files with mixed errors (read errors and eslint-disable)", async () => { - expect.hasAssertions(); - - const mockFiles = ["file1.ts", "file2.ts"]; - const mockError = new Error("Read error"); - const eslintDisableContent = `${ESLINT_DISABLE_FILES}\nexport const test = true;`; - - vi.mocked(path.resolve) - .mockReturnValueOnce("/absolute/path/file1.ts") - .mockReturnValueOnce("/absolute/path/file2.ts"); - - vi.spyOn(readFileStreamModule, "readFileStream").mockImplementation( - (filePath, callback) => { - if (filePath === "/absolute/path/file1.ts") { - callback(mockError, null); - } else { - callback(null, eslintDisableContent); - } - }, - ); - - const expectedError = [ - ERRORS.readFileError("/absolute/path/file1.ts"), - ERRORS.disableFoundError("/absolute/path/file2.ts"), - ].join("\n"); - - await expect( - checkFilePaths({ filePathsToCheck: mockFiles }), + checkFilePaths({ + filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, + }), ).rejects.toThrow(expectedError); }); - it("should handle empty file content", async () => { - expect.hasAssertions(); - - const mockFiles = ["file1.ts"]; - - vi.mocked(path.resolve).mockReturnValue("/absolute/path/file1.ts"); - vi.spyOn(readFileStreamModule, "readFileStream").mockImplementation( - (_, callback) => { - callback(null, ""); - }, - ); - - await expect( - checkFilePaths({ filePathsToCheck: mockFiles }), - ).resolves.toBeUndefined(); - }); - - it("should handle null file content", async () => { + it("should handle empty or null file content", async () => { expect.hasAssertions(); const mockFiles = ["file1.ts"]; @@ -183,25 +154,25 @@ describe("checkFilePaths", () => { vi.mocked(path.resolve).mockReturnValue("/absolute/path/file1.ts"); vi.spyOn(readFileStreamModule, "readFileStream").mockImplementation( (_, callback) => { - callback(null, null); + callback(null, ""); // Test empty string }, ); await expect( - checkFilePaths({ filePathsToCheck: mockFiles }), + checkFilePaths({ + filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, + }), ).resolves.toBeUndefined(); }); - it("should resolve absolute paths for files", async () => { + it("should resolve absolute paths for files and pass comment logic", async () => { expect.hasAssertions(); - const mockFiles = ["./relative/file1.ts", "../parent/file2.ts"]; + const mockFiles = ["./relative/file1.ts"]; const mockContent = "export const test = true;"; - vi.mocked(path.resolve) - .mockReturnValueOnce("/absolute/path/relative/file1.ts") - .mockReturnValueOnce("/absolute/path/parent/file2.ts"); - + vi.mocked(path.resolve).mockReturnValue("/absolute/path/relative/file1.ts"); vi.spyOn(readFileStreamModule, "readFileStream").mockImplementation( (_, callback) => { callback(null, mockContent); @@ -209,10 +180,12 @@ describe("checkFilePaths", () => { ); await expect( - checkFilePaths({ filePathsToCheck: mockFiles }), + checkFilePaths({ + filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, + }), ).resolves.toBeUndefined(); expect(path.resolve).toHaveBeenCalledWith("./relative/file1.ts"); - expect(path.resolve).toHaveBeenCalledWith("../parent/file2.ts"); }); }); diff --git a/src/utils/core/checkFilePaths/checkFilePaths.ts b/src/utils/core/checkFilePaths/checkFilePaths.ts index 84eb835..0a1b53d 100644 --- a/src/utils/core/checkFilePaths/checkFilePaths.ts +++ b/src/utils/core/checkFilePaths/checkFilePaths.ts @@ -1,5 +1,4 @@ import path from "node:path"; -import { ESLINT_DISABLE_FILES } from "../../../constants/disabling-comments"; import { ERRORS } from "../../../constants/messages"; import { readFileStream } from "../../fs/readFileStream/readFileStream"; @@ -7,13 +6,16 @@ import { readFileStream } from "../../fs/readFileStream/readFileStream"; * Checks if files contain the disabling comment and throws an error if they do * @param {Object} options - Configuration options * @param {string[]} [options.filePathsToCheck=[]] - Array of file paths to check + * @param {string} [options.disablingComment] - Disabling comment that will be checked in the file, e.g. \/* eslint-disable *\/ * @returns {Promise} Promise that resolves when all files have been checked, or rejects if any file contains disabling comment */ export const checkFilePaths = async ({ filePathsToCheck = [], + disablingComment, }: { filePathsToCheck?: string[]; -} = {}) => { + disablingComment: string; +}) => { const errors: string[] = []; return new Promise((resolve, reject) => { @@ -33,7 +35,7 @@ export const checkFilePaths = async ({ errors.push(ERRORS.readFileError(filePath)); } else if (data) { const content = data.toString(); - if (content.trim().startsWith(ESLINT_DISABLE_FILES)) { + if (content.trim().startsWith(disablingComment)) { errors.push(ERRORS.disableFoundError(filePath)); } } diff --git a/src/utils/core/checkFiles/__tests__/checkFiles.test.ts b/src/utils/core/checkFiles/__tests__/checkFiles.test.ts index 7490eef..25b614f 100644 --- a/src/utils/core/checkFiles/__tests__/checkFiles.test.ts +++ b/src/utils/core/checkFiles/__tests__/checkFiles.test.ts @@ -1,3 +1,5 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + import { DEFAULT_LINTED_FILE_REGEX } from "../../../../constants/regex"; import * as getDeepFilesFromDirModule from "../../../fs/getDeepFilesFromDir/getDeepFilesFromDir"; import * as checkFilePathsModule from "../../checkFilePaths/checkFilePaths"; @@ -7,6 +9,8 @@ vi.mock("../../../fs/getDeepFilesFromDir/getDeepFilesFromDir"); vi.mock("../../checkFilePaths/checkFilePaths"); describe("checkFiles", () => { + const MOCK_COMMENT = "/* custom-disable-comment */"; + beforeEach(() => { vi.resetAllMocks(); }); @@ -19,7 +23,9 @@ describe("checkFiles", () => { ); vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); - await expect(checkFiles()).resolves.toBeUndefined(); + await expect( + checkFiles({ disablingComment: MOCK_COMMENT }), + ).resolves.toBeUndefined(); expect(getDeepFilesFromDirModule.getDeepFilesFromDir).toHaveBeenCalledWith( "./", @@ -27,6 +33,7 @@ describe("checkFiles", () => { ); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: [], + disablingComment: MOCK_COMMENT, }); }); @@ -40,14 +47,13 @@ describe("checkFiles", () => { ); vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); - await expect(checkFiles()).resolves.toBeUndefined(); + await expect( + checkFiles({ disablingComment: MOCK_COMMENT }), + ).resolves.toBeUndefined(); - expect(getDeepFilesFromDirModule.getDeepFilesFromDir).toHaveBeenCalledWith( - "./", - [DEFAULT_LINTED_FILE_REGEX], - ); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, }); }); @@ -63,7 +69,7 @@ describe("checkFiles", () => { vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); await expect( - checkFiles({ rootDir: customRootDir }), + checkFiles({ rootDir: customRootDir, disablingComment: MOCK_COMMENT }), ).resolves.toBeUndefined(); expect(getDeepFilesFromDirModule.getDeepFilesFromDir).toHaveBeenCalledWith( @@ -72,6 +78,7 @@ describe("checkFiles", () => { ); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, }); }); @@ -87,7 +94,7 @@ describe("checkFiles", () => { vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); await expect( - checkFiles({ filesRegex: customRegex }), + checkFiles({ filesRegex: customRegex, disablingComment: MOCK_COMMENT }), ).resolves.toBeUndefined(); expect(getDeepFilesFromDirModule.getDeepFilesFromDir).toHaveBeenCalledWith( @@ -96,31 +103,7 @@ describe("checkFiles", () => { ); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: mockFiles, - }); - }); - - it("should use both custom rootDir and filesRegex", async () => { - expect.hasAssertions(); - - const customRootDir = "/custom/path"; - const customRegex = [/\.ts$/, /\.js$/]; - const mockFiles = ["/custom/path/file1.ts", "/custom/path/file2.js"]; - - vi.spyOn(getDeepFilesFromDirModule, "getDeepFilesFromDir").mockReturnValue( - mockFiles, - ); - vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); - - await expect( - checkFiles({ rootDir: customRootDir, filesRegex: customRegex }), - ).resolves.toBeUndefined(); - - expect(getDeepFilesFromDirModule.getDeepFilesFromDir).toHaveBeenCalledWith( - customRootDir, - customRegex, - ); - expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ - filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, }); }); @@ -137,7 +120,9 @@ describe("checkFiles", () => { mockError, ); - await expect(checkFiles()).rejects.toThrow(mockError); + await expect( + checkFiles({ disablingComment: MOCK_COMMENT }), + ).rejects.toThrow(mockError); }); it("should handle multiple regex patterns", async () => { @@ -152,36 +137,15 @@ describe("checkFiles", () => { vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); await expect( - checkFiles({ filesRegex: multipleRegex }), + checkFiles({ filesRegex: multipleRegex, disablingComment: MOCK_COMMENT }), ).resolves.toBeUndefined(); - expect(getDeepFilesFromDirModule.getDeepFilesFromDir).toHaveBeenCalledWith( - "./", - multipleRegex, - ); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: mockFiles, + disablingComment: MOCK_COMMENT, }); }); - it("should use DEFAULT_LINTED_FILE_REGEX when no filesRegex provided", async () => { - expect.hasAssertions(); - - const mockFiles = ["/path/to/file1.ts"]; - - vi.spyOn(getDeepFilesFromDirModule, "getDeepFilesFromDir").mockReturnValue( - mockFiles, - ); - vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); - - await expect(checkFiles()).resolves.toBeUndefined(); - - expect(getDeepFilesFromDirModule.getDeepFilesFromDir).toHaveBeenCalledWith( - "./", - [DEFAULT_LINTED_FILE_REGEX], - ); - }); - it("should handle empty results from getDeepFilesFromDir", async () => { expect.hasAssertions(); @@ -191,11 +155,12 @@ describe("checkFiles", () => { vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); await expect( - checkFiles({ rootDir: "/empty/dir" }), + checkFiles({ rootDir: "/empty/dir", disablingComment: MOCK_COMMENT }), ).resolves.toBeUndefined(); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: [], + disablingComment: MOCK_COMMENT, }); }); }); diff --git a/src/utils/core/checkFiles/checkFiles.ts b/src/utils/core/checkFiles/checkFiles.ts index dbcf8d2..77d4283 100644 --- a/src/utils/core/checkFiles/checkFiles.ts +++ b/src/utils/core/checkFiles/checkFiles.ts @@ -7,23 +7,21 @@ import { checkFilePaths } from "../checkFilePaths/checkFilePaths"; * @param {Object} options - Configuration options * @param {string} [options.rootDir='./'] - Directory path to start searching from * @param {RegExp[]} [options.filesRegex=[DEFAULT_LINTED_FILE_REGEX]] - Array of RegExp patterns to match files against + * @param {string} [options.disablingComment] - Disabling comment that will be checked in the file, e.g. \/* eslint-disable *\/ * @returns {Promise} Promise that resolves when all files have been checked, or rejects if any file contains disabling comment */ -export const checkFiles = async ( - { - rootDir = "./", - filesRegex = [DEFAULT_LINTED_FILE_REGEX], - }: { - rootDir?: string; - filesRegex?: RegExp[]; - } = { - rootDir: "./", - filesRegex: [DEFAULT_LINTED_FILE_REGEX], - }, -) => { +export const checkFiles = async ({ + rootDir = "./", + filesRegex = [DEFAULT_LINTED_FILE_REGEX], + disablingComment, +}: { + rootDir?: string; + filesRegex?: RegExp[]; + disablingComment: string; +}) => { // Get all files from the directory that match the regex patterns const filePathsToCheck = getDeepFilesFromDir(rootDir, filesRegex); // Check the files for eslint-disable comments - return checkFilePaths({ filePathsToCheck }); + return checkFilePaths({ filePathsToCheck, disablingComment }); }; diff --git a/src/utils/core/checkStagedFiles/__tests__/checkStagedFiles.test.ts b/src/utils/core/checkStagedFiles/__tests__/checkStagedFiles.test.ts index 8c6152d..badb3d9 100644 --- a/src/utils/core/checkStagedFiles/__tests__/checkStagedFiles.test.ts +++ b/src/utils/core/checkStagedFiles/__tests__/checkStagedFiles.test.ts @@ -8,6 +8,8 @@ vi.mock("../../../git/getStagedFiles"); vi.mock("../../checkFilePaths/checkFilePaths"); describe("checkStagedFiles", () => { + const MOCK_COMMENT = "/* eslint-disable */"; + beforeEach(() => { vi.resetAllMocks(); }); @@ -18,11 +20,14 @@ describe("checkStagedFiles", () => { vi.spyOn(getStagedFilesModule, "getStagedFiles").mockReturnValue([]); vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); - await expect(checkStagedFiles()).resolves.toBeUndefined(); + await expect( + checkStagedFiles({ disablingComment: MOCK_COMMENT }), + ).resolves.toBeUndefined(); expect(getStagedFilesModule.getStagedFiles).toHaveBeenCalledWith("./"); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: [], + disablingComment: MOCK_COMMENT, }); }); @@ -46,11 +51,13 @@ describe("checkStagedFiles", () => { ); vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); - await expect(checkStagedFiles()).resolves.toBeUndefined(); + await expect( + checkStagedFiles({ disablingComment: MOCK_COMMENT }), + ).resolves.toBeUndefined(); - expect(getStagedFilesModule.getStagedFiles).toHaveBeenCalledWith("./"); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: expectedFilteredFiles, + disablingComment: MOCK_COMMENT, }); }); @@ -63,7 +70,10 @@ describe("checkStagedFiles", () => { vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); await expect( - checkStagedFiles({ rootDir: customRootDir }), + checkStagedFiles({ + rootDir: customRootDir, + disablingComment: MOCK_COMMENT, + }), ).resolves.toBeUndefined(); expect(getStagedFilesModule.getStagedFiles).toHaveBeenCalledWith( @@ -88,11 +98,15 @@ describe("checkStagedFiles", () => { vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); await expect( - checkStagedFiles({ filesRegex: customRegex }), + checkStagedFiles({ + filesRegex: customRegex, + disablingComment: MOCK_COMMENT, + }), ).resolves.toBeUndefined(); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: expectedFilteredFiles, + disablingComment: MOCK_COMMENT, }); }); @@ -113,32 +127,15 @@ describe("checkStagedFiles", () => { vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); await expect( - checkStagedFiles({ onFileProcessed: mockOnFileProcessed }), + checkStagedFiles({ + onFileProcessed: mockOnFileProcessed, + disablingComment: MOCK_COMMENT, + }), ).resolves.toBeUndefined(); expect(mockOnFileProcessed).toHaveBeenCalledTimes( expectedFilteredFiles.length, ); - expect(mockOnFileProcessed).toHaveBeenCalledWith(expectedFilteredFiles[0]); - expect(mockOnFileProcessed).toHaveBeenCalledWith(expectedFilteredFiles[1]); - }); - - it("should not call onFileProcessed when not provided", async () => { - expect.hasAssertions(); - - const mockStagedFiles = ["/path/to/file1.ts"]; - - vi.spyOn(getStagedFilesModule, "getStagedFiles").mockReturnValue( - mockStagedFiles, - ); - vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); - - await expect(checkStagedFiles()).resolves.toBeUndefined(); - - // Should not throw any errors even without onFileProcessed - expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ - filePathsToCheck: ["/path/to/file1.ts"], - }); }); it("should propagate errors from checkFilePaths", async () => { @@ -154,7 +151,9 @@ describe("checkStagedFiles", () => { mockError, ); - await expect(checkStagedFiles()).rejects.toThrow(mockError); + await expect( + checkStagedFiles({ disablingComment: MOCK_COMMENT }), + ).rejects.toThrow(mockError); }); it("should handle multiple regex patterns", async () => { @@ -175,46 +174,15 @@ describe("checkStagedFiles", () => { vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); await expect( - checkStagedFiles({ filesRegex: multipleRegex }), + checkStagedFiles({ + filesRegex: multipleRegex, + disablingComment: MOCK_COMMENT, + }), ).resolves.toBeUndefined(); expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ filePathsToCheck: expectedFilteredFiles, - }); - }); - - it("should use DEFAULT_LINTED_FILE_REGEX when no filesRegex provided", async () => { - expect.hasAssertions(); - - const mockStagedFiles = ["/path/to/file1.ts"]; - - vi.spyOn(getStagedFilesModule, "getStagedFiles").mockReturnValue( - mockStagedFiles, - ); - vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); - - await expect(checkStagedFiles()).resolves.toBeUndefined(); - - // File should be filtered using DEFAULT_LINTED_FILE_REGEX - expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ - filePathsToCheck: ["/path/to/file1.ts"], - }); - }); - - it("should handle empty string files from git output", async () => { - expect.hasAssertions(); - - const mockStagedFiles = ["/path/to/file1.ts"]; - - vi.spyOn(getStagedFilesModule, "getStagedFiles").mockReturnValue( - mockStagedFiles, - ); - vi.spyOn(checkFilePathsModule, "checkFilePaths").mockResolvedValue(); - - await expect(checkStagedFiles()).resolves.toBeUndefined(); - - expect(checkFilePathsModule.checkFilePaths).toHaveBeenCalledWith({ - filePathsToCheck: ["/path/to/file1.ts"], + disablingComment: MOCK_COMMENT, }); }); }); diff --git a/src/utils/core/checkStagedFiles/checkStagedFiles.ts b/src/utils/core/checkStagedFiles/checkStagedFiles.ts index 232d08b..8ead774 100644 --- a/src/utils/core/checkStagedFiles/checkStagedFiles.ts +++ b/src/utils/core/checkStagedFiles/checkStagedFiles.ts @@ -11,25 +11,24 @@ import { checkFilePaths } from "../checkFilePaths/checkFilePaths"; * @param {RegExp[]} [options.filesRegex=[DEFAULT_LINTED_FILE_REGEX]] * Array of RegExp patterns to filter files * @param {function} [options.onFileProcessed] + * Disabling comment that will be checked in the file, e.g. \/* eslint-disable *\/ + * @param {string} [options.disablingComment] * Callback function called after each file is processed with the file path * @returns {Promise} * Promise that resolves when all staged files have been checked, * or rejects if any file contains disabling comment */ -export const checkStagedFiles = async ( - { - rootDir = "./", - filesRegex = [DEFAULT_LINTED_FILE_REGEX], - onFileProcessed, - }: { - rootDir?: string; - filesRegex?: RegExp[]; - onFileProcessed?: (filePath: string) => void; - } = { - rootDir: "./", - filesRegex: [DEFAULT_LINTED_FILE_REGEX], - }, -) => { +export const checkStagedFiles = async ({ + rootDir = "./", + filesRegex = [DEFAULT_LINTED_FILE_REGEX], + onFileProcessed, + disablingComment, +}: { + rootDir?: string; + filesRegex?: RegExp[]; + onFileProcessed?: (filePath: string) => void; + disablingComment: string; +}) => { // Get all staged files const stagedFiles = getStagedFiles(rootDir); @@ -41,6 +40,7 @@ export const checkStagedFiles = async ( // Check the filtered files for eslint-disable comments const result = await checkFilePaths({ filePathsToCheck: filteredFiles, + disablingComment, }); // Call onFileProcessed for each file if provided diff --git a/src/utils/core/disableFiles/__tests__/disableFiles.test.ts b/src/utils/core/disableFiles/__tests__/disableFiles.test.ts index 8a22802..ff0597a 100644 --- a/src/utils/core/disableFiles/__tests__/disableFiles.test.ts +++ b/src/utils/core/disableFiles/__tests__/disableFiles.test.ts @@ -18,16 +18,18 @@ describe("disableFiles", () => { .spyOn(console, "error") .mockImplementation(() => {}); + const MOCK_COMMENT = "/* test-disable-comment */"; + beforeEach(() => { vi.resetAllMocks(); }); - it("should add eslint-disable to files that do not have it", async () => { + it("should add the provided disablingComment to files that do not have it", async () => { expect.hasAssertions(); const mockFiles = ["file1.ts", "file2.ts"]; const mockContent = "export const test = true;"; - const expectedContent = `/* eslint-disable */\n\n${mockContent}`; + const expectedContent = `${MOCK_COMMENT}\n\n${mockContent}`; vi.spyOn(getDeepFilesFromDirModule, "getDeepFilesFromDir").mockReturnValue( mockFiles, @@ -38,12 +40,16 @@ describe("disableFiles", () => { callback(null, mockContent); }, ); + // @ts-expect-error - types are not needed here vi.mocked(fs.writeFile).mockImplementation((_, __, ___, callback) => { callback(null); }); - await disableFiles({ filesRegex: [/\.ts$/] }); + await disableFiles({ + filesRegex: [/\.ts$/], + disablingComment: MOCK_COMMENT, + }); expect(fs.writeFile).toHaveBeenCalledWith( expect.stringContaining("file"), @@ -54,11 +60,11 @@ describe("disableFiles", () => { expect(fs.writeFile).toHaveBeenCalledTimes(2); }); - it("should skip files that already have eslint-disable", async () => { + it("should skip files that already have the disablingComment", async () => { expect.hasAssertions(); const mockFiles = ["file1.ts"]; - const mockContent = "/* eslint-disable */\nexport const test = true;"; + const mockContent = `${MOCK_COMMENT}\nexport const test = true;`; vi.spyOn(getDeepFilesFromDirModule, "getDeepFilesFromDir").mockReturnValue( mockFiles, @@ -70,7 +76,11 @@ describe("disableFiles", () => { }, ); - await disableFiles({ rootDir: "./", filesRegex: [/\.ts$/] }); + await disableFiles({ + rootDir: "./", + filesRegex: [/\.ts$/], + disablingComment: MOCK_COMMENT, + }); expect(fs.writeFile).not.toHaveBeenCalled(); }); @@ -79,7 +89,6 @@ describe("disableFiles", () => { expect.hasAssertions(); const mockFiles = ["file1.ts"]; - const mockContent = "/* eslint-disable */\nexport const test = true;"; vi.spyOn(getDeepFilesFromDirModule, "getDeepFilesFromDir").mockReturnValue( mockFiles, @@ -91,7 +100,11 @@ describe("disableFiles", () => { }, ); - await disableFiles({ rootDir: "./", filesRegex: [/\.ts$/] }); + await disableFiles({ + rootDir: "./", + filesRegex: [/\.ts$/], + disablingComment: MOCK_COMMENT, + }); expect(fs.writeFile).not.toHaveBeenCalled(); }); @@ -112,7 +125,11 @@ describe("disableFiles", () => { }, ); - await disableFiles({ rootDir: "./", filesRegex: [/\.ts$/] }); + await disableFiles({ + rootDir: "./", + filesRegex: [/\.ts$/], + disablingComment: MOCK_COMMENT, + }); expect(mockConsoleError).toHaveBeenCalledWith( ERRORS.readFileError("./file1.ts"), @@ -137,6 +154,7 @@ describe("disableFiles", () => { callback(null, mockContent); }, ); + // @ts-expect-error - types are not needed here vi.mocked(fs.writeFile).mockImplementation((_, __, ___, callback) => { callback(mockError); @@ -146,6 +164,7 @@ describe("disableFiles", () => { rootDir: "./", filesRegex: [/\.ts$/], onFileProcessed: mockOnFileProcessed, + disablingComment: MOCK_COMMENT, }); expect(mockConsoleError).toHaveBeenCalledWith( @@ -154,7 +173,7 @@ describe("disableFiles", () => { expect(mockOnFileProcessed).not.toHaveBeenCalled(); }); - it("should call onFileProcessed for each file processed", async () => { + it("should call onFileProcessed for each file processed successfully", async () => { expect.hasAssertions(); const mockFiles = ["file1.ts"]; @@ -169,6 +188,7 @@ describe("disableFiles", () => { callback(null, "content"); }, ); + // @ts-expect-error - types are not needed here vi.mocked(fs.writeFile).mockImplementation((_, __, ___, callback) => { callback(null); @@ -178,6 +198,7 @@ describe("disableFiles", () => { rootDir: "./", filesRegex: [/\.ts$/], onFileProcessed: mockOnFileProcessed, + disablingComment: MOCK_COMMENT, }); expect(mockOnFileProcessed).toHaveBeenCalledTimes(1); diff --git a/src/utils/core/disableFiles/disableFiles.ts b/src/utils/core/disableFiles/disableFiles.ts index f7394e9..918a01e 100644 --- a/src/utils/core/disableFiles/disableFiles.ts +++ b/src/utils/core/disableFiles/disableFiles.ts @@ -1,6 +1,5 @@ import fs from "node:fs"; import path from "node:path"; -import { ESLINT_DISABLE_FILES } from "../../../constants/disabling-comments"; import { ERRORS } from "../../../constants/messages"; import { DEFAULT_LINTED_FILE_REGEX } from "../../../constants/regex"; import { getDeepFilesFromDir } from "../../fs/getDeepFilesFromDir/getDeepFilesFromDir"; @@ -12,23 +11,20 @@ import { readFileStream } from "../../fs/readFileStream/readFileStream"; * @param {string} [options.rootDir=./] - Directory path to start searching from * @param {RegExp[]} [options.filesRegex=[/\.[cm]?[jt]sx?$/]] - Array of RegExp patterns to match files against * @param {function} [options.onFileProcessed] - Callback function called after each file is processed with the file path + * @param {string} [options.disablingComment] - Disabling comment that will be added to the top of the file, e.g. \/* eslint-disable *\/ * @returns {Promise} Promise that resolves when all files have been processed */ -export const disableFiles = async ( - { - rootDir = "./", - filesRegex = [DEFAULT_LINTED_FILE_REGEX], - onFileProcessed = () => {}, - }: { - rootDir?: string; - filesRegex?: RegExp[]; - onFileProcessed?: (filePath: string) => void; - } = { - rootDir: "./", - filesRegex: [DEFAULT_LINTED_FILE_REGEX], - onFileProcessed: () => {}, - }, -) => { +export const disableFiles = async ({ + rootDir = "./", + filesRegex = [DEFAULT_LINTED_FILE_REGEX], + onFileProcessed = () => {}, + disablingComment, +}: { + rootDir?: string; + filesRegex?: RegExp[]; + onFileProcessed?: (filePath: string) => void; + disablingComment: string; +}) => { const files = getDeepFilesFromDir(rootDir, filesRegex); return Promise.all( @@ -41,8 +37,8 @@ export const disableFiles = async ( return; } const content = data?.toString(); - if (content && !content.trim().startsWith(ESLINT_DISABLE_FILES)) { - const updatedContents = `${ESLINT_DISABLE_FILES}\n\n${content}`; + if (content && !content.trim().startsWith(disablingComment)) { + const updatedContents = `${disablingComment}\n\n${content}`; fs.writeFile(filePath, updatedContents, "utf8", (writeErr) => { if (writeErr) { console.error(ERRORS.writeFileError(filePath)); From 9db205917491d469f706236776e5e0ae931df1dc Mon Sep 17 00:00:00 2001 From: Boris Shuliak Date: Sat, 3 Jan 2026 12:56:23 +0100 Subject: [PATCH 2/2] chore: provide change file --- .../remove-eslint-dependency_2026-01-03-11-56-15-15.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changes/remove-eslint-dependency_2026-01-03-11-56-15-15.json diff --git a/changes/remove-eslint-dependency_2026-01-03-11-56-15-15.json b/changes/remove-eslint-dependency_2026-01-03-11-56-15-15.json new file mode 100644 index 0000000..d6b677a --- /dev/null +++ b/changes/remove-eslint-dependency_2026-01-03-11-56-15-15.json @@ -0,0 +1,6 @@ +{ + "comment": "Provide `--disableComment` option to `disable`, `check`, `check-staged` commands. Remove hadrcoded `eslint-disable` disabling comment", + "type": "major", + "author": "Boris Shuliak", + "issueLinks": [] +}