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
57 changes: 14 additions & 43 deletions apps/oxlint/src-js/plugins/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,68 +516,39 @@ export function createContext(ruleDetails: RuleDetails): Readonly<Context> {
return Object.freeze({
// Inherit from `FILE_CONTEXT`, which provides getters for file-specific properties
__proto__: FILE_CONTEXT,

// Rule ID, in form `<plugin>/<rule>`
get id(): string {
// It's not possible to allow access to `id` in `createOnce` in ESLint compatibility mode, so we don't
// allow it here either. It's probably not very useful anyway - a rule should know what its own name is!
if (filePath === null) throw new Error("Cannot access `context.id` in `createOnce`");
return ruleDetails.fullName;
},

// Getter for rule options for this rule on this file
get options(): Readonly<Options> {
if (filePath === null) throw new Error("Cannot access `context.options` in `createOnce`");
debugAssertIsNonNull(ruleDetails.options);
return ruleDetails.options;
},

/**
* Report error.
*
* Normally called with a single `Diagnostic` object.
*
* Can also be called with legacy positional forms:
* - `context.report(node, message, data?, fix?)`
* - `context.report(node, loc, message, data?, fix?)`
* These legacy forms are not included in type def for this method, as they are deprecated,
* but some plugins still use them, so we support them.
*
* @param diagnostic - Diagnostic object
* @throws {TypeError} If `diagnostic` is invalid
*/
report(this: void, diagnostic: Diagnostic): void {
const normalizedDiagnostic = normalizeReportCallArgs(diagnostic, arguments);

report(this: void, diagnostic: Diagnostic, ...extraArgs: unknown[]): void {
// Delegate to `report` implementation shared between all rules, passing rule-specific details (`RuleDetails`)
report(normalizedDiagnostic, ruleDetails);
report(diagnostic, extraArgs, ruleDetails);
},
} as unknown as Context); // It seems TS can't understand `__proto__: FILE_CONTEXT`
}

/**
* Normalize `context.report()` arguments.
*
* Supports both forms:
* 1. New-style: `context.report({ ...descriptor })`
* 2. Legacy: `context.report(node, message, data?, fix?)`
* or `context.report(node, loc, message, data?, fix?)`
*/
function normalizeReportCallArgs(diagnostic: Diagnostic, args: IArguments): Diagnostic {
// New-style call already has a descriptor object.
if (args.length <= 1) return diagnostic;

// Legacy positional forms.
const node = diagnostic as unknown;
let loc: unknown = undefined,
message: unknown,
data: unknown = undefined,
fix: unknown = undefined;

if (typeof args[1] === "string") {
// [node, message, data, fix]
message = args[1];
data = args[2];
fix = args[3];
} else {
// [node, loc, message, data, fix]
loc = args[1];
message = args[2];
data = args[3];
fix = args[4];
}

const descriptor: Record<string, unknown> = { node, message };
if (loc !== undefined) descriptor.loc = loc;
if (data !== undefined) descriptor.data = data;
if (fix !== undefined) descriptor.fix = fix;
return descriptor as Diagnostic;
}
44 changes: 43 additions & 1 deletion apps/oxlint/src-js/plugins/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,20 @@ export const PLACEHOLDER_REGEX = /\{\{([^{}]+)\}\}/gu;
/**
* Report error.
* @param diagnostic - Diagnostic object
* @param extraArgs - Extra arguments passed to `context.report()` (legacy positional forms)
* @param ruleDetails - `RuleDetails` object, containing rule-specific details e.g. `isFixable`
* @throws {TypeError} If `diagnostic` is invalid
*/
export function report(diagnostic: Diagnostic, ruleDetails: RuleDetails): void {
export function report(
diagnostic: Diagnostic,
extraArgs: unknown[],
ruleDetails: RuleDetails,
): void {
if (filePath === null) throw new Error("Cannot report errors in `createOnce`");

// Handle legacy positional forms
if (extraArgs.length > 0) diagnostic = convertLegacyCallArgs(diagnostic, extraArgs);

const { message, messageId } = getMessage(
Object.hasOwn(diagnostic, "message") ? diagnostic.message : null,
diagnostic,
Expand Down Expand Up @@ -195,6 +203,40 @@ export function report(diagnostic: Diagnostic, ruleDetails: RuleDetails): void {
if (CONFORMANCE) diagnostics.at(-1)!.loc = conformedLoc;
}

/**
* Convert legacy `context.report()` arguments to a `Diagnostic` object.
*
* Supported:
* - `context.report(node, message, data?, fix?)`
* - `context.report(node, loc, message, data?, fix?)`
*
* @param node - Node to report (first argument)
* @param extraArgs - Extra arguments passed to `context.report()`
* @returns Diagnostic object
*/
function convertLegacyCallArgs(node: unknown, extraArgs: unknown[]): Diagnostic {
const firstExtraArg = extraArgs[0];
if (typeof firstExtraArg === "string") {
// `context.report(node, message, data, fix)`
return {
message: firstExtraArg,
node,
loc: undefined,
data: extraArgs[1],
fix: extraArgs[2],
} as Diagnostic;
}

// `context.report(node, loc, message, data, fix)`
return {
message: extraArgs[1],
node,
loc: firstExtraArg,
data: extraArgs[2],
fix: extraArgs[3],
} as Diagnostic;
}

/**
* Get message from a diagnostic or suggestion.
*
Expand Down
Loading