Skip to content

Commit cc187d9

Browse files
committed
chore(oxlint/napi): add createWorkspace & destroyWorkspace js callback function
1 parent bf3b963 commit cc187d9

File tree

19 files changed

+308
-169
lines changed

19 files changed

+308
-169
lines changed

apps/oxlint/src-js/bindings.d.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
/* auto-generated by NAPI-RS */
22
/* eslint-disable */
3-
/** JS callback to clear loaded plugins. */
4-
export type JsClearLoadedPluginCb =
5-
(() => void)
3+
/** JS callback to create a workspace. */
4+
export type JsCreateWorkspaceCb =
5+
((arg0: string) => Promise<undefined>)
6+
7+
/** JS callback to destroying a workspace. */
8+
export type JsDestroyWorkspaceCb =
9+
((arg0: string) => void)
610

711
/** JS callback to lint a file. */
812
export type JsLintFileCb =
9-
((arg0: string, arg1: number, arg2: Uint8Array | undefined | null, arg3: Array<number>, arg4: string) => string)
13+
((arg0: string, arg1: string, arg2: number, arg3: Uint8Array | undefined | null, arg4: Array<number>, arg5: string) => string)
1014

1115
/** JS callback to load a JS plugin. */
1216
export type JsLoadPluginCb =
13-
((arg0: string, arg1?: string | undefined | null) => Promise<string>)
17+
((arg0: string, arg1: string, arg2?: string | undefined | null) => Promise<string>)
1418

1519
/**
1620
* NAPI entry point.
@@ -19,8 +23,9 @@ export type JsLoadPluginCb =
1923
* 1. `args`: Command line arguments (process.argv.slice(2))
2024
* 2. `load_plugin`: Load a JS plugin from a file path.
2125
* 3. `lint_file`: Lint a file.
22-
* 4. `clear_loaded_plugin`: Clear loaded plugin state.
26+
* 4. `create_workspace`: Create a new workspace.
27+
* 5. `destroy_workspace`: Destroy a workspace.
2328
*
2429
* Returns `true` if linting succeeded without errors, `false` otherwise.
2530
*/
26-
export declare function lint(args: Array<string>, loadPlugin: JsLoadPluginCb, lintFile: JsLintFileCb, clearLoadedPlugin: JsClearLoadedPluginCb): Promise<boolean>
31+
export declare function lint(args: Array<string>, loadPlugin: JsLoadPluginCb, lintFile: JsLintFileCb, createWorkspace: JsCreateWorkspaceCb, destroyWorkspace: JsDestroyWorkspaceCb): Promise<boolean>

apps/oxlint/src-js/cli.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,42 @@ import { debugAssertIsNonNull } from "./utils/asserts.js";
66
// are identical. Ditto `lintFile` and `lintFileWrapper`.
77
let loadPlugin: typeof loadPluginWrapper | null = null;
88
let lintFile: typeof lintFileWrapper | null = null;
9-
let clearLoadedPlugin: typeof clearLoadedPluginWrapper | null = null;
9+
let createWorkspace: typeof createWorkspaceWrapper | null = null;
10+
let destroyWorkspace: typeof destroyWorkspaceWrapper | null = null;
1011

