fix(editor): comment JSX children with JSX comments

Cmd+/ on a JSX child line in a .jsx or .tsx file inserted `//`, which
JSX renders as text. JSX nodes now resolve to `{/* */}` block tokens,
while JavaScript inside a JSX escape keeps `//`.
This commit is contained in:
paseo-bot[bot]
2026-10-04 19:42:52 +02:00
parent 5b4c57a301
commit 883c1bde67
2 changed files with 64 additions and 10 deletions
@@ -15,8 +15,51 @@ test.each([
])("toggles the comment and restores the source in %s", (filename, doc, commented) => {
const language = getLanguageForFile(filename);
if (!language) throw new Error(`Missing language for ${filename}`);
let state = EditorState.create({ doc, extensions: [language] });
const target = {
const target = editorTarget(EditorState.create({ doc, extensions: [language] }));
expect(toggleComment(target)).toBe(true);
expect(target.state.doc.toString()).toBe(commented);
expect(toggleComment(target)).toBe(true);
expect(target.state.doc.toString()).toBe(doc);
});
const jsxView = [
"const view = (",
" <div>",
" text",
" {value}",
" <span />",
" {items.map(() => {",
" const item = 1;",
" })}",
" </div>",
");",
].join("\n");
test.each([
["view.jsx", "text", "{/* text */}"],
["view.tsx", "text", "{/* text */}"],
["view.tsx", "{value}", "{/* {value} */}"],
["view.tsx", "<span />", "{/* <span /> */}"],
["view.tsx", "const item = 1;", "// const item = 1;"],
])("toggles %s line %s as %s", (filename, line, commented) => {
const language = getLanguageForFile(filename);
if (!language) throw new Error(`Missing language for ${filename}`);
const target = editorTarget(
EditorState.create({
doc: jsxView,
extensions: [language],
selection: { anchor: jsxView.indexOf(line) },
}),
);
expect(toggleComment(target)).toBe(true);
expect(target.state.doc.toString()).toBe(jsxView.replace(line, commented));
expect(toggleComment(target)).toBe(true);
expect(target.state.doc.toString()).toBe(jsxView);
});
function editorTarget(initial: EditorState) {
let state = initial;
return {
get state() {
return state;
},
@@ -24,11 +67,7 @@ test.each([
state = transaction.state;
},
};
expect(toggleComment(target)).toBe(true);
expect(state.doc.toString()).toBe(commented);
expect(toggleComment(target)).toBe(true);
expect(state.doc.toString()).toBe(doc);
});
}
test("keeps JSON comment-free", () => {
const language = getLanguageForFile("data.json");
+18 -3
View File
@@ -3,6 +3,8 @@ import {
Language,
languageDataProp,
StreamLanguage,
sublanguageProp,
type Sublanguage,
} from "@codemirror/language";
import { dart } from "@codemirror/legacy-modes/mode/clike";
import { swift } from "@codemirror/legacy-modes/mode/swift";
@@ -33,15 +35,28 @@ const hashComments = { commentTokens: { line: "#" } };
const cssComments = { commentTokens: { block: { open: "/*", close: "*/" } } };
const markupComments = { commentTokens: { block: { open: "<!--", close: "-->" } } };
// JSX children are text, so a `//` there renders on the page instead of commenting the line out.
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: "*/}" } } }),
};
function language(
parser: LRParser | MarkdownParser,
languageData: Parameters<typeof defineLanguageFacet>[0] = {},
sublanguages: Sublanguage[] = [],
): Language {
const data = defineLanguageFacet(languageData);
return new Language(
data,
parser.configure({
props: [languageDataProp.add((node) => (node.isTop ? data : undefined))],
props: [
languageDataProp.add((node) => (node.isTop ? data : undefined)),
sublanguageProp.add((node) =>
node.isTop && sublanguages.length ? sublanguages : undefined,
),
],
}),
);
}
@@ -49,9 +64,9 @@ function language(
const languagesByExtension: Record<string, Language> = {
// JavaScript/TypeScript
js: language(jsParser, cStyleComments),
jsx: language(jsParser.configure({ dialect: "jsx" }), cStyleComments),
jsx: language(jsParser.configure({ dialect: "jsx" }), cStyleComments, [jsxComments]),
ts: language(jsParser.configure({ dialect: "ts" }), cStyleComments),
tsx: language(jsParser.configure({ dialect: "ts jsx" }), cStyleComments),
tsx: language(jsParser.configure({ dialect: "ts jsx" }), cStyleComments, [jsxComments]),
mjs: language(jsParser, cStyleComments),
cjs: language(jsParser, cStyleComments),
// C / C++ / Objective-C