44
55import type { AST , Linter , Rule } from 'eslint'
66import type { Node , Parent , Nodes , Root , RootContentMap } from 'mdast'
7+ import type { MdxFlowExpression , MdxTextExpression } from 'mdast-util-mdx'
78
89import { fromMarkdown } from '../from-markdown.js'
910import { meta } from '../meta.js'
1011
11- import type { Block , RangeMap } from './types.js'
12+ import type { CodeBlock , RangeMap } from './types.js'
1213
1314const UNSATISFIABLE_RULES = new Set ( [
1415 'eol-last' , // The Markdown parser strips trailing newlines in code fences
@@ -18,7 +19,7 @@ const SUPPORTS_AUTOFIX = true
1819
1920const BOM = '\uFEFF'
2021
21- const blocksCache : Map < string , Block [ ] > = new Map ( )
22+ const blocksCache : Map < string , CodeBlock [ ] > = new Map ( )
2223
2324/**
2425 * Performs a depth-first traversal of the Markdown AST.
@@ -64,10 +65,10 @@ const COMMENTS = [
6465const eslintCommentRegex = / ^ (?: e s l i n t \b | g l o b a l \s ) / u
6566
6667/**
67- * Extracts `eslint-*` or `global` comments from HTML comments if present.
68- * @param value The text content of an HTML AST node.
68+ * Extracts `eslint-*` or `global` comments from HTML/MDX comments if present.
69+ * @param value The text content of an HTML/MDX AST node.
6970 * @returns The comment's text without the opening and closing tags or
70- * an empty string if the text is not an ESLint HTML comment.
71+ * an empty string if the text is not an ESLint HTML/MDX comment.
7172 */
7273function getComment ( value : string , isMdx = false ) {
7374 const [ commentStart , commentEnd ] = COMMENTS [ + isMdx ]
@@ -241,14 +242,15 @@ function getBlockRangeMap(text: string, node: Node, comments: string[]) {
241242 return rangeMap
242243}
243244
244- const codeBlockFileNameRegex = / f i l e n a m e = ( [ " ' ] ) .* ?\1/ u
245+ // eslint-disable-next-line sonarjs/unused-named-groups -- https://community.sonarsource.com/t/names-of-regular-expressions-named-groups-should-be-used-for-self-reference/138306
246+ const codeBlockFileNameRegex = / f i l e n a m e = (?< quote > [ " ' ] ) (?< filename > .* ?) \1/ u
245247
246248/**
247249 * Parses the file name from a block meta, if available.
248250 * @param block A code block.
249251 * @returns The filename, if parsed from block meta.
250252 */
251- function fileNameFromMeta ( block : Block ) {
253+ function fileNameFromMeta ( block : CodeBlock ) {
252254 // istanbul ignore next
253255 return codeBlockFileNameRegex
254256 . exec ( block . meta )
@@ -270,19 +272,28 @@ function preprocess(sourceText: string, filename: string) {
270272 // FIXME: how to read `extensions` and `markdownExtensions` parser options?
271273 filename . endsWith ( '.mdx' ) ,
272274 )
273- const blocks : Block [ ] = [ ]
275+ const blocks : CodeBlock [ ] = [ ]
274276
275277 blocksCache . set ( filename , blocks )
276278
277279 /**
278- * During the depth-first traversal, keep track of any sequences of HTML
280+ * During the depth-first traversal, keep track of any sequences of HTML/MDX
279281 * comment nodes containing `eslint-*` or `global` comments. If a code
280282 * block immediately follows such a sequence, insert the comments at the
281283 * top of the code block. Any non-ESLint comment or other node type breaks
282284 * and empties the sequence.
283285 */
284286 let allComments : string [ ] = [ ]
285287
288+ function mdxExpression ( node : MdxFlowExpression | MdxTextExpression ) {
289+ const comment = getComment ( node . value , true )
290+ if ( comment ) {
291+ allComments . push ( comment )
292+ } else {
293+ allComments = [ ]
294+ }
295+ }
296+
286297 traverse ( ast , {
287298 '*' ( ) {
288299 allComments = [ ]
@@ -330,25 +341,11 @@ function preprocess(sourceText: string, filename: string) {
330341 allComments = [ ]
331342 }
332343 } ,
333- mdxFlowExpression ( node ) {
334- const comment = getComment ( node . value , true )
335- if ( comment ) {
336- allComments . push ( comment )
337- } else {
338- allComments = [ ]
339- }
340- } ,
341- mdxTextExpression ( node ) {
342- const comment = getComment ( node . value , true )
343- if ( comment ) {
344- allComments . push ( comment )
345- } else {
346- allComments = [ ]
347- }
348- } ,
344+
345+ mdxFlowExpression : mdxExpression ,
346+ mdxTextExpression : mdxExpression ,
349347 } )
350348
351- // istanbul ignore next
352349 return blocks . map ( ( block , index ) => {
353350 const [ language ] = block . lang . trim ( ) . split ( ' ' )
354351 return {
@@ -364,7 +361,7 @@ function preprocess(sourceText: string, filename: string) {
364361 * @param fix A fix to adjust.
365362 * @returns The fix with adjusted ranges.
366363 */
367- function adjustFix ( block : Block , fix : Rule . Fix ) : Rule . Fix {
364+ function adjustFix ( block : CodeBlock , fix : Rule . Fix ) : Rule . Fix {
368365 return {
369366 range : fix . range . map ( range => {
370367 // Advance through the block's range map to find the last
@@ -388,7 +385,7 @@ function adjustFix(block: Block, fix: Rule.Fix): Rule.Fix {
388385 * @param block A code block.
389386 * @returns A function that adjusts messages in a code block.
390387 */
391- function adjustBlock ( block : Block ) {
388+ function adjustBlock ( block : CodeBlock ) {
392389 const leadingCommentLines = block . comments . reduce (
393390 ( count , comment ) => count + comment . split ( '\n' ) . length ,
394391 0 ,
0 commit comments