1112
/**
1213
* Load a plugin.
1314
*
1415
* Lazy-loads plugins code on first call, so that overhead is skipped if user doesn't use JS plugins.
1516
*
17+
* @param workspaceDir - Workspace root directory
1618
* @param path - Absolute path of plugin file
1719
* @param packageName - Optional package name from `package.json` (fallback if `plugin.meta.name` is not defined)
1820
* @returns Plugin details or error serialized to JSON string
1921
*/
20-
function loadPluginWrapper(path: string, packageName: string | null): Promise<string> {
22+
function loadPluginWrapper(
23+
workspaceDir: string,
24+
path: string,
25+
packageName: string | null,
26+
): Promise<string> {
2127
if (loadPlugin === null) {
2228
// Use promises here instead of making `loadPluginWrapper` an async function,
2329
// to avoid a micro-tick and extra wrapper `Promise` in all later calls to `loadPluginWrapper`
2430
return import("./plugins/index.js").then((mod) => {
25-
({ loadPlugin, lintFile, clearLoadedPlugin } = mod);
26-
return loadPlugin(path, packageName);
31+
({ loadPlugin, lintFile, createWorkspace, destroyWorkspace } = mod);
32+
return loadPlugin(workspaceDir, path, packageName);
2733
});
2834
}
2935
debugAssertIsNonNull(loadPlugin);
30-
return loadPlugin(path, packageName);
36+
return loadPlugin(workspaceDir, path, packageName);
3137
}
3238

3339
/**
3440
* Lint a file.
3541
*
3642
* Delegates to `lintFile`, which was lazy-loaded by `loadPluginWrapper`.
3743
*
44+
* @param workspaceDir - Directory of the workspace
3845
* @param filePath - Absolute path of file being linted
3946
* @param bufferId - ID of buffer containing file data
4047
* @param buffer - Buffer containing file data, or `null` if buffer with this ID was previously sent to JS
@@ -43,6 +50,7 @@ function loadPluginWrapper(path: string, packageName: string | null): Promise<st
4350
* @returns Diagnostics or error serialized to JSON string
4451
*/
4552
function lintFileWrapper(
53+
rootDir: string,
4654
filePath: string,
4755
bufferId: number,
4856
buffer: Uint8Array | null,
@@ -52,26 +60,54 @@ function lintFileWrapper(
5260
// `lintFileWrapper` is never called without `loadPluginWrapper` being called first,
5361
// so `lintFile` must be defined here
5462
debugAssertIsNonNull(lintFile);
55-
return lintFile(filePath, bufferId, buffer, ruleIds, settingsJSON);
63+
return lintFile(rootDir, filePath, bufferId, buffer, ruleIds, settingsJSON);
5664
}
5765

5866
/**
59-
* Clear loaded plugin state.
67+
* Create a new workspace.
6068
*
61-
* Delegates to `clearLoadedPlugin`, which was lazy-loaded by `loadPluginWrapper`.
62-
* If plugins haven't been loaded yet, this is a no-op.
69+
* Delegates to `createWorkspace`, which was lazy-loaded by `createWorkspaceWrapper`.
70+
*
71+
* @param rootDir - Root directory of the workspace
72+
* @returns Promise that resolves when workspace is created
6373
*/
64-
function clearLoadedPluginWrapper(): void {
65-
if (clearLoadedPlugin !== null) {
66-
clearLoadedPlugin();
74+
function createWorkspaceWrapper(rootDir: string): Promise<undefined> {
75+
if (createWorkspace === null) {
76+
// Use promises here instead of making `createWorkspaceWrapper` an async function,
77+
// to avoid a micro-tick and extra wrapper `Promise` in all later calls to `createWorkspaceWrapper`
78+
return import("./plugins/index.js").then((mod) => {
79+
({ loadPlugin, lintFile, createWorkspace, destroyWorkspace } = mod);
80+
return createWorkspace(rootDir);
81+
});
6782
}
83+
84+
debugAssertIsNonNull(createWorkspace);
85+
return Promise.resolve(createWorkspace(rootDir));
86+
}
87+
88+
/**
89+
* Destroy a workspace.
90+
*
91+
* @param rootDir - Root directory of the workspace
92+
*/
93+
function destroyWorkspaceWrapper(rootDir: string): void {
94+
// `destroyWorkspaceWrapper` is never called without `createWorkspaceWrapper` being called first,
95+
// so `destroyWorkspace` must be defined here
96+
debugAssertIsNonNull(destroyWorkspace);
97+
destroyWorkspace(rootDir);
6898
}
6999

70100
// Get command line arguments, skipping first 2 (node binary and script path)
71101
const args = process.argv.slice(2);
72102

73103
// Call Rust, passing `loadPlugin`, `lintFile`, and `clearLoadedPlugin` as callbacks, and CLI arguments
74-
const success = await lint(args, loadPluginWrapper, lintFileWrapper, clearLoadedPluginWrapper);
104+
const success = await lint(
105+
args,
106+
loadPluginWrapper,
107+
lintFileWrapper,
108+
createWorkspaceWrapper,
109+
destroyWorkspaceWrapper,
110+
);
75111

76112
// Note: It's recommended to set `process.exitCode` instead of calling `process.exit()`.
77113
// `process.exit()` kills the process immediately and `stdout` may not be flushed before process dies.

apps/oxlint/src-js/index.ts

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,16 @@ export type * as ESTree from "./generated/types.d.ts";
1111
export type { Context, LanguageOptions } from "./plugins/context.ts";
1212
export type { Fix, Fixer, FixFn } from "./plugins/fix.ts";
1313
export type { CreateOnceRule, CreateRule, Options, Plugin, Rule } from "./plugins/load.ts";
14+
export type { LineColumn, Location, Range, Ranged, Span } from "./plugins/location.ts";
1415
export type { Diagnostic, Suggestion } from "./plugins/report.ts";
16+
export type {
17+
RuleDeprecatedInfo,
18+
RuleDocs,
19+
RuleMeta,
20+
RuleOptionsSchema,
21+
RuleReplacedByExternalSpecifier,
22+
RuleReplacedByInfo,
23+
} from "./plugins/rule_meta.ts";
1524
export type {
1625
Definition,
1726
DefinitionType,
@@ -24,36 +33,27 @@ export type {
2433
export type { Settings } from "./plugins/settings.ts";
2534
export type { SourceCode } from "./plugins/source_code.ts";
2635
export type {
27-
CountOptions,
28-
FilterFn,
29-
RangeOptions,
30-
SkipOptions,
31-
Token,
36+
BlockCommentToken,
3237
BooleanToken,
3338
CommentToken,
34-
BlockCommentToken,
35-
LineCommentToken,
39+
CountOptions,
40+
FilterFn,
3641
IdentifierToken,
3742
JSXIdentifierToken,
3843
JSXTextToken,
3944
KeywordToken,
45+
LineCommentToken,
4046
NullToken,
4147
NumericToken,
4248
PrivateIdentifierToken,
4349
PunctuatorToken,
50+
RangeOptions,
4451
RegularExpressionToken,
52+
SkipOptions,
4553
StringToken,
4654
TemplateToken,
55+
Token,
4756
} from "./plugins/tokens.ts";
48-
export type {
49-
RuleMeta,
50-
RuleDocs,
51-
RuleOptionsSchema,
52-
RuleDeprecatedInfo,
53-
RuleReplacedByInfo,
54-
RuleReplacedByExternalSpecifier,
55-
} from "./plugins/rule_meta.ts";
56-
export type { LineColumn, Location, Range, Ranged, Span } from "./plugins/location.ts";
5757
export type {
5858
AfterHook,
5959
BeforeHook,
@@ -286,19 +286,3 @@ function createContextAndVisitor(rule: CreateOnceRule): {
286286

287287
return { context, visitor, beforeHook };
288288
}
289-
290-
/**
291-
* Clear all loaded plugins and rules.
292-
*
293-
* This function clears the internal state of registered plugins and rules,
294-
* allowing plugins to be reloaded from scratch. This is useful for the
295-
* language server when restarting or reloading configuration.
296-
*
297-
* Note: This function is lazy-loaded and will only be available after
298-
* the first plugin has been loaded. It will not have any effect if no
299-
* plugins have been loaded yet.
300-
*/
301-
export async function clearLoadedPlugin(): Promise<void> {
302-
const mod = await import("./plugins/index.js");
303-
mod.clearLoadedPlugin();
304-
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { lintFile } from "./lint.js";
2-
import { clearLoadedPlugin, loadPlugin } from "./load.js";
2+
import { loadPlugin } from "./load.js";
3+
import { createWorkspace, destroyWorkspace } from "./workspace.js";
34

4-
export { clearLoadedPlugin, lintFile, loadPlugin };
5+
export { createWorkspace, destroyWorkspace, lintFile, loadPlugin };

apps/oxlint/src-js/plugins/lint.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { setupFileContext, resetFileContext } from "./context.js";
1+
import { debugAssertIsNonNull, typeAssertIs } from "../utils/asserts.js";
2+
import { getErrorMessage } from "../utils/utils.js";
3+
import { resetFileContext, setupFileContext } from "./context.js";
24
import { registeredRules } from "./load.js";
35
import { diagnostics } from "./report.js";
4-
import { setSettingsForFile, resetSettings } from "./settings.js";
6+
import { resetSettings, setSettingsForFile } from "./settings.js";
57
import { ast, initAst, resetSourceAndAst, setupSourceForFile } from "./source_code.js";
6-
import { typeAssertIs, debugAssertIsNonNull } from "../utils/asserts.js";
7-
import { getErrorMessage } from "../utils/utils.js";
88
import {
99
addVisitorToCompiled,
1010
compiledVisitor,
@@ -41,6 +41,7 @@ const PARSER_SERVICES_DEFAULT: Record<string, unknown> = Object.freeze({});
4141
*
4242
* Main logic is in separate function `lintFileImpl`, because V8 cannot optimize functions containing try/catch.
4343
*
44+
* @param workspaceDir - Directory of the workspace
4445
* @param filePath - Absolute path of file being linted
4546
* @param bufferId - ID of buffer containing file data
4647
* @param buffer - Buffer containing file data, or `null` if buffer with this ID was previously sent to JS
@@ -49,14 +50,15 @@ const PARSER_SERVICES_DEFAULT: Record<string, unknown> = Object.freeze({});
4950
* @returns Diagnostics or error serialized to JSON string
5051
*/
5152
export function lintFile(
53+
workspaceDir: string,
5254
filePath: string,
5355
bufferId: number,
5456
buffer: Uint8Array | null,
5557
ruleIds: number[],
5658
settingsJSON: string,
5759
): string {
5860
try {
59-
lintFileImpl(filePath, bufferId, buffer, ruleIds, settingsJSON);
61+
lintFileImpl(workspaceDir, filePath, bufferId, buffer, ruleIds, settingsJSON);
6062
return JSON.stringify({ Success: diagnostics });
6163
} catch (err) {
6264
return JSON.stringify({ Failure: getErrorMessage(err) });
@@ -68,6 +70,7 @@ export function lintFile(
6870
/**
6971
* Run rules on a file.
7072
*
73+
* @param workspaceDir - Directory of the workspace
7174
* @param filePath - Absolute path of file being linted
7275
* @param bufferId - ID of buffer containing file data
7376
* @param buffer - Buffer containing file data, or `null` if buffer with this ID was previously sent to JS
@@ -78,6 +81,7 @@ export function lintFile(
7881
* @throws {*} If any rule throws
7982
*/
8083
function lintFileImpl(
84+
workspaceDir: string,
8185
filePath: string,
8286
bufferId: number,
8387
buffer: Uint8Array | null,
@@ -133,10 +137,16 @@ function lintFileImpl(
133137

134138
// Get visitors for this file from all rules
135139
initCompiledVisitor();
140+
if (DEBUG) {
141+
if (!registeredRules.has(workspaceDir))
142+
throw new Error(`No registered rules for workspaceDir "${workspaceDir}"`);
143+
}
144+
145+
const rules = registeredRules.get(workspaceDir)!;
136146

137147
for (let i = 0, len = ruleIds.length; i < len; i++) {
138148
const ruleId = ruleIds[i],
139-
ruleDetails = registeredRules[ruleId];
149+
ruleDetails = rules[ruleId];
140150

141151
// Set `ruleIndex` for rule. It's used when sending diagnostics back to Rust.
142152
ruleDetails.ruleIndex = i;

0 commit comments

Comments
 (0)