Skip to content

Is it possible to detect unclosed JSX/HTML tags? #474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
alxndrsn opened this issue Mar 4, 2025 · 5 comments
Open

Is it possible to detect unclosed JSX/HTML tags? #474

alxndrsn opened this issue Mar 4, 2025 · 5 comments

Comments

@alxndrsn
Copy link
Contributor

alxndrsn commented Mar 4, 2025

I occasionally run into bugs in my astro projects when a JSX tag is left unclosed. Depending on following tags, this may pass linting, or may fail cryptically later.

E.g.

Passes

<div>
  <div>
    <unclosed>
  </div>
</div>

Fails

<div>
  <div>
    <unclosed>
  </div>
  <!-- a comment -->
</div>

Fails with:

Parsing error: Unknown token at 39, expected: "<!--", actual: "</div>\n  <"

The error message itself seems a bit confusing.


I've created a repo with reproduction at https://github.com/alxndrsn/astro-eslint-parser-weirdness

@ota-meshi
Copy link
Owner

I think that's a problem with @astrojs/compiler.
@astrojs/compiler parses it as the following HTML, which fails because the parser can't generate a correct position from the broken AST.

<div>
  <div>
    <unclosed>
    <!-- a comment -->
  </div>
</div>

@alxndrsn
Copy link
Contributor Author

alxndrsn commented Apr 1, 2025

@ota-meshi how can I confirm that?

It looks like astro compiler is coping OK:

(await require('@astrojs/compiler').convertToTSX('<div>\n  <div>\n    <unclosed>\n  </div>\n  <!-- a comment -->\n</div>\n')).code
'/* @jsxImportSource astro */\n' +
  '\n' +
  '<Fragment>\n' +
  '<div>\n' +
  '  <div>\n' +
  '    <unclosed>\n' +
  '  </unclosed>\n' +
  '  {/** a comment */}\n' +
  '</div>\n' +
  '</div>\n' +
  '</Fragment>\n' +
  'export default function __AstroComponent_(_props: Record<string, any>): any {}\n' +
  '\n' +
  '//# sourceMappingURL=data:application/json;charset=utf-8;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiPHN0ZGluPiJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiXHUwMDNjZGl2XHUwMDNlXG4gIFx1MDAzY2Rpdlx1MDAzZVxuICAgIFx1MDAzY3VuY2xvc2VkXHUwMDNlXG4gIFx1MDAzYy9kaXZcdTAwM2VcbiAgXHUwMDNjIS0tIGEgY29tbWVudCAtLVx1MDAzZVxuXHUwMDNjL2Rpdlx1MDAzZVxuIl0sCiAgIm1hcHBpbmdzIjogIjs7QUFBQSxBQUFBO0FBQUEsQ0FBQyxHQUFHLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUM7QUFDUCxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDO0FBQ2QsQ0FBQyxDQUFDLEVBQUUsUUFDRCxDQUFDLEFBREk7QUFDUixDQUFDLEFBSkQsS0FJTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEFBSmhCLElBSW9CO0FBQ3BCLEVBQUUsR0FBRyxDQUFDO0FBQ0osRUFBRSxHQUFHLENBQUMsQUFBUixBQU5BO0FBQUE7QUFBQTsiLAogICJuYW1lcyI6IFtdCn0='

This is from the same project as the original bug report.

Versions:

$ jq .version ./node_modules/eslint-plugin-astro/package.json 
"1.3.1"
$ jq .version ./node_modules/@astrojs/compiler/package.json 
"2.10.3"

@ota-meshi
Copy link
Owner

This project does not use convertToTSX(), as it does not return an AST and is therefore not useful. This project uses parse().

@alxndrsn
Copy link
Contributor Author

alxndrsn commented Apr 1, 2025

This project does not use convertToTSX(), as it does not return an AST and is therefore not useful. This project uses parse().

Thanks for the clarification.

The AST also looks reasonable to me:

> console.log(JSON.stringify((await require('@astrojs/compiler').parse('<div>\n  <div>\n    <unclosed>\n  </div>\n  <!-- a comment -->\n</div>\n')).ast))
{"type":"root","children":[{"type":"element","name":"div","attributes":[],"children":[{"type":"text","value":"\n  ","position":{"start":{"line":1,"column":6,"offset":5},"end":{"line":2,"column":3,"offset":8}}},{"type":"element","name":"div","attributes":[],"children":[{"type":"text","value":"\n    ","position":{"start":{"line":2,"column":8,"offset":13},"end":{"line":3,"column":5,"offset":18}}},{"type":"element","name":"unclosed","attributes":[],"children":[{"type":"text","value":"\n  ","position":{"start":{"line":3,"column":15,"offset":28},"end":{"line":4,"column":3,"offset":31}}}],"position":{"start":{"line":3,"column":5,"offset":18},"end":{"line":5,"column":5,"offset":42}}},{"type":"text","value":"\n  ","position":{"start":{"line":4,"column":9,"offset":37},"end":{"line":5,"column":3,"offset":40}}},{"type":"comment","value":" a comment ","position":{"start":{"line":5,"column":7,"offset":44},"end":{"line":5,"column":21,"offset":58}}},{"type":"text","value":"\n","position":{"start":{"line":5,"column":21,"offset":58},"end":{"line":6,"column":1,"offset":59}}}],"position":{"start":{"line":2,"column":3,"offset":8},"end":{"line":6,"column":7,"offset":65}}},{"type":"text","value":"\n","position":{"start":{"line":6,"column":7,"offset":65},"end":{"line":7,"column":1,"offset":66}}}],"position":{"start":{"line":1,"column":2,"offset":1}}}]}

Specifically:

> ((await require('@astrojs/compiler').parse('<div>\n  <div>\n    <unclosed>\n  </div>\n  <!-- a comment -->\n</div>\n')).ast).children[0].children[1]
{
  type: 'element',
  name: 'div',
  attributes: [],
  children: [
    { type: 'text', value: '\n    ', position: [Object] },
    {
      type: 'element',
      name: 'unclosed',
      attributes: [],
      children: [Array],
      position: [Object]
    },
    { type: 'text', value: '\n  ', position: [Object] },
    { type: 'comment', value: ' a comment ', position: [Object] },
    { type: 'text', value: '\n', position: [Object] }
  ],
  position: {
    start: { line: 2, column: 3, offset: 8 },
    end: { line: 6, column: 7, offset: 65 }
  }
}

@ota-meshi
Copy link
Owner

ota-meshi commented Apr 1, 2025

The AST also looks reasonable to me:

What does that mean? I think the output AST represents the following part:

...
<div>
  <unclosed>
  <!-- a comment -->
</div>
...

#474 (comment)

(I don't understand English so I may have misread your comment)

I completely misread that. sorry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants