From f77692fefba4d66a0229309a92727859fd4076ef Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:54:11 +0200 Subject: [PATCH] fix(editor): keep // on JSX attribute lines and use {/* */} in MDX Cmd+/ on an attribute line of a multi-line JSX tag wrapped it in {/* */}, which TypeScript rejects. Only lines that start a JSX child (text, the { of a child escape, or the < of a nested element) take {/* */} now, and attribute lines keep //. .mdx files got , which MDX 2 and later fail to compile. They use {/* */} now. Co-authored-by: 3ae3ae <83870598+3ae3ae@users.noreply.github.com> --- .../app/src/file-pane/editor/language.test.ts | 16 ++++++++++++- packages/highlight/src/parsers.ts | 24 +++++++++++++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/app/src/file-pane/editor/language.test.ts b/packages/app/src/file-pane/editor/language.test.ts index 1f1bb09fe6..4c45094c3c 100644 --- a/packages/app/src/file-pane/editor/language.test.ts +++ b/packages/app/src/file-pane/editor/language.test.ts @@ -9,6 +9,7 @@ test.each([ ["source.css", "body {}", "/* body {} */"], ["source.html", "

text

", ""], ["source.md", "text", ""], + ["source.mdx", "text", "{/* text */}"], ["source.ex", "value = 1", "# value = 1"], ["source.cs", "var value = 1;", "// var value = 1;"], ["source.nix", "1", "# 1"], @@ -24,10 +25,18 @@ test.each([ const jsxView = [ "const view = (", - "
", + " go()}", + " >", " text", " {value}", " ", + "{flag}", + "
", + " ", " {items.map(() => {", " const item = 1;", " })}", @@ -40,6 +49,11 @@ test.each([ ["view.tsx", "text", "{/* text */}"], ["view.tsx", "{value}", "{/* {value} */}"], ["view.tsx", "", "{/* */}"], + ["view.tsx", "{flag}", "{/* {flag} */}"], + ["view.tsx", "
", "{/*
*/}"], + ["view.tsx", 'className="x"', '// className="x"'], + ["view.tsx", "onClick={() => go()}", "// onClick={() => go()}"], + ["view.tsx", 'a="1"', '// a="1"'], ["view.tsx", "const item = 1;", "// const item = 1;"], ])("toggles %s line %s as %s", (filename, line, commented) => { const language = getLanguageForFile(filename); diff --git a/packages/highlight/src/parsers.ts b/packages/highlight/src/parsers.ts index 8b7fae7c59..8464978dd0 100644 --- a/packages/highlight/src/parsers.ts +++ b/packages/highlight/src/parsers.ts @@ -22,7 +22,7 @@ import { parser as rustParser } from "@lezer/rust"; import { parser as xmlParser } from "@lezer/xml"; import { parser as yamlParser } from "@lezer/yaml"; import { parser as elixirParser } from "lezer-elixir"; -import type { Parser } from "@lezer/common"; +import type { Parser, SyntaxNode } from "@lezer/common"; import type { LRParser } from "@lezer/lr"; import { csharpLanguage } from "./csharp/language.js"; import { astroParser } from "./astro/parser.js"; @@ -35,13 +35,27 @@ const hashComments = { commentTokens: { line: "#" } }; const cssComments = { commentTokens: { block: { open: "/*", close: "*/" } } }; const markupComments = { commentTokens: { block: { open: "" } } }; +const jsxChildComments = { commentTokens: { block: { open: "{/*", close: "*/}" } } }; + // JSX children are text, so a `//` there renders on the page instead of commenting the line out. +// Attribute lines of a multi-line tag are not children and keep `//`. const jsxComments: Sublanguage = { - // The `{` opening a JSX escape sits on a child line too, so judge it by its escape. - test: (node) => (node.name === "{" && node.parent ? node.parent : node).name.startsWith("JSX"), - facet: defineLanguageFacet({ commentTokens: { block: { open: "{/*", close: "*/}" } } }), + test: isJsxChild, + facet: defineLanguageFacet(jsxChildComments), }; +// A line starts on a child as text, as the `{` of an escape, or as the `<` of a nested element. +function isJsxChild(node: SyntaxNode): boolean { + return childAt(node)?.parent?.name === "JSXElement"; +} + +function childAt(node: SyntaxNode): SyntaxNode | null { + if (node.name === "JSXText") return node; + if (node.name === "{") return node.parent; + if (node.name === "JSXStartTag") return node.parent?.parent ?? null; + return null; +} + function language( parser: LRParser | MarkdownParser, languageData: Parameters[0] = {}, @@ -122,7 +136,7 @@ const languagesByExtension: Record = { exs: language(elixirParser, hashComments), // Markdown md: language(markdownParser, markupComments), - mdx: language(markdownParser, markupComments), + mdx: language(markdownParser, jsxChildComments), }; export function getLanguageForFile(filename: string): Language | null {