Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -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": []
}
2 changes: 2 additions & 0 deletions src/commands/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 || "./";
Expand All @@ -17,6 +18,7 @@ export const check = async (options: {
await checkFiles({
rootDir,
filesRegex,
disablingComment: options.disablingComment,
});

console.log(SUCCESS.cleanFiles());
Expand Down
2 changes: 2 additions & 0 deletions src/commands/checkStaged.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 || "./";
Expand All @@ -17,6 +18,7 @@ export const checkStaged = async (options: {
await checkStagedFiles({
rootDir,
filesRegex,
disablingComment: options.disablingComment,
onFileProcessed: (filePath) => {
console.log(INFO.fileChecked(filePath));
},
Expand Down
8 changes: 5 additions & 3 deletions src/commands/disable.ts
Original file line number Diff line number Diff line change
@@ -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));
},
Expand Down
1 change: 0 additions & 1 deletion src/constants/disabling-comments.ts

This file was deleted.

12 changes: 12 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ program
"-p, --pattern <regex...>",
"regex pattern to match files against (e.g. \\.[cm]?[jt]sx?$ \\.test\\.[cm]?[jt]sx?$)",
)
.option(
"-d, --disablingComment <comment>",
"Disabling comment that will be added to the top of the file, (e.g. \/* eslint-disable *\/)",
)
.action(disable);

program
Expand All @@ -40,6 +44,10 @@ program
"-p, --pattern <regex...>",
"regex pattern to match files against (e.g. \\.[cm]?[jt]sx?$ \\.test\\.[cm]?[jt]sx?$)",
)
.option(
"-d, --disablingComment <comment>",
"Disabling comment that will be checked in the file, (e.g. \/* eslint-disable *\/)",
)
.action(check);

program
Expand All @@ -52,6 +60,10 @@ program
"-p, --pattern <regex...>",
"regex pattern to match files against (e.g. \\.[cm]?[jt]sx?$ \\.test\\.[cm]?[jt]sx?$)",
)
.option(
"-d, --disablingComment <comment>",
"Disabling comment that will be checked in the file, (e.g. \/* eslint-disable *\/)",
)
.action(checkStaged);

program.parse();
127 changes: 50 additions & 77 deletions src/utils/core/checkFilePaths/__tests__/checkFilePaths.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -8,20 +7,26 @@ vi.mock("node:path");
vi.mock("../../../fs/readFileStream/readFileStream");

describe("checkFilePaths", () => {
const MOCK_COMMENT = "/* test-disable */";

beforeEach(() => {
vi.resetAllMocks();
});

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"];
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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"));
});

Expand All @@ -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")
Expand All @@ -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);
}
},
);
Expand All @@ -123,96 +139,53 @@ 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"];

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);
},
);

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");
});
});
8 changes: 5 additions & 3 deletions src/utils/core/checkFilePaths/checkFilePaths.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import path from "node:path";
import { ESLINT_DISABLE_FILES } from "../../../constants/disabling-comments";
import { ERRORS } from "../../../constants/messages";
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<void>} 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<void>((resolve, reject) => {
Expand All @@ -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));
}
}
Expand Down
Loading