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: 1 addition & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export default defineConfig(
enableAutofixRemoval: {
imports: true,
},
ignoreUsingDeclarations: true,
},
],
"@typescript-eslint/prefer-nullish-coalescing": [
Expand Down
9 changes: 6 additions & 3 deletions packages/cli/src/findConfigFileName.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import fs from "node:fs/promises";
import type { LinterHost } from "@flint.fyi/core";

const candidatesOrdered = [
"flint.config.ts",
Expand All @@ -9,8 +9,11 @@ const candidatesOrdered = [
"flint.config.js",
];

export async function findConfigFileName(directory: string) {
const children = new Set(await fs.readdir(directory));
export async function findConfigFileName(host: LinterHost) {
const currentDirectoryContents = await host.readDirectory(
host.getCurrentDirectory(),
);
const children = new Set(currentDirectoryContents.map((file) => file.name));

const fileName = candidatesOrdered.find((candidate) =>
children.has(candidate),
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/renderers/singleRendererFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const singleRendererFactory: RendererFactory = {
continue;
}

// TODO: Can we re-use the sourcefile representation?
const sourceFileText = await fs.readFile(filePath, "utf-8");

const body = presenter.renderFile({
Expand Down
7 changes: 3 additions & 4 deletions packages/cli/src/runCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ export async function runCli(args: string[]) {
return 0;
}

const cwd = process.cwd();
const configFileName = await findConfigFileName(cwd);
const host = createDiskBackedLinterHost(process.cwd());
const cwd = host.getCurrentDirectory();
const configFileName = await findConfigFileName(host);
if (!configFileName) {
console.error(`No flint.config.* file found in ${cwd}.`);
console.error(
Expand All @@ -89,8 +90,6 @@ export async function runCli(args: string[]) {

const getRenderer = createRendererFactory(configFileName, values);

const host = createDiskBackedLinterHost(cwd);

if (values.watch) {
await runCliWatch(host, configFileName, getRenderer, values);
console.log("👋 Thanks for using Flint!");
Expand Down
32 changes: 4 additions & 28 deletions packages/cli/src/runCliWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { LinterHost, LintResults } from "@flint.fyi/core";
import { normalizePath } from "@flint.fyi/core";
import debounce from "debounce";
import { debugForFile } from "debug-for-file";
import * as fs from "node:fs";

import type { OptionsValues } from "./options.ts";
import type { Renderer } from "./renderers/types.ts";
Expand All @@ -16,7 +15,6 @@ export async function runCliWatch(
getRenderer: () => Renderer,
values: OptionsValues,
) {
const abortController = new AbortController();
const cwd = host.getCurrentDirectory();

log("Running single-run CLI once before watching");
Expand Down Expand Up @@ -46,7 +44,7 @@ export async function runCliWatch(
);

renderer.onQuit?.(() => {
abortController.abort();
watcher[Symbol.dispose]();
resolve();
});

Expand All @@ -56,19 +54,6 @@ export async function runCliWatch(
currentRenderer = startNewTask(true);

const rerun = debounce((fileName: string) => {
if (
fileName.startsWith("node_modules/.cache") ||
fileName.startsWith(".git") ||
fileName.startsWith(".jj") ||
fileName.startsWith(".turbo")
) {
log(
"Skipping re-running watch mode for ignored change to: %s",
fileName,
);
return;
}

const normalizedPath = normalizePath(fileName, true);

const shouldRerun = shouldRerunForFileChange(
Expand All @@ -90,18 +75,9 @@ export async function runCliWatch(
}, 100);

log("Watching cwd:", cwd);
fs.watch(
cwd,
{
recursive: true,
signal: abortController.signal,
},
(_, fileName) => {
if (fileName) {
rerun(fileName);
}
},
);
const watcher = host.watchDirectorySync(cwd, rerun, {
recursive: true,
});
});
}

Expand Down
7 changes: 0 additions & 7 deletions packages/core/src/cache/getFileTouchTime.ts

This file was deleted.

10 changes: 5 additions & 5 deletions packages/core/src/cache/readFromCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import { nullThrows } from "@flint.fyi/utils";
import { CachedFactory } from "cached-factory";
import { debugForFile } from "debug-for-file";

import { readFileSafe } from "../running/readFileSafe.ts";
import type { FileCacheStorage } from "../types/cache.ts";
import type { LinterHost } from "../types/host.ts";
import { cacheStorageSchema } from "./cacheSchema.ts";
import { getCacheFilePath } from "./getCacheFilePath.ts";
import { getFileTouchTime } from "./getFileTouchTime.ts";

const log = debugForFile(import.meta.filename);

export async function readFromCache(
host: LinterHost,
allFilePaths: Set<string>,
configFilePath: string,
cacheLocation: string | undefined,
): Promise<Map<string, FileCacheStorage> | undefined> {
const cacheFilePath = getCacheFilePath(cacheLocation);
const rawCacheString = await readFileSafe(cacheFilePath);
const rawCacheString = await host.readFile(cacheFilePath);

if (!rawCacheString) {
log("Linting all %d file path(s) due to lack of cache.", allFilePaths.size);
Expand Down Expand Up @@ -50,7 +50,7 @@ export async function readFromCache(
cache.configs[filePath],
"Cache timestamp is expected to be present",
);
const timestampTouched = getFileTouchTime(filePath);
const timestampTouched = await host.getFileTouchTime(filePath);
if (timestampTouched > timestampCached) {
log(
"Linting all %d file path(s) due to %s touch timestamp %d after cache timestamp %d",
Expand Down Expand Up @@ -93,7 +93,7 @@ export async function readFromCache(
}

const timestampCached = fileCached.timestamp;
const timestampTouched = getFileTouchTime(filePath);
const timestampTouched = await host.getFileTouchTime(filePath);
if (timestampTouched > timestampCached) {
log(
"Directly invalidating cache for: %s due to touch timestamp %d after cache timestamp %d",
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/cache/writeToCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { dirname } from "node:path";
import omitEmpty from "omit-empty";

import type { CacheStorage } from "../types/cache.ts";
import type { LinterHost } from "../types/host.ts";
import type { LintResults } from "../types/linting.ts";
import { cacheStorageSchema } from "./cacheSchema.ts";
import { getCacheFilePath } from "./getCacheFilePath.ts";
import { getFileTouchTime } from "./getFileTouchTime.ts";

const log = debugForFile(import.meta.filename);

export async function writeToCache(
host: LinterHost,
configFileName: string,
lintResults: LintResults,
cacheLocation: string | undefined,
Expand All @@ -28,8 +29,8 @@ export async function writeToCache(

const storage: CacheStorage = {
configs: {
[configFileName]: getFileTouchTime(configFileName),
"package.json": getFileTouchTime("package.json"),
[configFileName]: await host.getFileTouchTime(configFileName),
"package.json": await host.getFileTouchTime("package.json"),
},
files: {
...Object.fromEntries(
Expand Down
15 changes: 6 additions & 9 deletions packages/core/src/changing/applyChangesToFile.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { nullThrows } from "@flint.fyi/utils";
import { debugForFile } from "debug-for-file";
import * as fs from "node:fs/promises";

import type { FileChange } from "../types/changes.ts";
import type { LinterHost } from "../types/host.ts";
import { applyChangesToText } from "./applyChangesToText.ts";

const log = debugForFile(import.meta.filename);

export async function applyChangesToFile(
host: LinterHost,
absoluteFilePath: string,
changes: FileChange[],
) {
Expand All @@ -16,20 +18,15 @@ export async function applyChangesToFile(
absoluteFilePath,
);

const fileContent = await host.readFile(absoluteFilePath);
const updatedFileContent = applyChangesToText(
changes,
// TODO: Eventually, the file system should be abstracted
// Direct fs read calls don't make sense in e.g. virtual file systems
// https://github.com/flint-fyi/flint/issues/73
await fs.readFile(absoluteFilePath, "utf8"),
nullThrows(fileContent, "Expected linted file to exist."),
);

log("Writing %d changes to file: %s", changes.length, absoluteFilePath);

// TODO: Eventually, the file system should be abstracted
// Direct fs write calls don't make sense in e.g. virtual file systems
// https://github.com/flint-fyi/flint/issues/73
await fs.writeFile(absoluteFilePath, updatedFileContent);
await host.writeFile(absoluteFilePath, updatedFileContent);

log("Wrote changes to file: %s", absoluteFilePath);
}
7 changes: 5 additions & 2 deletions packages/core/src/changing/applyChangesToFiles.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import { debugForFile } from "debug-for-file";

import type { LinterHost } from "../index.ts";
import type { FileResults } from "../types/linting.ts";
import { applyChangesToFile } from "./applyChangesToFile.ts";
import { resolveChangesByFile } from "./resolveChangesByFile.ts";

const log = debugForFile(import.meta.filename);

export async function applyChangesToFiles(
host: LinterHost,
filesResults: Map<string, FileResults>,
requestedSuggestions: Set<string>,
) {
log("Resolving changes from results.");

const changesByFile = await resolveChangesByFile(
host,
filesResults,
requestedSuggestions,
);

log("Resolved %d changes from results.");
log("Resolved %d changes from results.", changesByFile.length);

await Promise.all(
changesByFile.map(async ([absoluteFilePath, fileChanges]) => {
await applyChangesToFile(absoluteFilePath, fileChanges);
await applyChangesToFile(host, absoluteFilePath, fileChanges);
}),
);

Expand Down
15 changes: 9 additions & 6 deletions packages/core/src/changing/resolveChange.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as fs from "node:fs/promises";
import { nullThrows } from "@flint.fyi/utils";

import type { Change, ResolvedChange } from "../types/changes.ts";
import type { LinterHost } from "../types/host.ts";
import { isSuggestionForFiles } from "../utils/predicates.ts";

export async function resolveChange(
host: LinterHost,
change: Change,
sourceFilePath: string,
): Promise<ResolvedChange | ResolvedChange[]> {
Expand All @@ -17,11 +19,12 @@ export async function resolveChange(
return (
await Promise.all(
Object.entries(change.files).flatMap(async ([filePath, generator]) => {
// TODO: Eventually, the file system should be abstracted
// Direct fs read calls don't make sense in e.g. virtual file systems
// https://github.com/flint-fyi/flint/issues/73
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const fileChanges = generator!(await fs.readFile(filePath, "utf8"));
const gen = nullThrows(
generator,
"Expected suggestion generator to exist",
);
const file = await host.readFile(filePath);
const fileChanges = gen(nullThrows(file, "Expected file to exist"));

return fileChanges.map((fileChange) => ({
filePath,
Expand Down
11 changes: 9 additions & 2 deletions packages/core/src/changing/resolveChangesByFile.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { CachedFactory } from "cached-factory";

import type { FileChange } from "../types/changes.ts";
import type { LinterHost } from "../types/host.ts";
import type { FileResults } from "../types/linting.ts";
import type { FileReport } from "../types/reports.ts";
import { flatten } from "../utils/arrays.ts";
import { createReportSuggestionKey } from "./createReportSuggestionKey.ts";
import { resolveChange } from "./resolveChange.ts";

export async function resolveChangesByFile(
host: LinterHost,
filesResults: Map<string, FileResults>,
requestedSuggestions: Set<string>,
) {
Expand All @@ -20,13 +22,18 @@ export async function resolveChangesByFile(
}

async function collectReportSuggestions(
host: LinterHost,
absoluteFilePath: string,
report: FileReport,
) {
for (const suggestion of report.suggestions ?? []) {
const key = createReportSuggestionKey(report, suggestion);
if (requestedSuggestions.has(key)) {
const resolved = await resolveChange(suggestion, absoluteFilePath);
const resolved = await resolveChange(
host,
suggestion,
absoluteFilePath,
);

for (const change of flatten(resolved)) {
changesByFile.get(change.filePath).push(change);
Expand All @@ -40,7 +47,7 @@ export async function resolveChangesByFile(
async ([absoluteFilePath, fileResults]) => {
for (const report of fileResults.reports) {
collectReportFix(absoluteFilePath, report);
await collectReportSuggestions(absoluteFilePath, report);
await collectReportSuggestions(host, absoluteFilePath, report);
}
},
),
Expand Down
Loading