Skip to content

Commit 5e99ed3

Browse files
committed
refactor(linter/plugins): allow nullish values as message or messageId (#14422)
It makes the types a bit shit, but so far we accept `null` or `undefined` for all properties, and treat them the same as the property being absent. Extend this convention to `message` and `messageId` properties. Also split out getting the message from a diagnostic into a separate function, to make this pattern simpler via early return.
1 parent dc30938 commit 5e99ed3

File tree

2 files changed

+29
-29
lines changed

2 files changed

+29
-29
lines changed

apps/oxlint/src-js/index.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,7 @@ import type { CreateOnceRule, Plugin, Rule } from './plugins/load.ts';
33
import type { BeforeHook, Visitor, VisitorWithHooks } from './plugins/types.ts';
44

55
export type * as ESTree from './generated/types.d.ts';
6-
export type {
7-
Context,
8-
Diagnostic,
9-
DiagnosticBase,
10-
DiagnosticWithLoc,
11-
DiagnosticWithMessageId,
12-
DiagnosticWithNode,
13-
} from './plugins/context.ts';
6+
export type { Context, Diagnostic, DiagnosticBase, DiagnosticWithLoc, DiagnosticWithNode } from './plugins/context.ts';
147
export type { Fix, Fixer, FixFn } from './plugins/fix.ts';
158
export type { CreateOnceRule, CreateRule, Plugin, Rule } from './plugins/load.ts';
169
export type {

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

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,23 @@ import type { Location, Ranged } from './types.ts';
99
const { hasOwn, keys: ObjectKeys } = Object;
1010

1111
// Diagnostic in form passed by user to `Context#report()`
12-
export type Diagnostic = DiagnosticWithNode | DiagnosticWithLoc | DiagnosticWithMessageId;
12+
export type Diagnostic = DiagnosticWithNode | DiagnosticWithLoc;
1313

1414
export interface DiagnosticBase {
15-
message?: string;
15+
message?: string | null | undefined;
16+
messageId?: string | null | undefined;
1617
data?: Record<string, string | number> | null | undefined;
1718
fix?: FixFn;
1819
}
1920

2021
export interface DiagnosticWithNode extends DiagnosticBase {
21-
message: string;
2222
node: Ranged;
2323
}
2424

2525
export interface DiagnosticWithLoc extends DiagnosticBase {
26-
message: string;
2726
loc: Location;
2827
}
2928

30-
export interface DiagnosticWithMessageId extends DiagnosticBase {
31-
messageId: string;
32-
node?: Ranged;
33-
loc?: Location;
34-
}
35-
3629
// Diagnostic in form sent to Rust
3730
interface DiagnosticReport {
3831
message: string;
@@ -158,16 +151,8 @@ export class Context {
158151
report(diagnostic: Diagnostic): void {
159152
const internal = getInternal(this, 'report errors');
160153

161-
// Resolve message from messageId if present
162-
let message: string;
163-
if (hasOwn(diagnostic, 'messageId')) {
164-
message = resolveMessage((diagnostic as DiagnosticWithMessageId).messageId, internal);
165-
} else {
166-
message = diagnostic.message;
167-
if (typeof message !== 'string') {
168-
throw new TypeError('Either `message` or `messageId` is required');
169-
}
170-
}
154+
// Get message, resolving message from `messageId` if present
155+
let message = getMessage(diagnostic, internal);
171156

172157
// Interpolate placeholders {{key}} with data values
173158
if (hasOwn(diagnostic, 'data')) {
@@ -239,14 +224,36 @@ export class Context {
239224
}
240225
}
241226

227+
/**
228+
* Get message from diagnostic.
229+
* @param diagnostic - Diagnostic object
230+
* @param internal - Internal context object
231+
* @returns Message string
232+
* @throws {Error|TypeError} If neither `message` nor `messageId` provided, or of wrong type
233+
*/
234+
function getMessage(diagnostic: Diagnostic, internal: InternalContext): string {
235+
if (hasOwn(diagnostic, 'messageId')) {
236+
const { messageId } = diagnostic as { messageId: string | null | undefined };
237+
if (messageId != null) return resolveMessageFromMessageId(messageId, internal);
238+
}
239+
240+
if (hasOwn(diagnostic, 'message')) {
241+
const { message } = diagnostic;
242+
if (typeof message === 'string') return message;
243+
if (message != null) throw new TypeError('`message` must be a string');
244+
}
245+
246+
throw new Error('Either `message` or `messageId` is required');
247+
}
248+
242249
/**
243250
* Resolve a message ID to its message string, with optional data interpolation.
244251
* @param messageId - The message ID to resolve
245252
* @param internal - Internal context containing messages
246253
* @returns Resolved message string
247254
* @throws {Error} If `messageId` is not found in `messages`
248255
*/
249-
function resolveMessage(messageId: string, internal: InternalContext): string {
256+
function resolveMessageFromMessageId(messageId: string, internal: InternalContext): string {
250257
const { messages } = internal;
251258
if (messages === null) {
252259
throw new Error(`Cannot use messageId '${messageId}' - rule does not define any messages in meta.messages`);

0 commit comments

Comments
 (0)