diff --git a/apps/oxlint/src-js/index.ts b/apps/oxlint/src-js/index.ts index 5a7bf7ae62a2c..1ec77f1185de4 100644 --- a/apps/oxlint/src-js/index.ts +++ b/apps/oxlint/src-js/index.ts @@ -1,5 +1,6 @@ -import type { Context } from './plugins/context.ts'; +import type { Context, FileContext, LanguageOptions } from './plugins/context.ts'; import type { CreateOnceRule, Plugin, Rule } from './plugins/load.ts'; +import type { SourceCode } from './plugins/source_code.ts'; import type { BeforeHook, Visitor, VisitorWithHooks } from './plugins/types.ts'; export type * as ESTree from './generated/types.d.ts'; @@ -41,7 +42,15 @@ export type { VisitorWithHooks, } from './plugins/types.ts'; -const { defineProperty, getPrototypeOf, hasOwn, setPrototypeOf, create: ObjectCreate } = Object; +const { + defineProperty, + getPrototypeOf, + hasOwn, + setPrototypeOf, + create: ObjectCreate, + freeze, + assign: ObjectAssign, +} = Object; /** * Define a plugin. @@ -125,6 +134,81 @@ export function defineRule(rule: Rule): Rule { return rule; } +// Cached current working directory +let cwd: string | null = null; + +// File context object. Used as prototype for `Context` objects for each rule during `createOnce` call. +// When running the rules, ESLint's `context` object is switching in as prototype for `Context` objects. +// +// Only `cwd` property and `extends` method are available in `createOnce`, so only those are implemented here. +// All other getters/methods throw, same as they do in main implementation. +const FILE_CONTEXT: FileContext = freeze({ + get filename(): string { + throw new Error('Cannot access `context.filename` in `createOnce`'); + }, + + get physicalFilename(): string { + throw new Error('Cannot access `context.physicalFilename` in `createOnce`'); + }, + + /** + * Current working directory. + */ + get cwd(): string { + // Note: We can allow accessing `cwd` in `createOnce`, as it's global + if (cwd === null) cwd = process.cwd(); + return cwd; + }, + + get sourceCode(): SourceCode { + throw new Error('Cannot access `context.sourceCode` in `createOnce`'); + }, + + get languageOptions(): LanguageOptions { + throw new Error('Cannot access `context.languageOptions` in `createOnce`'); + }, + + get settings(): Record { + throw new Error('Cannot access `context.settings` in `createOnce`'); + }, + + /** + * Create a new object with the current object as the prototype and + * the specified properties as its own properties. + * @param extension - The properties to add to the new object. + * @returns A new object with the current object as the prototype + * and the specified properties as its own properties. + */ + extend(this: FileContext, extension: Record): FileContext { + return freeze(ObjectAssign(ObjectCreate(this), extension)); + }, + + get parserOptions(): Record { + throw new Error('Cannot access `context.parserOptions` in `createOnce`'); + }, + + get parserPath(): string { + throw new Error('Cannot access `context.parserPath` in `createOnce`'); + }, + + getCwd(): string { + // TODO: Implement this? + throw new Error('`context.getCwd` is deprecated. Use `cwd` instead.'); + }, + + getFilename(): string { + throw new Error('Cannot call `context.getFilename` in `createOnce`'); + }, + + getPhysicalFilename(): string { + throw new Error('Cannot call `context.getPhysicalFilename` in `createOnce`'); + }, + + getSourceCode(): SourceCode { + throw new Error('Cannot call `context.getSourceCode` in `createOnce`'); + }, +}); + /** * Call `createOnce` method of rule, and return `Context`, `Visitor`, and `beforeHook` (if any). * @@ -142,10 +226,12 @@ function createContextAndVisitor(rule: CreateOnceRule): { if (typeof createOnce !== 'function') throw new Error('Rule `createOnce` property must be a function'); // Call `createOnce` with empty context object. - // Really, `context` should be an instance of `Context`, which would throw error on accessing e.g. `id` - // in body of `createOnce`. But any such bugs should have been caught when testing the rule in Oxlint, - // so should be OK to take this shortcut. - const context = ObjectCreate(null, { + // Really, accessing `options` or calling `report` should throw, because they're illegal in `createOnce`. + // But any such bugs should have been caught when testing the rule in Oxlint, so should be OK to take this shortcut. + // `FILE_CONTEXT` prototype provides `cwd` property and `extends` method, which are available in `createOnce`. + const context = ObjectCreate(FILE_CONTEXT, { + // TODO: Need to set `id` to full rule name - it's available in `createOnce`. + // Or make it inaccessible in `createOnce`. id: { value: '', enumerable: true, configurable: true }, options: { value: null, enumerable: true, configurable: true }, report: { value: null, enumerable: true, configurable: true }, diff --git a/apps/oxlint/src-js/plugins/context.ts b/apps/oxlint/src-js/plugins/context.ts index d444a24fc9d37..1fd1ae69ec093 100644 --- a/apps/oxlint/src-js/plugins/context.ts +++ b/apps/oxlint/src-js/plugins/context.ts @@ -259,6 +259,7 @@ const FILE_CONTEXT = freeze({ */ getCwd(): string { // TODO: Implement this? + // If do implement it, also implement `getCwd` in `index.ts` (`createOnce` shim for ESLint). throw new Error('`context.getCwd` is deprecated. Use `cwd` instead.'); }, @@ -297,7 +298,7 @@ const FILE_CONTEXT = freeze({ * Context object for a file. * Is the prototype for `Context` objects for each rule. */ -type FileContext = typeof FILE_CONTEXT; +export type FileContext = typeof FILE_CONTEXT; /** * Context object for a rule.