steid

@jamesgill /

feat: a .jsx file reads as JavaScript rather than as plain text

two-face's JavaScript grammar registers `js` and `htc` and nothing else, so `.jsx`
came out colourless while `.tsx` was highlighted — TypeScriptReact is its own
grammar and JavaScriptReact is not. JSX is JavaScript with element literals in it
and the JavaScript grammar reads it well enough.

An `ALIASES` table rather than a special case, consulted *after* the syntax set has
had its say: an alias is what to do when nothing claims an extension, never a way
to overrule a grammar that does. One entry, and it should stay near one — a wrong
alias reads worse than no colour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J18ViwAfdswUCMb2DXJZFG
JamesPatrickGill authored 14 hours agoparent00d9c06Browse files610ab384a91b2fd66840bcff0aa636c69ee5d8ef

2 files changed+27 −3

plans/current.md+3 −2View file
@@ -142,8 +142,9 @@ instance, and Steid's own source is pushed to it and browsable there.
142142 - **Diffs, READMEs and markdown code fences are unhighlighted.** The README goes through
143143 `web/markdown.rs`, which writes its own `<pre><code>`; wiring `highlight.rs` into it is
144144 a small follow-up.
145- **`.jsx` is plain**: two-face's JavaScript grammar claims only `js`/`htc`; `tsx` is
146 covered by TypeScriptReact. An alias is one line.
145+- ~~`.jsx` is plain~~ — **aliased onto JavaScript.** `highlight.rs` gained an `ALIASES`
146+ table, consulted only after the syntax set's own answer, so an alias can never
147+ overrule a real grammar. It has one entry and should stay small.
147148 - **`\r\n` files keep the `\r` inside highlighted markup.** Invisible under
148149 `white-space: pre`; noted rather than fixed.
149150 - **The blob header does not name the detected language.** It is only visible as colour.
src/infrastructure/highlight.rs+24 −1View file
@@ -121,6 +121,17 @@ fn syntaxes() -> &'static SyntaxSet {
121121 SYNTAXES.get_or_init(two_face::syntax::extra_newlines)
122122 }
123123
124+/// Extensions no grammar in the set claims, and the grammar to read them with anyway.
125+///
126+/// `two-face`'s JavaScript grammar registers `js` and `htc` and nothing else, so `.jsx`
127+/// rendered as plain text while `.tsx` was highlighted, TypeScriptReact being its own
128+/// grammar. JSX is JavaScript with element literals in it and the JavaScript grammar
129+/// reads it well enough.
130+///
131+/// One entry, deliberately: every alias is a claim that two languages are close enough
132+/// to read as one, and a wrong one is worse than no colour.
133+const ALIASES: &[(&str, &str)] = &[("jsx", "js")];
134+
124135 /// The language, from the file name and then the first line.
125136 ///
126137 /// This is `SyntaxSet::find_syntax_for_file` without the filesystem: that method opens
@@ -135,9 +146,19 @@ fn syntax_for<'a>(
135146 ) -> Option<&'a SyntaxReference> {
136147 let extension = file_name.rsplit_once('.').map(|(_, ext)| ext);
137148
149+ // After the set's own answer, never before it: an alias is what to do when no
150+ // grammar claims the extension, not a way to overrule one that does.
151+ let aliased = extension.and_then(|ext| {
152+ ALIASES
153+ .iter()
154+ .find(|(from, _)| *from == ext)
155+ .map(|(_, to)| *to)
156+ });
157+
138158 let syntax = syntaxes
139159 .find_syntax_by_extension(file_name)
140160 .or_else(|| extension.and_then(|ext| syntaxes.find_syntax_by_extension(ext)))
161+ .or_else(|| aliased.and_then(|to| syntaxes.find_syntax_by_extension(to)))
141162 .or_else(|| syntaxes.find_syntax_by_first_line(text.lines().next().unwrap_or("")))?;
142163
143164 (syntax.name != syntaxes.find_syntax_plain_text().name).then_some(syntax)
@@ -310,7 +331,8 @@ mod tests {
310331
311332 /// The gap `two_face` was added to close, asserted rather than described: TOML,
312333 /// Dockerfile and TypeScript rendered plain until it landed, and the first two are
313 /// in this repository. `.jsx` is still absent — see the handover.
334+ /// in this repository. `.jsx` is here through [`ALIASES`] rather than through a
335+ /// grammar of its own.
314336 #[test]
315337 fn the_languages_this_repository_uses_are_all_highlighted() {
316338 for name in [
@@ -327,6 +349,7 @@ mod tests {
327349 "Makefile",
328350 "app.py",
329351 "index.js",
352+ "App.jsx",
330353 "main.go",
331354 "query.sql",
332355 "main.c",