@@ -9,21 +9,29 @@ import type { Location, Ranged } from './types.ts';
99const { hasOwn } = Object ;
1010
1111// Diagnostic in form passed by user to `Context#report()`
12- export type Diagnostic = DiagnosticWithNode | DiagnosticWithLoc ;
12+ export type Diagnostic = DiagnosticWithNode | DiagnosticWithLoc | DiagnosticWithMessageId ;
1313
1414export interface DiagnosticBase {
15- message : string ;
15+ message ? : string ;
1616 fix ?: FixFn ;
1717}
1818
1919export interface DiagnosticWithNode extends DiagnosticBase {
20+ message : string ;
2021 node : Ranged ;
2122}
2223
2324export interface DiagnosticWithLoc extends DiagnosticBase {
25+ message : string ;
2426 loc : Location ;
2527}
2628
29+ export interface DiagnosticWithMessageId extends DiagnosticBase {
30+ messageId : string ;
31+ node ?: Ranged ;
32+ loc ?: Location ;
33+ }
34+
2735// Diagnostic in form sent to Rust
2836interface DiagnosticReport {
2937 message : string ;
@@ -83,6 +91,8 @@ export interface InternalContext {
8391 options : unknown [ ] ;
8492 // `true` if rule can provide fixes (`meta.fixable` in `RuleMeta` is 'code' or 'whitespace')
8593 isFixable : boolean ;
94+ // Message templates for messageId support
95+ messages : Record < string , string > | null ;
8696}
8797
8898/**
@@ -98,14 +108,17 @@ export class Context {
98108 /**
99109 * @class
100110 * @param fullRuleName - Rule name, in form `<plugin>/<rule>`
111+ * @param isFixable - Whether the rule can provide fixes
112+ * @param messages - Message templates for messageId support
101113 */
102- constructor ( fullRuleName : string , isFixable : boolean ) {
114+ constructor ( fullRuleName : string , isFixable : boolean , messages : Record < string , string > | null = null ) {
103115 this . #internal = {
104116 id : fullRuleName ,
105117 filePath : '' ,
106118 ruleIndex : - 1 ,
107119 options : [ ] ,
108120 isFixable,
121+ messages,
109122 } ;
110123 }
111124
@@ -144,8 +157,21 @@ export class Context {
144157 report ( diagnostic : Diagnostic ) : void {
145158 const internal = getInternal ( this , 'report errors' ) ;
146159
160+ // Resolve message from messageId if present
161+ let message : string ;
162+ if ( hasOwn ( diagnostic , 'messageId' ) ) {
163+ const diagWithMessageId = diagnostic as DiagnosticWithMessageId ;
164+ message = this . #resolveMessage( diagWithMessageId . 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+ }
171+
147172 // TODO: Validate `diagnostic`
148173 let start : number , end : number , loc : Location ;
174+
149175 if ( hasOwn ( diagnostic , 'loc' ) && ( loc = ( diagnostic as DiagnosticWithLoc ) . loc ) != null ) {
150176 // `loc`
151177 if ( typeof loc !== 'object' ) throw new TypeError ( '`loc` must be an object' ) ;
@@ -177,14 +203,42 @@ export class Context {
177203 }
178204
179205 diagnostics . push ( {
180- message : diagnostic . message ,
206+ message,
181207 start,
182208 end,
183209 ruleIndex : internal . ruleIndex ,
184210 fixes : getFixes ( diagnostic , internal ) ,
185211 } ) ;
186212 }
187213
214+ /**
215+ * Resolve a messageId to its message string.
216+ * @param messageId - The message ID to resolve
217+ * @param internal - Internal context containing messages
218+ * @returns Resolved message string
219+ * @throws {Error } If messageId is not found in messages
220+ */
221+ #resolveMessage(
222+ messageId : string ,
223+ internal : InternalContext ,
224+ ) : string {
225+ const { messages } = internal ;
226+
227+ if ( ! messages ) {
228+ throw new Error ( `Cannot use messageId '${ messageId } ' - rule does not define any messages in meta.messages` ) ;
229+ }
230+
231+ if ( ! hasOwn ( messages , messageId ) ) {
232+ throw new Error (
233+ `Unknown messageId '${ messageId } '. Available messages: ${
234+ Object . keys ( messages ) . map ( ( msg ) => `'${ msg } '` ) . join ( ', ' )
235+ } `,
236+ ) ;
237+ }
238+
239+ return messages [ messageId ] ;
240+ }
241+
188242 static {
189243 setupContextForFile = ( context , ruleIndex , filePath ) => {
190244 // TODO: Support `options`
0 commit comments