Repository: vuejs/vue-eslint-parser
Pull request #257: fix: text token type in V_EXPRESSION_DATA state
Author: login: woongsikchoi
Created: 2025-03-19T03:39:12Z | Merged: 2025-04-05T03:12:57Z

This PR fixes the problem that `HTMLRCDataText` and `HTMLRawText` token types were reversed inside `V_EXPRESSION_DATA` state.


Diff:
diff --git a/src/html/tokenizer.ts b/src/html/tokenizer.ts
index 6285cdfa..ad95bc81 100644
--- a/src/html/tokenizer.ts
+++ b/src/html/tokenizer.ts
@@ -1892,9 +1892,9 @@ export class Tokenizer {
             const type = isWhitespace(cp)
                 ? "HTMLWhitespace"
                 : state === "RCDATA"
-                  ? "HTMLRawText"
+                  ? "HTMLRCDataText"
                   : state === "RAWTEXT"
-                    ? "HTMLRCDataText"
+                    ? "HTMLRawText"
                     : "HTMLText"
             if (this.currentToken != null && this.currentToken.type !== type) {
                 this.endToken()
diff --git a/test/fixtures/ast/mustache-errors-in-textarea/ast.json b/test/fixtures/ast/mustache-errors-in-textarea/ast.json
new file mode 100644
index 00000000..9dd73fed
--- /dev/null
+++ b/test/fixtures/ast/mustache-errors-in-textarea/ast.json
@@ -0,0 +1,495 @@
+{
+    "type": "Program",
+    "start": 0,
+    "end": 0,
+    "loc": {
+        "start": {
+            "line": 1,
+            "column": 0
+        },
+        "end": {
+            "line": 1,
+            "column": 0
+        }
+    },
+    "range": [
+        0,
+        0
+    ],
+    "body": [],
+    "sourceType": "module",
+    "comments": [],
+    "tokens": [],
+    "templateBody": {
+        "type": "VElement",
+        "range": [
+            0,
+            59
+        ],
+        "loc": {
+            "start": {
+                "line": 1,
+                "column": 0
+            },
+            "end": {
+                "line": 3,
+                "column": 11
+            }
+        },
+        "name": "template",
+        "rawName": "template",
+        "namespace": "http://www.w3.org/1999/xhtml",
+        "startTag": {
+            "type": "VStartTag",
+            "range": [
+                0,
+                10
+            ],
+            "loc": {
+                "start": {
+                    "line": 1,
+                    "column": 0
+                },
+                "end": {
+                    "line": 1,
+                    "column": 10
+                }
+            },
+            "selfClosing": false,
+            "attributes": []
+        },
+        "children": [
+            {
+                "type": "VText",
+                "range": [
+                    10,
+                    15
+                ],
+                "loc": {
+                    "start": {
+                        "line": 1,
+                        "column": 10
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 4
+                    }
+                },
+                "value": "\n    "
+            },
+            {
+                "type": "VElement",
+                "range": [
+                    15,
+                    47
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 4
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 36
+                    }
+                },
+                "name": "textarea",
+                "rawName": "textarea",
+                "namespace": "http://www.w3.org/1999/xhtml",
+                "startTag": {
+                    "type": "VStartTag",
+                    "range": [
+                        15,
+                        25
+                    ],
+                    "loc": {
+                        "start": {
+                            "line": 2,
+                            "column": 4
+                        },
+                        "end": {
+                            "line": 2,
+                            "column": 14
+                        }
+                    },
+                    "selfClosing": false,
+                    "attributes": []
+                },
+                "children": [
+                    {
+                        "type": "VExpressionContainer",
+                        "range": [
+                            25,
+                            36
+                        ],
+                        "loc": {
+                            "start": {
+                                "line": 2,
+                                "column": 14
+                            },
+                            "end": {
+                                "line": 2,
+                                "column": 25
+                            }
+                        },
+                        "expression": null,
+                        "references": []
+                    }
+                ],
+                "endTag": {
+                    "type": "VEndTag",
+                    "range": [
+                        36,
+                        47
+                    ],
+                    "loc": {
+                        "start": {
+                            "line": 2,
+                            "column": 25
+                        },
+                        "end": {
+                            "line": 2,
+                            "column": 36
+                        }
+                    }
+                },
+                "variables": []
+            },
+            {
+                "type": "VText",
+                "range": [
+                    47,
+                    48
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 36
+                    },
+                    "end": {
+                        "line": 3,
+                        "column": 0
+                    }
+                },
+                "value": "\n"
+            }
+        ],
+        "endTag": {
+            "type": "VEndTag",
+            "range": [
+                48,
+                59
+            ],
+            "loc": {
+                "start": {
+                    "line": 3,
+                    "column": 0
+                },
+                "end": {
+                    "line": 3,
+                    "column": 11
+                }
+            }
+        },
+        "variables": [],
+        "tokens": [
+            {
+                "type": "HTMLTagOpen",
+                "range": [
+                    0,
+                    9
+                ],
+                "loc": {
+                    "start": {
+                        "line": 1,
+                        "column": 0
+                    },
+                    "end": {
+                        "line": 1,
+                        "column": 9
+                    }
+                },
+                "value": "template"
+            },
+            {
+                "type": "HTMLTagClose",
+                "range": [
+                    9,
+                    10
+                ],
+                "loc": {
+                    "start": {
+                        "line": 1,
+                        "column": 9
+                    },
+                    "end": {
+                        "line": 1,
+                        "column": 10
+                    }
+                },
+                "value": ""
+            },
+            {
+                "type": "HTMLWhitespace",
+                "range": [
+                    10,
+                    15
+                ],
+                "loc": {
+                    "start": {
+                        "line": 1,
+                        "column": 10
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 4
+                    }
+                },
+                "value": "\n    "
+            },
+            {
+                "type": "HTMLTagOpen",
+                "range": [
+                    15,
+                    24
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 4
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 13
+                    }
+                },
+                "value": "textarea"
+            },
+            {
+                "type": "HTMLTagClose",
+                "range": [
+                    24,
+                    25
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 13
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 14
+                    }
+                },
+                "value": ""
+            },
+            {
+                "type": "VExpressionStart",
+                "range": [
+                    25,
+                    27
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 14
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 16
+                    }
+                },
+                "value": "{{"
+            },
+            {
+                "type": "HTMLRCDataText",
+                "range": [
+                    27,
+                    30
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 16
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 19
+                    }
+                },
+                "value": "foo"
+            },
+            {
+                "type": "HTMLWhitespace",
+                "range": [
+                    30,
+                    31
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 19
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 20
+                    }
+                },
+                "value": " "
+            },
+            {
+                "type": "HTMLRCDataText",
+                "range": [
+                    31,
+                    34
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 20
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 23
+                    }
+                },
+                "value": "bar"
+            },
+            {
+                "type": "VExpressionEnd",
+                "range": [
+                    34,
+                    36
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 23
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 25
+                    }
+                },
+                "value": "}}"
+            },
+            {
+                "type": "HTMLEndTagOpen",
+                "range": [
+                    36,
+                    46
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 25
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 35
+                    }
+                },
+                "value": "textarea"
+            },
+            {
+                "type": "HTMLTagClose",
+                "range": [
+                    46,
+                    47
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 35
+                    },
+                    "end": {
+                        "line": 2,
+                        "column": 36
+                    }
+                },
+                "value": ""
+            },
+            {
+                "type": "HTMLWhitespace",
+                "range": [
+                    47,
+                    48
+                ],
+                "loc": {
+                    "start": {
+                        "line": 2,
+                        "column": 36
+                    },
+                    "end": {
+                        "line": 3,
+                        "column": 0
+                    }
+                },
+                "value": "\n"
+            },
+            {
+                "type": "HTMLEndTagOpen",
+                "range": [
+                    48,
+                    58
+                ],
+                "loc": {
+                    "start": {
+                        "line": 3,
+                        "column": 0
+                    },
+                    "end": {
+                        "line": 3,
+                        "column": 10
+                    }
+                },
+                "value": "template"
+            },
+            {
+                "type": "HTMLTagClose",
+                "range": [
+                    58,
+                    59
+                ],
+                "loc": {
+                    "start": {
+                        "line": 3,
+                        "column": 10
+                    },
+                    "end": {
+                        "line": 3,
+                        "column": 11
+                    }
+                },
+                "value": ""
+            },
+            {
+                "type": "HTMLWhitespace",
+                "range": [
+                    59,
+                    60
+                ],
+                "loc": {
+                    "start": {
+                        "line": 3,
+                        "column": 11
+                    },
+                    "end": {
+                        "line": 4,
+                        "column": 0
+                    }
+                },
+                "value": "\n"
+            }
+        ],
+        "comments": [],
+        "errors": [
+            {
+                "message": "Unexpected token bar",
+                "index": 31,
+                "lineNumber": 2,
+                "column": 20
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/test/fixtures/ast/mustache-errors-in-textarea/source.vue b/test/fixtures/ast/mustache-errors-in-textarea/source.vue
new file mode 100644
index 00000000..da4a40ab
--- /dev/null
+++ b/test/fixtures/ast/mustache-errors-in-textarea/source.vue
@@ -0,0 +1,3 @@
+<template>
+    <textarea>{{foo bar}}</textarea>
+</template>
diff --git a/test/fixtures/ast/mustache-errors-in-textarea/token-ranges.json b/test/fixtures/ast/mustache-errors-in-textarea/token-ranges.json
new file mode 100644
index 00000000..8f3b13ea
--- /dev/null
+++ b/test/fixtures/ast/mustache-errors-in-textarea/token-ranges.json
@@ -0,0 +1,18 @@
+[
+    "<template",
+    ">",
+    "\n    ",
+    "<textarea",
+    ">",
+    "{{",
+    "foo",
+    " ",
+    "bar",
+    "}}",
+    "</textarea",
+    ">",
+    "\n",
+    "</template",
+    ">",
+    "\n"
+]
\ No newline at end of file
diff --git a/test/fixtures/ast/mustache-errors-in-textarea/tree.json b/test/fixtures/ast/mustache-errors-in-textarea/tree.json
new file mode 100644
index 00000000..f5b30cc1
--- /dev/null
+++ b/test/fixtures/ast/mustache-errors-in-textarea/tree.json
@@ -0,0 +1,49 @@
+[
+    {
+        "type": "VElement",
+        "text": "<template>\n    <textarea>{{foo bar}}</textarea>\n</template>",
+        "children": [
+            {
+                "type": "VStartTag",
+                "text": "<template>",
+                "children": []
+            },
+            {
+                "type": "VText",
+                "text": "\n    ",
+                "children": []
+            },
+            {
+                "type": "VElement",
+                "text": "<textarea>{{foo bar}}</textarea>",
+                "children": [
+                    {
+                        "type": "VStartTag",
+                        "text": "<textarea>",
+                        "children": []
+                    },
+                    {
+                        "type": "VExpressionContainer",
+                        "text": "{{foo bar}}",
+                        "children": []
+                    },
+                    {
+                        "type": "VEndTag",
+                        "text": "</textarea>",
+                        "children": []
+                    }
+                ]
+            },
+            {
+                "type": "VText",
+                "text": "\n",
+                "children": []
+            },
+            {
+                "type": "VEndTag",
+                "text": "</template>",
+                "children": []
+            }
+        ]
+    }
+]
\ No newline at end of file


Reviews:
user: login: ota-meshi
body: Thank you for the explanation and this PR!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-05T01:27:30Z
user: login: ws807
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T02:47:01Z
user: login: ota-meshi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T03:12:00Z
user: login: ota-meshi
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-05T03:12:14Z

Review comments:
diff_hunk: @@ -0,0 +1,3 @@
+<template>
+    <textarea>{{foo bar}}</textarea>
path: test/fixtures/ast/mustache-errors-in-textarea/source.vue
user: login: ota-meshi
body: Is there any purpose to using an invalid expression?
If we just want to validate the token type, I think it would be fine to use code similar to the example in #261.

```suggestion
    <textarea>foo{{bar}}</textarea>
```
created_at: 2025-04-05T01:27:19Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,3 @@
+<template>
+    <textarea>{{foo bar}}</textarea>
path: test/fixtures/ast/mustache-errors-in-textarea/source.vue
user: login: ws807
body: The invalid expression is needed because valid ones get converted to JS tokens like `Identifier` and we cannot distinguish between `HTMLRCDataText` and `HTMLRawText`.

Note that the example in #261 also contains an invalid expression because of the semicolon in `bar;`.
created_at: 2025-04-05T02:47:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,3 @@
+<template>
+    <textarea>{{foo bar}}</textarea>
path: test/fixtures/ast/mustache-errors-in-textarea/source.vue
user: login: ota-meshi
body: Thank you for the explanation :heart: 
created_at: 2025-04-05T03:11:59Z
author_association: MEMBER

PR comments:
user: login: ota-meshi
created_at: 2025-04-01T00:09:36Z
body: I'm not sure what this PR solves.
Could you please open an issue and explain the problem in more detail? Also, please share a repo where we can reproduce the problem.

author_association: MEMBER
user: login: ws807
created_at: 2025-04-04T10:16:19Z
body: I have opened issue #261 explaining the problem in more detail with a reproduction step.
However, I could not find any visible problems caused by this issue.
author_association: CONTRIBUTOR