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
1 change: 0 additions & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"$schema": "./node_modules/knip/schema.json",
"ignore": ["packages/e2e/**/*"],
"ignoreExportsUsedInFile": { "interface": true, "type": true },
"ignoreUnresolved": ["./vitest.setup.ts"],
"treatConfigHintsAsErrors": true,
"workspaces": {
".": {
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/runCliWatch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { LinterHost, LintResults } from "@flint.fyi/core";
import { normalizePath } from "@flint.fyi/core";
import { pathKey } from "@flint.fyi/utils";
import debounce from "debounce";
import { debugForFile } from "debug-for-file";

Expand All @@ -16,6 +16,7 @@ export async function runCliWatch(
values: OptionsValues,
) {
const cwd = host.getCurrentDirectory();
const isCaseSensitiveFS = host.isCaseSensitiveFS();

log("Running single-run CLI once before watching");

Expand Down Expand Up @@ -54,7 +55,7 @@ export async function runCliWatch(
currentRenderer = startNewTask(true);

const rerun = debounce((fileName: string) => {
const normalizedPath = normalizePath(fileName, true);
const normalizedPath = pathKey(fileName, isCaseSensitiveFS);

const shouldRerun = shouldRerunForFileChange(
normalizedPath,
Expand Down
73 changes: 16 additions & 57 deletions packages/core/src/host/createDiskBackedLinterHost.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { normalizePath } from "@flint.fyi/utils";
import fs from "node:fs";
import path from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { createDiskBackedLinterHost } from "./createDiskBackedLinterHost.ts";
import { normalizePath } from "./normalizePath.ts";

const INTEGRATION_DIR_NAME = ".flint-disk-backed-linter-host-integration-tests";

Expand Down Expand Up @@ -41,9 +41,7 @@ describe("createDiskBackedLinterHost", () => {
it("normalizes cwd", () => {
const host = createDiskBackedLinterHost(integrationRoot + "/dir/..");

expect(host.getCurrentDirectory()).toEqual(
normalizePath(integrationRoot, host.isCaseSensitiveFS()),
);
expect(host.getCurrentDirectory()).toEqual(normalizePath(integrationRoot));
});

it("stats files and directories", () => {
Expand Down Expand Up @@ -366,10 +364,7 @@ describe("createDiskBackedLinterHost", () => {
fs.writeFileSync(nestedFile, "nested");
fs.writeFileSync(directFile, "direct");

const normalizedDirect = normalizePath(
directFile,
host.isCaseSensitiveFS(),
);
const normalizedDirect = normalizePath(directFile);

await vi.waitFor(() => {
expect(onEvent).toHaveBeenCalledWith(normalizedDirect);
Expand All @@ -392,10 +387,7 @@ describe("createDiskBackedLinterHost", () => {
const nestedFile = path.join(nestedPath, "nested.txt");
fs.writeFileSync(nestedFile, "nested");

const normalizedNested = normalizePath(
nestedFile,
host.isCaseSensitiveFS(),
);
const normalizedNested = normalizePath(nestedFile);

await vi.waitFor(() => {
expect(onEvent).toHaveBeenCalledWith(normalizedNested);
Expand All @@ -415,10 +407,7 @@ describe("createDiskBackedLinterHost", () => {
fs.writeFileSync(path.join(baseDir, ".git", "config"), "content");
fs.writeFileSync(path.join(baseDir, "src.txt"), "content");

const normalizedFile = normalizePath(
path.join(baseDir, "src.txt"),
host.isCaseSensitiveFS(),
);
const normalizedFile = normalizePath(path.join(baseDir, "src.txt"));
await sleep(50);
expect(onEvent).toHaveBeenCalledWith(normalizedFile);
});
Expand All @@ -441,10 +430,7 @@ describe("createDiskBackedLinterHost", () => {
);
fs.writeFileSync(path.join(baseDir, "src.txt"), "content");

const normalizedFile = normalizePath(
path.join(baseDir, "src.txt"),
host.isCaseSensitiveFS(),
);
const normalizedFile = normalizePath(path.join(baseDir, "src.txt"));
await sleep(50);
expect(onEvent).toHaveBeenCalledWith(normalizedFile);
});
Expand All @@ -461,7 +447,7 @@ describe("createDiskBackedLinterHost", () => {
const filePath = path.join(baseDir, ".gitignore");
fs.writeFileSync(filePath, "content");

const normalizedFile = normalizePath(filePath, host.isCaseSensitiveFS());
const normalizedFile = normalizePath(filePath);
await vi.waitFor(() => {
expect(onEvent).toHaveBeenCalledWith(normalizedFile);
});
Expand All @@ -470,10 +456,7 @@ describe("createDiskBackedLinterHost", () => {
it("emits when watching directory is created", async () => {
const host = createDiskBackedLinterHost(integrationRoot);
const directoryPath = path.join(integrationRoot, "recreate-dir");
const normalizedDirectory = normalizePath(
directoryPath,
host.isCaseSensitiveFS(),
);
const normalizedDirectory = normalizePath(directoryPath);
const onEvent = vi.fn();
using _ = host.watchDirectorySync(directoryPath, onEvent, {
pollingInterval: 10,
Expand All @@ -492,10 +475,7 @@ describe("createDiskBackedLinterHost", () => {
it("emits when watching directory is deleted", async () => {
const host = createDiskBackedLinterHost(integrationRoot);
const directoryPath = path.join(integrationRoot, "recreate-dir");
const normalizedDirectory = normalizePath(
directoryPath,
host.isCaseSensitiveFS(),
);
const normalizedDirectory = normalizePath(directoryPath);
const onEvent = vi.fn();
fs.mkdirSync(directoryPath, { recursive: true });

Expand All @@ -521,16 +501,10 @@ describe("createDiskBackedLinterHost", () => {

const firstFile = path.join(directoryPath, "first.txt");
fs.writeFileSync(firstFile, "first");
const normalizedFirst = normalizePath(
firstFile,
host.isCaseSensitiveFS(),
);
const normalizedFirst = normalizePath(firstFile);
const secondFile = path.join(directoryPath, "second.txt");
fs.writeFileSync(secondFile, "second");
const normalizedSecond = normalizePath(
secondFile,
host.isCaseSensitiveFS(),
);
const normalizedSecond = normalizePath(secondFile);

using _ = host.watchDirectorySync(directoryPath, onEvent, {
pollingInterval: 10,
Expand Down Expand Up @@ -567,10 +541,7 @@ describe("createDiskBackedLinterHost", () => {
const firstFile = path.join(directoryPath, "first.txt");
fs.writeFileSync(firstFile, "first");

const normalizedFirst = normalizePath(
firstFile,
host.isCaseSensitiveFS(),
);
const normalizedFirst = normalizePath(firstFile);

await vi.waitFor(() => {
expect(onEvent).toHaveBeenCalledWith(normalizedFirst);
Expand All @@ -579,10 +550,7 @@ describe("createDiskBackedLinterHost", () => {

fs.rmSync(directoryPath, { force: true, recursive: true });

const normalizedDirectory = normalizePath(
directoryPath,
host.isCaseSensitiveFS(),
);
const normalizedDirectory = normalizePath(directoryPath);
await vi.waitFor(() => {
expect(onEvent).toHaveBeenCalledWith(normalizedDirectory);
});
Expand All @@ -597,25 +565,16 @@ describe("createDiskBackedLinterHost", () => {
const secondFile = path.join(directoryPath, "second.txt");
fs.writeFileSync(secondFile, "second");

const normalizedSecond = normalizePath(
secondFile,
host.isCaseSensitiveFS(),
);
const normalizedSecond = normalizePath(secondFile);
await vi.waitFor(() => {
expect(onEvent).toHaveBeenCalledWith(normalizedSecond);
});
});

it("correctly reports when dir and its child have the same name", async () => {
const host = createDiskBackedLinterHost(integrationRoot);
const directoryPath = normalizePath(
path.join(integrationRoot, "dir"),
host.isCaseSensitiveFS(),
);
const subDirectoryPath = normalizePath(
path.join(directoryPath, "dir"),
host.isCaseSensitiveFS(),
);
const directoryPath = normalizePath(path.join(integrationRoot, "dir"));
const subDirectoryPath = normalizePath(path.join(directoryPath, "dir"));
const onEvent = vi.fn();
using _ = host.watchDirectorySync(directoryPath, onEvent, {
pollingInterval: 10,
Expand Down
34 changes: 18 additions & 16 deletions packages/core/src/host/createDiskBackedLinterHost.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { dirnameKey, normalizePath, pathKey } from "@flint.fyi/utils";
import fs from "node:fs";
import path from "node:path";

Expand All @@ -7,13 +8,12 @@ import type {
LinterHostFileWatcherEvent,
} from "../types/host.ts";
import { isFileSystemCaseSensitive } from "./isFileSystemCaseSensitive.ts";
import { normalizePath } from "./normalizePath.ts";

const ignoredPaths = ["/node_modules", "/.git", "/.jj"];

export function createDiskBackedLinterHost(cwd: string): LinterHost {
const caseSensitiveFS = isFileSystemCaseSensitive();
cwd = normalizePath(cwd, caseSensitiveFS);
cwd = normalizePath(cwd);

function createWatcher(
normalizedWatchPath: string,
Expand All @@ -35,7 +35,7 @@ export function createDiskBackedLinterHost(cwd: string): LinterHost {
existsNow: boolean | null = null,
) {
if (changedFileName != null) {
changedFileName = normalizePath(changedFileName, caseSensitiveFS);
changedFileName = normalizePath(changedFileName);
}
existsNow ??= fs.existsSync(normalizedWatchPath);
if (existsNow) {
Expand Down Expand Up @@ -80,7 +80,6 @@ export function createDiskBackedLinterHost(cwd: string): LinterHost {
) {
changedPath = normalizePath(
path.resolve(normalizedWatchPath, filename),
caseSensitiveFS,
);
}
if (statAndEmitIfChanged(changedPath)) {
Expand All @@ -93,10 +92,7 @@ export function createDiskBackedLinterHost(cwd: string): LinterHost {
statAndEmitIfChanged(
filename == null
? null
: normalizePath(
path.resolve(normalizedWatchPath, filename),
caseSensitiveFS,
),
: normalizePath(path.resolve(normalizedWatchPath, filename)),
)
) {
return;
Expand Down Expand Up @@ -253,20 +249,23 @@ export function createDiskBackedLinterHost(cwd: string): LinterHost {
}
},
watchDirectorySync(directoryPathAbsolute, callback, options) {
directoryPathAbsolute = normalizePath(
directoryPathAbsolute,
caseSensitiveFS,
);
directoryPathAbsolute = normalizePath(directoryPathAbsolute);
const dirKey = pathKey(directoryPathAbsolute, caseSensitiveFS);
const dirKeySlash = dirnameKey(directoryPathAbsolute, caseSensitiveFS);

return createWatcher(
directoryPathAbsolute,
options.recursive,
options.pollingInterval ?? 2_000,
(normalizedChangedFilePath) => {
normalizedChangedFilePath ??= directoryPathAbsolute;
if (normalizedChangedFilePath !== directoryPathAbsolute) {
const changedKey = pathKey(
normalizedChangedFilePath,
caseSensitiveFS,
);
if (changedKey !== dirKey) {
let relative = normalizedChangedFilePath;
if (relative.startsWith(directoryPathAbsolute + "/")) {
if (changedKey.startsWith(dirKeySlash)) {
relative = relative.slice(directoryPathAbsolute.length);
}
for (const ignored of ignoredPaths) {
Expand All @@ -283,14 +282,17 @@ export function createDiskBackedLinterHost(cwd: string): LinterHost {
);
},
watchFileSync(filePathAbsolute, callback, options) {
filePathAbsolute = normalizePath(filePathAbsolute, caseSensitiveFS);
const watchKey = pathKey(filePathAbsolute, caseSensitiveFS);

return createWatcher(
filePathAbsolute,
false,
options?.pollingInterval ?? 2_000,
(normalizedChangedFilePath, event) => {
if (normalizedChangedFilePath === filePathAbsolute) {
if (
normalizedChangedFilePath != null &&
pathKey(normalizedChangedFilePath, caseSensitiveFS) === watchKey
) {
callback(event);
}
},
Expand Down
21 changes: 18 additions & 3 deletions packages/core/src/host/createVFSLinterHost.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,31 @@ describe(createVFSLinterHost, () => {
expect(host.isCaseSensitiveFS()).toEqual(true);
});

it("normalizes cwd case-insensitively", () => {
it("normalizes cwd without lowercasing", () => {
const host = createVFSLinterHost({
caseSensitive: false,
cwd: "C:\\HELLO\\world\\",
});

expect(host.getCurrentDirectory()).toEqual("c:/hello/world");
expect(host.getCurrentDirectory()).toEqual("C:/HELLO/world");
expect(host.isCaseSensitiveFS()).toEqual(false);
});

it("handles case-insensitive operations", () => {
const baseHost = createVFSLinterHost({
caseSensitive: false,
cwd: "/root",
});
const host = createVFSLinterHost({ baseHost });

host.vfsUpsertFile("/root/file.ts", "fake content");
host.vfsUpsertFile("/root/FILE.ts", "real content");
host.vfsUpsertFile("/root/otheR-File.ts", "other content");

expect(host.readFileSync("/root/file.ts")).toEqual("real content");
expect(host.readFileSync("/root/OTHER-file.ts")).toEqual("other content");
});

it("inherits cwd and case sensitivity from base host", () => {
const baseHost = createVFSLinterHost({
caseSensitive: true,
Expand Down Expand Up @@ -494,7 +509,7 @@ describe(createVFSLinterHost, () => {
});
host.vfsUpsertFile("C:\\file.txt", "content");

expect(onEvent).toHaveBeenCalledExactlyOnceWith("c:/file.txt");
expect(onEvent).toHaveBeenCalledExactlyOnceWith("C:/file.txt");
});

it("reports file editing", () => {
Expand Down
Loading
Loading