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
8 changes: 8 additions & 0 deletions .changeset/curvy-socks-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@flint.fyi/rule-tester": minor
"@flint.fyi/core": minor
"@flint.fyi/cli": minor
"@flint.fyi/typescript-language": minor
---

feat: use LinterHost for linting
9 changes: 8 additions & 1 deletion packages/browser/src/rules/ruleTester.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { RuleTester } from "@flint.fyi/rule-tester";
import { createRuleTesterTSConfig } from "@flint.fyi/typescript-language";
import { describe, it } from "vitest";

export const ruleTester = new RuleTester({
defaults: { fileName: "file.ts" },
defaults: {
fileName: "file.ts",
files: createRuleTesterTSConfig({
lib: ["esnext", "DOM"],
}),
},
describe,
diskBackedFSRoot: import.meta.dirname,
it,
});
2 changes: 1 addition & 1 deletion packages/browser/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"tsBuildInfoFile": "node_modules/.cache/tsbuild/info.test.json",
"rootDir": "src/",
"outDir": "node_modules/.cache/tsbuild/test",
"types": []
"types": ["node"]
},
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.test.ts", "src/rules/ruleTester.ts"],
Expand Down
17 changes: 14 additions & 3 deletions packages/cli/src/runCliOnce.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { isConfig, runConfig, runConfigFixing } from "@flint.fyi/core";
import {
createDiskBackedLinterHost,
createEphemeralLinterHost,
isConfig,
runConfig,
runConfigFixing,
} from "@flint.fyi/core";
import { debugForFile } from "debug-for-file";
import path from "node:path";
import { pathToFileURL } from "node:url";
Expand Down Expand Up @@ -37,13 +43,18 @@ export async function runCliOnce(
const ignoreCache = !!values["cache-ignore"];

const skipDiagnostics = !!values["skip-diagnostics"];

const host = createEphemeralLinterHost(
createDiskBackedLinterHost(process.cwd()),
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Non-Actionable] Hmm, runCliOnce is called by runCliWatch. runCliWatch might want to set up a longer-lived linter host... but I don't think we should block this PR on that. That's a nice-to-have optimization for now IMO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we could also reuse disk-backed LinterHost's watchDirectory there. I'll file a followup.


const lintResults = await (values.fix
? runConfigFixing(configDefinition, {
? runConfigFixing(configDefinition, host, {
ignoreCache,
requestedSuggestions: new Set(values["fix-suggestions"]),
skipDiagnostics,
})
: runConfig(configDefinition, { ignoreCache, skipDiagnostics }));
: runConfig(configDefinition, host, { ignoreCache, skipDiagnostics }));

// TODO: Eventually, it'd be nice to move everything fully in-memory.
// This would be better for performance to avoid excess file system I/O.
Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/host/createEphemeralLinterHost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { LinterHost } from "../types/host.ts";

/**
* Creates a one-shot `LinterHost` that disables file/directory watching.
*
* Useful for single-run linting (e.g. CLI execution, rule tester),
* where persistent watchers are unnecessary and can affect performance.
*/
export function createEphemeralLinterHost(baseHost: LinterHost): LinterHost {
return {
...baseHost,
watchDirectory() {
return {
[Symbol.dispose]() {
// Intentionally empty to satisfy the Disposable interface.
},
};
},
watchFile() {
return {
[Symbol.dispose]() {
// Intentionally empty to satisfy the Disposable interface.
},
};
},
};
}
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { DirectivesCollector } from "./directives/DirectivesCollector.ts";
export { directiveReports } from "./directives/reports/directiveReports.ts";
export { globs } from "./globs/index.ts";
export { createDiskBackedLinterHost } from "./host/createDiskBackedLinterHost.ts";
export { createEphemeralLinterHost } from "./host/createEphemeralLinterHost.ts";
export {
createVFSLinterHost,
type CreateVFSLinterHostOpts,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/languages/createLanguage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ export function createLanguage<AstNodesByName, FileServices extends object>(
const language: Language<AstNodesByName, FileServices> = {
...languageDefinition,

createFileFactory() {
createFileFactory(host) {
log(
"Creating file factory for language: %s",
languageDefinition.about.name,
);

const fileFactoryDefinition = languageDefinition.createFileFactory();
const fileFactoryDefinition = languageDefinition.createFileFactory(host);

log("Created file factory.");

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/languages/makeDisposable.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export function makeDisposable<T extends object>(obj: T): Disposable & T {
return {
...obj,
[Symbol.dispose]: () => () => {
// Intentionally empty to satisfy the Disposable interface.
},
...obj,
};
}
3 changes: 3 additions & 0 deletions packages/core/src/running/collectFilesAndMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { nullThrows } from "@flint.fyi/utils";
import { readFromCache } from "../cache/readFromCache.ts";
import type { FileCacheStorage } from "../types/cache.ts";
import type { ProcessedConfigDefinition } from "../types/configs.ts";
import type { LinterHost } from "../types/host.ts";
import type { AnyRule } from "../types/rules.ts";
import { collectLanguageMetadataByFilePath } from "./collectLanguageMetadataByFilePath.ts";
import { collectRulesOptionsByFile } from "./collectRulesOptionsByFile.ts";
Expand Down Expand Up @@ -45,6 +46,7 @@ export interface CollectedFilesAndMetadata {
// Also, what if we removed the concept of a virtual file...?
export async function collectFilesAndMetadata(
configDefinition: ProcessedConfigDefinition,
host: LinterHost,
ignoreCache: boolean | undefined,
): Promise<CollectedFilesAndMetadata> {
// 1. Collect all file paths to lint and the 'use' rule configuration groups
Expand All @@ -63,6 +65,7 @@ export async function collectFilesAndMetadata(
const languageFileMetadataByFilePath = collectLanguageMetadataByFilePath(
cached,
rulesOptionsByFile,
host,
);

// 5. Join language metadata files into the corresponding options by file path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { makeAbsolute } from "@flint.fyi/utils";
import { CachedFactory } from "cached-factory";

import type { FileCacheStorage } from "../types/cache.ts";
import type { LinterHost } from "../types/host.ts";
import type {
AnyLanguage,
AnyLanguageFileMetadata,
Expand All @@ -11,6 +12,7 @@ import type { AnyRule } from "../types/rules.ts";
export function collectLanguageMetadataByFilePath(
cached: Map<string, FileCacheStorage> | undefined,
rulesOptionsByFile: Map<AnyRule, Map<string, unknown>>,
host: LinterHost,
) {
const languageFileMetadataByFilePath = new CachedFactory<
string,
Expand All @@ -19,7 +21,7 @@ export function collectLanguageMetadataByFilePath(

const languageFilesMetadataByLanguage = new CachedFactory(
(language: AnyLanguage) => {
const fileFactory = language.createFileFactory();
const fileFactory = language.createFileFactory(host);

return new CachedFactory((filePath: string) =>
fileFactory.prepareFromDisk({
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/running/runConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CachedFactory } from "cached-factory";

import { writeToCache } from "../cache/writeToCache.ts";
import type { ProcessedConfigDefinition } from "../types/configs.ts";
import type { LinterHost } from "../types/host.ts";
import type { LintResults } from "../types/linting.ts";
import type { FileReport } from "../types/reports.ts";
import type { AnyRule } from "../types/rules.ts";
Expand All @@ -17,6 +18,7 @@ export interface RunConfigOptions {

export async function runConfig(
configDefinition: ProcessedConfigDefinition,
host: LinterHost,
{ ignoreCache, skipDiagnostics }: RunConfigOptions,
): Promise<LintResults> {
// 1. Based on the original config definition, collect:
Expand All @@ -29,7 +31,7 @@ export async function runConfig(
cached,
languageFileMetadataByFilePath,
rulesFilesAndOptionsByRule,
} = await collectFilesAndMetadata(configDefinition, ignoreCache);
} = await collectFilesAndMetadata(configDefinition, host, ignoreCache);

// 2. For each lint rule, run it on all files and store each file's results
const reportsByFilePath = await runRules(rulesFilesAndOptionsByRule);
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/running/runConfigFixing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { debugForFile } from "debug-for-file";

import { applyChangesToFiles } from "../changing/applyChangesToFiles.ts";
import type { ProcessedConfigDefinition } from "../types/configs.ts";
import type { LinterHost } from "../types/host.ts";
import type { LintResultsWithChanges } from "../types/linting.ts";
import { runConfig } from "./runConfig.ts";

Expand All @@ -17,6 +18,7 @@ export interface RunConfigFixingOptions {

export async function runConfigFixing(
configDefinition: ProcessedConfigDefinition,
host: LinterHost,
{
ignoreCache,
requestedSuggestions,
Expand All @@ -38,7 +40,7 @@ export async function runConfigFixing(
// Why read file many time when few do trick?
// Or, at least it should all be virtual...
// https://github.com/flint-fyi/flint/issues/73
const lintResults = await runConfig(configDefinition, {
const lintResults = await runConfig(configDefinition, host, {
ignoreCache,
skipDiagnostics,
});
Expand Down
12 changes: 7 additions & 5 deletions packages/core/src/types/languages.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CommentDirective } from "./directives.ts";
import type { LinterHost } from "./host.ts";
import type { FileReport } from "./reports.ts";
import type { Rule, RuleAbout, RuleDefinition, RuleRuntime } from "./rules.ts";
import type { AnyOptionalSchema, InferredOutputObject } from "./shapes.ts";
Expand Down Expand Up @@ -68,7 +69,9 @@ export interface Language<
AstNodesByName,
FileServices extends object,
> extends LanguageDefinition<AstNodesByName, FileServices> {
createFileFactory(): LanguageFileFactory<AstNodesByName, FileServices>;
createFileFactory(
host: LinterHost,
): LanguageFileFactory<AstNodesByName, FileServices>;
createRule: LanguageCreateRule<AstNodesByName, FileServices>;
}

Expand All @@ -91,10 +94,9 @@ export interface LanguageDefinition<
FileServices extends object,
> {
about: LanguageAbout;
createFileFactory(): LanguageFileFactoryDefinition<
AstNodesByName,
FileServices
>;
createFileFactory(
host: LinterHost,
): LanguageFileFactoryDefinition<AstNodesByName, FileServices>;
}

export interface LanguageFileCacheImpacts {
Expand Down
3 changes: 2 additions & 1 deletion packages/jsx/src/rules/ruleTester.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { RuleTester } from "@flint.fyi/rule-tester";
import { createRuleTesterTSConfig } from "@flint.fyi/typescript-language";
import { describe, it } from "vitest";

export const ruleTester = new RuleTester({
defaults: { fileName: "file.tsx" },
defaults: { fileName: "file.tsx", files: createRuleTesterTSConfig() },
describe,
it,
});
2 changes: 1 addition & 1 deletion packages/next/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"tsBuildInfoFile": "node_modules/.cache/tsbuild/info.test.json",
"rootDir": "src/",
"outDir": "node_modules/.cache/tsbuild/test",
"types": []
"types": ["node"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this so that import.meta.dirname is available

},
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.test.ts", "src/rules/ruleTester.ts"],
Expand Down
11 changes: 10 additions & 1 deletion packages/node/src/rules/ruleTester.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { RuleTester } from "@flint.fyi/rule-tester";
import { createRuleTesterTSConfig } from "@flint.fyi/typescript-language";
import { describe, it } from "vitest";

export const ruleTester = new RuleTester({
defaults: { fileName: "file.ts" },
defaults: {
fileName: "file.ts",
files: createRuleTesterTSConfig({
types: ["node"],
// TODO: remove this; there is a bug in blobReadingMethods - it doesn't respect type from @types/node
lib: ["dom"],
}),
},
describe,
diskBackedFSRoot: import.meta.dirname,
it,
});
2 changes: 1 addition & 1 deletion packages/node/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"tsBuildInfoFile": "node_modules/.cache/tsbuild/info.test.json",
"rootDir": "src/",
"outDir": "node_modules/.cache/tsbuild/test",
"types": []
"types": ["node"]
},
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.test.ts", "src/rules/ruleTester.ts"],
Expand Down
6 changes: 5 additions & 1 deletion packages/performance/src/rules/ruleTester.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { RuleTester } from "@flint.fyi/rule-tester";
import { createRuleTesterTSConfig } from "@flint.fyi/typescript-language";
import { describe, it } from "vitest";

export const ruleTester = new RuleTester({
defaults: { fileName: "file.ts" },
defaults: {
fileName: "file.ts",
files: createRuleTesterTSConfig(),
},
describe,
it,
});
7 changes: 6 additions & 1 deletion packages/plugin-flint/src/rules/ruleTester.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { RuleTester } from "@flint.fyi/rule-tester";
import { createRuleTesterTSConfig } from "@flint.fyi/typescript-language";
import { describe, it } from "vitest";

export const ruleTester = new RuleTester({
defaults: { fileName: "file.test.ts" },
defaults: {
fileName: "file.test.ts",
files: createRuleTesterTSConfig(),
},
describe,
diskBackedFSRoot: import.meta.dirname,
it,
});
2 changes: 1 addition & 1 deletion packages/plugin-flint/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"tsBuildInfoFile": "node_modules/.cache/tsbuild/info.test.json",
"rootDir": "src/",
"outDir": "node_modules/.cache/tsbuild/test",
"types": []
"types": ["node"]
},
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.test.ts", "src/rules/ruleTester.ts"],
Expand Down
Loading