From bebc564fdf0587b2aa47bb13707031da676b851c Mon Sep 17 00:00:00 2001 From: JounQin Date: Thu, 15 Sep 2022 06:57:41 +0800 Subject: [PATCH] fix: parse error on void boolean attributes (#431) close #429 --- .changeset/khaki-squids-leave.md | 5 + packages/eslint-mdx/src/tokens.ts | 22 +- packages/eslint-mdx/src/worker.ts | 15 +- test/__snapshots__/fixtures.test.ts.snap | 23 + test/__snapshots__/parser.test.ts.snap | 696 +++++++++++++++++++++-- test/fixtures/429.mdx | 3 + 6 files changed, 716 insertions(+), 48 deletions(-) create mode 100644 .changeset/khaki-squids-leave.md create mode 100644 test/fixtures/429.mdx diff --git a/.changeset/khaki-squids-leave.md b/.changeset/khaki-squids-leave.md new file mode 100644 index 00000000..5656698e --- /dev/null +++ b/.changeset/khaki-squids-leave.md @@ -0,0 +1,5 @@ +--- +"eslint-mdx": patch +--- + +fix: parse error on void boolean attributes diff --git a/packages/eslint-mdx/src/tokens.ts b/packages/eslint-mdx/src/tokens.ts index 8c2954cc..d0b061b8 100644 --- a/packages/eslint-mdx/src/tokens.ts +++ b/packages/eslint-mdx/src/tokens.ts @@ -57,6 +57,8 @@ export const restoreTokens = ( const nodeStart = nodePos.start.offset const nodeEnd = nodePos.end.offset + const lastCharOffset = prevCharOffset(nodeEnd - 2) + assert(nodeStart != null) assert(nodeEnd != null) @@ -79,6 +81,8 @@ export const restoreTokens = ( const nodeName = node.name const nodeNameLength = nodeName?.length ?? 0 + const selfClosing = text[lastCharOffset] === '/' + let nodeNameStart = nodeStart + 1 if (nodeName) { @@ -200,15 +204,23 @@ export const restoreTokens = ( assert(text[lastAttrOffset] === attrQuote) } - const nextOffset = nextCharOffset(lastAttrOffset + 1) + let nextOffset = nextCharOffset(lastAttrOffset + 1) + let nextChar = text[nextOffset] - const nextChar = text[nextOffset] + const expectedNextChar = selfClosing ? '/' : '>' - // self closing tag - if (nextChar === '/') { + if (nextChar !== expectedNextChar) { + nextOffset = /** @type {number} */ nextCharOffset(lastAttrOffset) + nextChar = text[nextOffset] + } + + if (selfClosing) { tokens.push(newToken(tt.slash, nextOffset, nextOffset + 1, '/')) } else { - assert(nextChar === '>') + assert( + nextChar === '>', + `\`nextChar\` must be '>' but actually is '${nextChar}'`, + ) const prevOffset = prevCharOffset(nodeEnd - 2) diff --git a/packages/eslint-mdx/src/worker.ts b/packages/eslint-mdx/src/worker.ts index 21dd2996..70bdf637 100644 --- a/packages/eslint-mdx/src/worker.ts +++ b/packages/eslint-mdx/src/worker.ts @@ -526,11 +526,20 @@ runAsWorker( children, } - const nextOffset = nextCharOffset(lastAttrOffset + 1) + let nextOffset = nextCharOffset(lastAttrOffset + 1) + let nextChar = text[nextOffset] - const nextChar = text[nextOffset] + const expectedNextChar = selfClosing ? '/' : '>' - assert(nextChar === (selfClosing ? '/' : '>')) + if (nextChar !== expectedNextChar) { + nextOffset = /** @type {number} */ nextCharOffset(lastAttrOffset) + nextChar = text[nextOffset] + } + + assert( + nextChar === expectedNextChar, + `\`nextChar\` must be '${expectedNextChar}' but actually is '${nextChar}'`, + ) Object.assign( jsxEl.openingElement, diff --git a/test/__snapshots__/fixtures.test.ts.snap b/test/__snapshots__/fixtures.test.ts.snap index 39ba38b4..db1899e8 100644 --- a/test/__snapshots__/fixtures.test.ts.snap +++ b/test/__snapshots__/fixtures.test.ts.snap @@ -394,6 +394,29 @@ exports[`fixtures should match all snapshots: 391.mdx 1`] = ` ] `; +exports[`fixtures should match all snapshots: 429.mdx 1`] = ` +[ + { + "column": 17, + "endColumn": 17, + "endLine": 3, + "fix": { + "range": [ + 49, + 49, + ], + "text": " ", + }, + "line": 3, + "message": "Insert \`ยท\`", + "messageId": "insert", + "nodeType": null, + "ruleId": "prettier/prettier", + "severity": 2, + }, +] +`; + exports[`fixtures should match all snapshots: acorn.mdx 1`] = ` [ { diff --git a/test/__snapshots__/parser.test.ts.snap b/test/__snapshots__/parser.test.ts.snap index 48fd6fc2..99a4d85f 100644 --- a/test/__snapshots__/parser.test.ts.snap +++ b/test/__snapshots__/parser.test.ts.snap @@ -29989,6 +29989,644 @@ exports[`parser should match all AST snapshots: 391.mdx 1`] = ` } `; +exports[`parser should match all AST snapshots: 429.mdx 1`] = ` +{ + "body": [ + { + "end": 31, + "expression": { + "children": [], + "closingElement": { + "end": 31, + "loc": { + "end": { + "column": 31, + "line": 1, + "offset": 31, + }, + "start": { + "column": 22, + "line": 1, + "offset": 22, + }, + }, + "name": { + "end": 30, + "loc": { + "end": { + "column": 30, + "line": 1, + "offset": 30, + }, + "start": { + "column": 24, + "line": 1, + "offset": 24, + }, + }, + "name": "button", + "range": [ + 24, + 30, + ], + "raw": "button", + "start": 24, + "type": "JSXIdentifier", + }, + "range": [ + 22, + 31, + ], + "raw": "", + "start": 22, + "type": "JSXClosingElement", + }, + "end": 31, + "loc": { + "end": { + "column": 31, + "line": 1, + "offset": 31, + }, + "start": { + "column": 0, + "line": 1, + "offset": 0, + }, + }, + "openingElement": { + "attributes": [ + { + "end": 17, + "loc": { + "end": { + "column": 17, + "line": 1, + "offset": 17, + }, + "start": { + "column": 8, + "line": 1, + "offset": 8, + }, + }, + "name": { + "end": 16, + "loc": { + "end": { + "column": 16, + "line": 1, + "offset": 16, + }, + "start": { + "column": 8, + "line": 1, + "offset": 8, + }, + }, + "name": "disabled", + "range": [ + 8, + 16, + ], + "raw": "disabled", + "start": 8, + "type": "JSXIdentifier", + }, + "range": [ + 8, + 17, + ], + "raw": "disabled>", + "start": 8, + "type": "JSXAttribute", + "value": null, + }, + ], + "end": 17, + "loc": { + "end": { + "column": 17, + "line": 1, + "offset": 17, + }, + "start": { + "column": 0, + "line": 1, + "offset": 0, + }, + }, + "name": { + "end": 7, + "loc": { + "end": { + "column": 7, + "line": 1, + "offset": 7, + }, + "start": { + "column": 1, + "line": 1, + "offset": 1, + }, + }, + "name": "button", + "range": [ + 1, + 7, + ], + "raw": "button", + "start": 1, + "type": "JSXIdentifier", + }, + "range": [ + 0, + 17, + ], + "raw": "", + "start": 0, + "type": "JSXElement", + }, + "loc": { + "end": { + "column": 32, + "line": 1, + "offset": 31, + }, + "start": { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "range": [ + 0, + 31, + ], + "start": 0, + "type": "ExpressionStatement", + }, + { + "end": 51, + "expression": { + "children": [], + "closingElement": null, + "end": 51, + "loc": { + "end": { + "column": 18, + "line": 3, + "offset": 51, + }, + "start": { + "column": 0, + "line": 3, + "offset": 33, + }, + }, + "openingElement": { + "attributes": [ + { + "end": 50, + "loc": { + "end": { + "column": 17, + "line": 3, + "offset": 50, + }, + "start": { + "column": 8, + "line": 3, + "offset": 41, + }, + }, + "name": { + "end": 49, + "loc": { + "end": { + "column": 16, + "line": 3, + "offset": 49, + }, + "start": { + "column": 8, + "line": 3, + "offset": 41, + }, + }, + "name": "disabled", + "range": [ + 41, + 49, + ], + "raw": "disabled", + "start": 41, + "type": "JSXIdentifier", + }, + "range": [ + 41, + 50, + ], + "raw": "disabled/", + "start": 41, + "type": "JSXAttribute", + "value": null, + }, + ], + "end": 51, + "loc": { + "end": { + "column": 18, + "line": 3, + "offset": 51, + }, + "start": { + "column": 0, + "line": 3, + "offset": 33, + }, + }, + "name": { + "end": 40, + "loc": { + "end": { + "column": 7, + "line": 3, + "offset": 40, + }, + "start": { + "column": 1, + "line": 3, + "offset": 34, + }, + }, + "name": "button", + "range": [ + 34, + 40, + ], + "raw": "button", + "start": 34, + "type": "JSXIdentifier", + }, + "range": [ + 33, + 51, + ], + "raw": " + +