Skip to content

Commit 18a61f3

Browse files
Fix HTML entity handling in JSX transformer (#1695)
1 parent 6c49566 commit 18a61f3

File tree

5 files changed

+15
-31
lines changed

5 files changed

+15
-31
lines changed

internal/transformers/jsxtransforms/jsx.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -839,23 +839,22 @@ func (tx *JSXTransformer) visitJsxExpression(expression *ast.JsxExpression) *ast
839839
var htmlEntityMatcher = regexp2.MustCompile(`&((#((\d+)|x([\da-fA-F]+)))|(\w+));`, regexp2.ECMAScript)
840840

841841
func htmlEntityReplacer(m regexp2.Match) string {
842-
decimal := m.GroupByNumber(3)
843-
if decimal != nil {
842+
decimal := m.GroupByNumber(4)
843+
if decimal != nil && decimal.Capture.String() != "" {
844844
parsed, err := strconv.ParseInt(decimal.Capture.String(), 10, 32)
845845
if err == nil {
846846
return string(rune(parsed))
847847
}
848848
}
849-
hex := m.GroupByNumber(4)
850-
if hex != nil {
849+
hex := m.GroupByNumber(5)
850+
if hex != nil && hex.Capture.String() != "" {
851851
parsed, err := strconv.ParseInt(hex.Capture.String(), 16, 32)
852852
if err == nil {
853853
return string(rune(parsed))
854854
}
855855
}
856-
word := m.GroupByNumber(5)
857-
if word != nil {
858-
// If this is not a valid entity, then just use `match` (replace it with itself, i.e. don't replace)
856+
word := m.GroupByNumber(6)
857+
if word != nil && word.Capture.String() != "" {
859858
res, ok := entities[word.Capture.String()]
860859
if ok {
861860
return string(res)

testdata/baselines/reference/submodule/conformance/tsxReactEmitEntities.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ declare var React: any;
2525
<div>&#x1F408;&#x1F415;&#128007;&#128017;</div>;
2626

2727
//// [file.js]
28-
React.createElement("div", null, "Dot goes here: &middot; &notAnEntity; ");
29-
React.createElement("div", null, "Be careful of &quot;-ed strings!");
30-
React.createElement("div", null, "{{braces&#x7d;&#x7D;");
28+
React.createElement("div", null, "Dot goes here: \u00B7 &notAnEntity; ");
29+
React.createElement("div", null, "Be careful of \"-ed strings!");
30+
React.createElement("div", null, "{{braces}}");
3131
// Escapes do nothing
3232
React.createElement("div", null, "\\n");
3333
// Also works in string literal attributes
34-
React.createElement("div", { attr: "{&hellip;&#x7D;\\" });
34+
React.createElement("div", { attr: "{\u2026}\\" });
3535
// Does not happen for a string literal that happens to be inside an attribute (and escapes then work)
3636
React.createElement("div", { attr: "&#0123;&hellip;&#x7D;\"" });
3737
// Preserves single quotes
3838
React.createElement("div", { attr: "\"" });
3939
// https://github.com/microsoft/TypeScript/issues/35732
40-
React.createElement("div", null, "&#x1F408;&#x1F415;\uD83D\uDC07\uD83D\uDC11");
40+
React.createElement("div", null, "\uD83D\uDC08\uD83D\uDC15\uD83D\uDC07\uD83D\uDC11");
Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,10 @@
11
--- old.tsxReactEmitEntities.js
22
+++ new.tsxReactEmitEntities.js
3-
@@= skipped -24, +24 lines =@@
4-
<div>&#x1F408;&#x1F415;&#128007;&#128017;</div>;
5-
6-
//// [file.js]
7-
-React.createElement("div", null, "Dot goes here: \u00B7 &notAnEntity; ");
8-
-React.createElement("div", null, "Be careful of \"-ed strings!");
9-
-React.createElement("div", null, "{{braces}}");
10-
+React.createElement("div", null, "Dot goes here: &middot; &notAnEntity; ");
11-
+React.createElement("div", null, "Be careful of &quot;-ed strings!");
12-
+React.createElement("div", null, "{{braces&#x7d;&#x7D;");
13-
// Escapes do nothing
14-
React.createElement("div", null, "\\n");
15-
// Also works in string literal attributes
16-
-React.createElement("div", { attr: "{\u2026}\\" });
17-
+React.createElement("div", { attr: "{&hellip;&#x7D;\\" });
3+
@@= skipped -34, +34 lines =@@
184
// Does not happen for a string literal that happens to be inside an attribute (and escapes then work)
195
React.createElement("div", { attr: "&#0123;&hellip;&#x7D;\"" });
206
// Preserves single quotes
217
-React.createElement("div", { attr: '"' });
228
+React.createElement("div", { attr: "\"" });
239
// https://github.com/microsoft/TypeScript/issues/35732
24-
-React.createElement("div", null, "\uD83D\uDC08\uD83D\uDC15\uD83D\uDC07\uD83D\uDC11");
25-
+React.createElement("div", null, "&#x1F408;&#x1F415;\uD83D\uDC07\uD83D\uDC11");
10+
React.createElement("div", null, "\uD83D\uDC08\uD83D\uDC15\uD83D\uDC07\uD83D\uDC11");

testdata/baselines/reference/submodule/conformance/tsxReactEmitNesting.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,6 @@ let render = (ctrl, model) =>
3838

3939
//// [file.js]
4040
// A simple render function with nesting and control statements
41-
let render = (ctrl, model) => vdom.createElement("section", { class: "todoapp" }, vdom.createElement("header", { class: "header" }, vdom.createElement("h1", null, "todos &lt;x&gt;"), vdom.createElement("input", { class: "new-todo", autofocus: true, autocomplete: "off", placeholder: "What needs to be done?", value: model.newTodo, onKeyup: ctrl.addTodo.bind(ctrl, model) })), vdom.createElement("section", { class: "main", style: { display: (model.todos && model.todos.length) ? "block" : "none" } }, vdom.createElement("input", { class: "toggle-all", type: "checkbox", onChange: ctrl.toggleAll.bind(ctrl) }), vdom.createElement("ul", { class: "todo-list" }, model.filteredTodos.map((todo) => vdom.createElement("li", { class: { todo: true, completed: todo.completed, editing: todo == model.editedTodo } }, vdom.createElement("div", { class: "view" }, (!todo.editable) ?
41+
let render = (ctrl, model) => vdom.createElement("section", { class: "todoapp" }, vdom.createElement("header", { class: "header" }, vdom.createElement("h1", null, "todos <x>"), vdom.createElement("input", { class: "new-todo", autofocus: true, autocomplete: "off", placeholder: "What needs to be done?", value: model.newTodo, onKeyup: ctrl.addTodo.bind(ctrl, model) })), vdom.createElement("section", { class: "main", style: { display: (model.todos && model.todos.length) ? "block" : "none" } }, vdom.createElement("input", { class: "toggle-all", type: "checkbox", onChange: ctrl.toggleAll.bind(ctrl) }), vdom.createElement("ul", { class: "todo-list" }, model.filteredTodos.map((todo) => vdom.createElement("li", { class: { todo: true, completed: todo.completed, editing: todo == model.editedTodo } }, vdom.createElement("div", { class: "view" }, (!todo.editable) ?
4242
vdom.createElement("input", { class: "toggle", type: "checkbox" })
4343
: null, vdom.createElement("label", { onDoubleClick: () => { ctrl.editTodo(todo); } }, todo.title), vdom.createElement("button", { class: "destroy", onClick: ctrl.removeTodo.bind(ctrl, todo) }), vdom.createElement("div", { class: "iconBorder" }, vdom.createElement("div", { class: "icon" }))))))));

testdata/baselines/reference/submodule/conformance/tsxReactEmitNesting.js.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@
1919
- vdom.createElement("button", { class: "destroy", onClick: ctrl.removeTodo.bind(ctrl, todo) }),
2020
- vdom.createElement("div", { class: "iconBorder" },
2121
- vdom.createElement("div", { class: "icon" }))))))));
22-
+let render = (ctrl, model) => vdom.createElement("section", { class: "todoapp" }, vdom.createElement("header", { class: "header" }, vdom.createElement("h1", null, "todos &lt;x&gt;"), vdom.createElement("input", { class: "new-todo", autofocus: true, autocomplete: "off", placeholder: "What needs to be done?", value: model.newTodo, onKeyup: ctrl.addTodo.bind(ctrl, model) })), vdom.createElement("section", { class: "main", style: { display: (model.todos && model.todos.length) ? "block" : "none" } }, vdom.createElement("input", { class: "toggle-all", type: "checkbox", onChange: ctrl.toggleAll.bind(ctrl) }), vdom.createElement("ul", { class: "todo-list" }, model.filteredTodos.map((todo) => vdom.createElement("li", { class: { todo: true, completed: todo.completed, editing: todo == model.editedTodo } }, vdom.createElement("div", { class: "view" }, (!todo.editable) ?
22+
+let render = (ctrl, model) => vdom.createElement("section", { class: "todoapp" }, vdom.createElement("header", { class: "header" }, vdom.createElement("h1", null, "todos <x>"), vdom.createElement("input", { class: "new-todo", autofocus: true, autocomplete: "off", placeholder: "What needs to be done?", value: model.newTodo, onKeyup: ctrl.addTodo.bind(ctrl, model) })), vdom.createElement("section", { class: "main", style: { display: (model.todos && model.todos.length) ? "block" : "none" } }, vdom.createElement("input", { class: "toggle-all", type: "checkbox", onChange: ctrl.toggleAll.bind(ctrl) }), vdom.createElement("ul", { class: "todo-list" }, model.filteredTodos.map((todo) => vdom.createElement("li", { class: { todo: true, completed: todo.completed, editing: todo == model.editedTodo } }, vdom.createElement("div", { class: "view" }, (!todo.editable) ?
2323
+ vdom.createElement("input", { class: "toggle", type: "checkbox" })
2424
+ : null, vdom.createElement("label", { onDoubleClick: () => { ctrl.editTodo(todo); } }, todo.title), vdom.createElement("button", { class: "destroy", onClick: ctrl.removeTodo.bind(ctrl, todo) }), vdom.createElement("div", { class: "iconBorder" }, vdom.createElement("div", { class: "icon" }))))))));

0 commit comments

Comments
 (0)