Skip to content

Commit 7409630

Browse files
committed
fix(linter/plugins): allow access to cwd in createOnce in ESLint interop mode (#15488)
#15301 made `Context#cwd` available in `createOnce`. Make it available in ESLint compat mode too. Ditto the `extend` method added in #15477. Define all other `Context` methods/getters on prototype of `Context` in ESLint compat mode, for completeness.
1 parent fe502f8 commit 7409630

File tree

2 files changed

+94
-7
lines changed

2 files changed

+94
-7
lines changed

apps/oxlint/src-js/index.ts

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import type { Context } from './plugins/context.ts';
1+
import type { Context, FileContext, LanguageOptions } from './plugins/context.ts';
22
import type { CreateOnceRule, Plugin, Rule } from './plugins/load.ts';
3+
import type { SourceCode } from './plugins/source_code.ts';
34
import type { BeforeHook, Visitor, VisitorWithHooks } from './plugins/types.ts';
45

56
export type * as ESTree from './generated/types.d.ts';
@@ -41,7 +42,15 @@ export type {
4142
VisitorWithHooks,
4243
} from './plugins/types.ts';
4344

44-
const { defineProperty, getPrototypeOf, hasOwn, setPrototypeOf, create: ObjectCreate } = Object;
45+
const {
46+
defineProperty,
47+
getPrototypeOf,
48+
hasOwn,
49+
setPrototypeOf,
50+
create: ObjectCreate,
51+
freeze,
52+
assign: ObjectAssign,
53+
} = Object;
4554

4655
/**
4756
* Define a plugin.
@@ -125,6 +134,81 @@ export function defineRule(rule: Rule): Rule {
125134
return rule;
126135
}
127136

137+
// Cached current working directory
138+
let cwd: string | null = null;
139+
140+
// File context object. Used as prototype for `Context` objects for each rule during `createOnce` call.
141+
// When running the rules, ESLint's `context` object is switching in as prototype for `Context` objects.
142+
//
143+
// Only `cwd` property and `extends` method are available in `createOnce`, so only those are implemented here.
144+
// All other getters/methods throw, same as they do in main implementation.
145+
const FILE_CONTEXT: FileContext = freeze({
146+
get filename(): string {
147+
throw new Error('Cannot access `context.filename` in `createOnce`');
148+
},
149+
150+
get physicalFilename(): string {
151+
throw new Error('Cannot access `context.physicalFilename` in `createOnce`');
152+
},
153+
154+
/**
155+
* Current working directory.
156+
*/
157+
get cwd(): string {
158+
// Note: We can allow accessing `cwd` in `createOnce`, as it's global
159+
if (cwd === null) cwd = process.cwd();
160+
return cwd;
161+
},
162+
163+
get sourceCode(): SourceCode {
164+
throw new Error('Cannot access `context.sourceCode` in `createOnce`');
165+
},
166+
167+
get languageOptions(): LanguageOptions {
168+
throw new Error('Cannot access `context.languageOptions` in `createOnce`');
169+
},
170+
171+
get settings(): Record<string, unknown> {
172+
throw new Error('Cannot access `context.settings` in `createOnce`');
173+
},
174+
175+
/**
176+
* Create a new object with the current object as the prototype and
177+
* the specified properties as its own properties.
178+
* @param extension - The properties to add to the new object.
179+
* @returns A new object with the current object as the prototype
180+
* and the specified properties as its own properties.
181+
*/
182+
extend(this: FileContext, extension: Record<string | number | symbol, unknown>): FileContext {
183+
return freeze(ObjectAssign(ObjectCreate(this), extension));
184+
},
185+
186+
get parserOptions(): Record<string, unknown> {
187+
throw new Error('Cannot access `context.parserOptions` in `createOnce`');
188+
},
189+
190+
get parserPath(): string {
191+
throw new Error('Cannot access `context.parserPath` in `createOnce`');
192+
},
193+
194+
getCwd(): string {
195+
// TODO: Implement this?
196+
throw new Error('`context.getCwd` is deprecated. Use `cwd` instead.');
197+
},
198+
199+
getFilename(): string {
200+
throw new Error('Cannot call `context.getFilename` in `createOnce`');
201+
},
202+
203+
getPhysicalFilename(): string {
204+
throw new Error('Cannot call `context.getPhysicalFilename` in `createOnce`');
205+
},
206+
207+
getSourceCode(): SourceCode {
208+
throw new Error('Cannot call `context.getSourceCode` in `createOnce`');
209+
},
210+
});
211+
128212
/**
129213
* Call `createOnce` method of rule, and return `Context`, `Visitor`, and `beforeHook` (if any).
130214
*
@@ -142,10 +226,12 @@ function createContextAndVisitor(rule: CreateOnceRule): {
142226
if (typeof createOnce !== 'function') throw new Error('Rule `createOnce` property must be a function');
143227

144228
// Call `createOnce` with empty context object.
145-
// Really, `context` should be an instance of `Context`, which would throw error on accessing e.g. `id`
146-
// in body of `createOnce`. But any such bugs should have been caught when testing the rule in Oxlint,
147-
// so should be OK to take this shortcut.
148-
const context = ObjectCreate(null, {
229+
// Really, accessing `options` or calling `report` should throw, because they're illegal in `createOnce`.
230+
// But any such bugs should have been caught when testing the rule in Oxlint, so should be OK to take this shortcut.
231+
// `FILE_CONTEXT` prototype provides `cwd` property and `extends` method, which are available in `createOnce`.
232+
const context = ObjectCreate(FILE_CONTEXT, {
233+
// TODO: Need to set `id` to full rule name - it's available in `createOnce`.
234+
// Or make it inaccessible in `createOnce`.
149235
id: { value: '', enumerable: true, configurable: true },
150236
options: { value: null, enumerable: true, configurable: true },
151237
report: { value: null, enumerable: true, configurable: true },

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ const FILE_CONTEXT = freeze({
259259
*/
260260
getCwd(): string {
261261
// TODO: Implement this?
262+
// If do implement it, also implement `getCwd` in `index.ts` (`createOnce` shim for ESLint).
262263
throw new Error('`context.getCwd` is deprecated. Use `cwd` instead.');
263264
},
264265

@@ -297,7 +298,7 @@ const FILE_CONTEXT = freeze({
297298
* Context object for a file.
298299
* Is the prototype for `Context` objects for each rule.
299300
*/
300-
type FileContext = typeof FILE_CONTEXT;
301+
export type FileContext = typeof FILE_CONTEXT;
301302

302303
/**
303304
* Context object for a rule.

0 commit comments

Comments
 (0)