Commit 25bdb494c147272167d2b31de5a2af104298b354

Michael Schmidt 2020-11-04T11:51:00

TSX: Temporary fix for the collisions of JSX tags and TS generics (#2596)

diff --git a/components/prism-jsx.js b/components/prism-jsx.js
index 30e6e3f..8c5a868 100644
--- a/components/prism-jsx.js
+++ b/components/prism-jsx.js
@@ -3,10 +3,10 @@
 var javascript = Prism.util.clone(Prism.languages.javascript);
 
 Prism.languages.jsx = Prism.languages.extend('markup', javascript);
-Prism.languages.jsx.tag.pattern= /<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:$-]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s{'">=]+|\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\}))?|\{\s*\.{3}\s*[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\s*\}))*\s*\/?)?>/i;
+Prism.languages.jsx.tag.pattern = /<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:$-]+(?:=(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s{'">=]+|\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\}))?|\{\s*\.{3}\s*[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\s*\}))*\s*\/?)?>/i;
 
 Prism.languages.jsx.tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/i;
-Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">]+)/i;
+Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s'">]+)/i;
 Prism.languages.jsx.tag.inside['tag'].inside['class-name'] = /^[A-Z]\w*(?:\.[A-Z]\w*)*$/;
 
 Prism.languages.insertBefore('inside', 'attr-name', {
diff --git a/components/prism-jsx.min.js b/components/prism-jsx.min.js
index 8139021..a5a3566 100644
--- a/components/prism-jsx.min.js
+++ b/components/prism-jsx.min.js
@@ -1 +1 @@
-!function(i){var t=i.util.clone(i.languages.javascript);i.languages.jsx=i.languages.extend("markup",t),i.languages.jsx.tag.pattern=/<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:$-]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s{'">=]+|\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\}))?|\{\s*\.{3}\s*[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\s*\}))*\s*\/?)?>/i,i.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/i,i.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">]+)/i,i.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,i.languages.insertBefore("inside","attr-name",{spread:{pattern:/\{\s*\.{3}\s*[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\s*\}/,inside:{punctuation:/\.{3}|[{}.]/,"attr-value":/\w+/}}},i.languages.jsx.tag),i.languages.insertBefore("inside","attr-value",{script:{pattern:/=(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\})/i,inside:{"script-punctuation":{pattern:/^=(?={)/,alias:"punctuation"},rest:i.languages.jsx},alias:"language-javascript"}},i.languages.jsx.tag);var o=function(t){return t?"string"==typeof t?t:"string"==typeof t.content?t.content:t.content.map(o).join(""):""},p=function(t){for(var n=[],e=0;e<t.length;e++){var a=t[e],s=!1;if("string"!=typeof a&&("tag"===a.type&&a.content[0]&&"tag"===a.content[0].type?"</"===a.content[0].content[0].content?0<n.length&&n[n.length-1].tagName===o(a.content[0].content[1])&&n.pop():"/>"===a.content[a.content.length-1].content||n.push({tagName:o(a.content[0].content[1]),openedBraces:0}):0<n.length&&"punctuation"===a.type&&"{"===a.content?n[n.length-1].openedBraces++:0<n.length&&0<n[n.length-1].openedBraces&&"punctuation"===a.type&&"}"===a.content?n[n.length-1].openedBraces--:s=!0),(s||"string"==typeof a)&&0<n.length&&0===n[n.length-1].openedBraces){var g=o(a);e<t.length-1&&("string"==typeof t[e+1]||"plain-text"===t[e+1].type)&&(g+=o(t[e+1]),t.splice(e+1,1)),0<e&&("string"==typeof t[e-1]||"plain-text"===t[e-1].type)&&(g=o(t[e-1])+g,t.splice(e-1,1),e--),t[e]=new i.Token("plain-text",g,null,g)}a.content&&"string"!=typeof a.content&&p(a.content)}};i.hooks.add("after-tokenize",function(t){"jsx"!==t.language&&"tsx"!==t.language||p(t.tokens)})}(Prism);
\ No newline at end of file
+!function(i){var t=i.util.clone(i.languages.javascript);i.languages.jsx=i.languages.extend("markup",t),i.languages.jsx.tag.pattern=/<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:$-]+(?:=(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s{'">=]+|\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\}))?|\{\s*\.{3}\s*[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\s*\}))*\s*\/?)?>/i,i.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/i,i.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[^]|[^\\"])*"|'(?:\\[^]|[^\\'])*'|[^\s'">]+)/i,i.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,i.languages.insertBefore("inside","attr-name",{spread:{pattern:/\{\s*\.{3}\s*[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\s*\}/,inside:{punctuation:/\.{3}|[{}.]/,"attr-value":/\w+/}}},i.languages.jsx.tag),i.languages.insertBefore("inside","attr-value",{script:{pattern:/=(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])+\})/i,inside:{"script-punctuation":{pattern:/^=(?={)/,alias:"punctuation"},rest:i.languages.jsx},alias:"language-javascript"}},i.languages.jsx.tag);var o=function(t){return t?"string"==typeof t?t:"string"==typeof t.content?t.content:t.content.map(o).join(""):""},p=function(t){for(var n=[],e=0;e<t.length;e++){var a=t[e],s=!1;if("string"!=typeof a&&("tag"===a.type&&a.content[0]&&"tag"===a.content[0].type?"</"===a.content[0].content[0].content?0<n.length&&n[n.length-1].tagName===o(a.content[0].content[1])&&n.pop():"/>"===a.content[a.content.length-1].content||n.push({tagName:o(a.content[0].content[1]),openedBraces:0}):0<n.length&&"punctuation"===a.type&&"{"===a.content?n[n.length-1].openedBraces++:0<n.length&&0<n[n.length-1].openedBraces&&"punctuation"===a.type&&"}"===a.content?n[n.length-1].openedBraces--:s=!0),(s||"string"==typeof a)&&0<n.length&&0===n[n.length-1].openedBraces){var g=o(a);e<t.length-1&&("string"==typeof t[e+1]||"plain-text"===t[e+1].type)&&(g+=o(t[e+1]),t.splice(e+1,1)),0<e&&("string"==typeof t[e-1]||"plain-text"===t[e-1].type)&&(g=o(t[e-1])+g,t.splice(e-1,1),e--),t[e]=new i.Token("plain-text",g,null,g)}a.content&&"string"!=typeof a.content&&p(a.content)}};i.hooks.add("after-tokenize",function(t){"jsx"!==t.language&&"tsx"!==t.language||p(t.tokens)})}(Prism);
\ No newline at end of file
diff --git a/components/prism-tsx.js b/components/prism-tsx.js
index d566701..badc7a8 100644
--- a/components/prism-tsx.js
+++ b/components/prism-tsx.js
@@ -1,2 +1,11 @@
-var typescript = Prism.util.clone(Prism.languages.typescript);
-Prism.languages.tsx = Prism.languages.extend('jsx', typescript);
\ No newline at end of file
+(function (Prism) {
+	var typescript = Prism.util.clone(Prism.languages.typescript);
+	Prism.languages.tsx = Prism.languages.extend('jsx', typescript);
+
+	// This will prevent collisions between TSX tags and TS generic types.
+	// Idea by https://github.com/karlhorky
+	// Discussion: https://github.com/PrismJS/prism/issues/2594#issuecomment-710666928
+	var tag = Prism.languages.tsx.tag;
+	tag.pattern = RegExp(/(^|[^\w$]|(?=<\/))/.source + '(?:' + tag.pattern.source + ')', tag.pattern.flags);
+	tag.lookbehind = true;
+}(Prism));
diff --git a/components/prism-tsx.min.js b/components/prism-tsx.min.js
index deb84d3..a20ba38 100644
--- a/components/prism-tsx.min.js
+++ b/components/prism-tsx.min.js
@@ -1 +1 @@
-var typescript=Prism.util.clone(Prism.languages.typescript);Prism.languages.tsx=Prism.languages.extend("jsx",typescript);
\ No newline at end of file
+!function(a){var e=a.util.clone(a.languages.typescript);a.languages.tsx=a.languages.extend("jsx",e);var t=a.languages.tsx.tag;t.pattern=RegExp("(^|[^\\w$]|(?=</))(?:"+t.pattern.source+")",t.pattern.flags),t.lookbehind=!0}(Prism);
\ No newline at end of file
diff --git a/tests/languages/tsx/issue2594.test b/tests/languages/tsx/issue2594.test
new file mode 100644
index 0000000..3bc8e00
--- /dev/null
+++ b/tests/languages/tsx/issue2594.test
@@ -0,0 +1,252 @@
+function Add1(a, b) { return <div>a + b</div> }
+
+type Bar = Foo<string>;
+
+function Add2(a, b) { return <div>a + b</div> }
+
+function handleSubmit(event: FormEvent<HTMLFormElement>) {
+  event.preventDefault();
+}
+
+function handleChange(event: ChangeEvent<HTMLInputElement>) {
+  console.log(event.target.value);
+}
+
+function handleClick(event: MouseEvent) {
+  console.log(event.button);
+}
+
+export default function Form() {
+  return (
+    <form onSubmit={handleSubmit}>
+      <input onChange={handleChange} placeholder="Name" />
+      <button onClick={handleClick}></button>
+    </form>
+  );
+}
+
+----------------------------------------------------
+
+[
+	["keyword", "function"],
+	["function", "Add1"],
+	["punctuation", "("],
+	["parameter", [
+		"a",
+		["punctuation", ","],
+		" b"
+	]],
+	["punctuation", ")"],
+	["punctuation", "{"],
+	["keyword", "return"],
+	["tag", [
+		["tag", [
+			["punctuation", "<"],
+			"div"
+		]],
+		["punctuation", ">"]
+	]],
+	["plain-text", "a + b"],
+	["tag", [
+		["tag", [
+			["punctuation", "</"],
+			"div"
+		]],
+		["punctuation", ">"]
+	]],
+	["punctuation", "}"],
+
+	["keyword", "type"],
+	["class-name", [
+		"Bar"
+	]],
+	["operator", "="],
+	" Foo",
+	["operator", "<"],
+	["builtin", "string"],
+	["operator", ">"],
+	["punctuation", ";"],
+
+	["keyword", "function"],
+	["function", "Add2"],
+	["punctuation", "("],
+	["parameter", [
+		"a",
+		["punctuation", ","],
+		" b"
+	]],
+	["punctuation", ")"],
+	["punctuation", "{"],
+	["keyword", "return"],
+	["tag", [
+		["tag", [
+			["punctuation", "<"],
+			"div"
+		]],
+		["punctuation", ">"]
+	]],
+	["plain-text", "a + b"],
+	["tag", [
+		["tag", [
+			["punctuation", "</"],
+			"div"
+		]],
+		["punctuation", ">"]
+	]],
+	["punctuation", "}"],
+
+	["keyword", "function"],
+	["function", "handleSubmit"],
+	["punctuation", "("],
+	["parameter", [
+		"event",
+		["operator", ":"],
+		" FormEvent",
+		["operator", "<"],
+		"HTMLFormElement",
+		["operator", ">"]
+	]],
+	["punctuation", ")"],
+	["punctuation", "{"],
+	"\r\n  event",
+	["punctuation", "."],
+	["function", "preventDefault"],
+	["punctuation", "("],
+	["punctuation", ")"],
+	["punctuation", ";"],
+	["punctuation", "}"],
+
+	["keyword", "function"],
+	["function", "handleChange"],
+	["punctuation", "("],
+	["parameter", [
+		"event",
+		["operator", ":"],
+		" ChangeEvent",
+		["operator", "<"],
+		"HTMLInputElement",
+		["operator", ">"]
+	]],
+	["punctuation", ")"],
+	["punctuation", "{"],
+	["builtin", "console"],
+	["punctuation", "."],
+	["function", "log"],
+	["punctuation", "("],
+	"event",
+	["punctuation", "."],
+	"target",
+	["punctuation", "."],
+	"value",
+	["punctuation", ")"],
+	["punctuation", ";"],
+	["punctuation", "}"],
+
+	["keyword", "function"],
+	["function", "handleClick"],
+	["punctuation", "("],
+	["parameter", [
+		"event",
+		["operator", ":"],
+		" MouseEvent"
+	]],
+	["punctuation", ")"],
+	["punctuation", "{"],
+	["builtin", "console"],
+	["punctuation", "."],
+	["function", "log"],
+	["punctuation", "("],
+	"event",
+	["punctuation", "."],
+	"button",
+	["punctuation", ")"],
+	["punctuation", ";"],
+	["punctuation", "}"],
+
+	["keyword", "export"],
+	["keyword", "default"],
+	["keyword", "function"],
+	["function", "Form"],
+	["punctuation", "("],
+	["punctuation", ")"],
+	["punctuation", "{"],
+	["keyword", "return"],
+	["punctuation", "("],
+	["tag", [
+		["tag", [
+			["punctuation", "<"],
+			"form"
+		]],
+		["attr-name", [
+			"onSubmit"
+		]],
+		["script", [
+			["script-punctuation", "="],
+			["punctuation", "{"],
+			"handleSubmit",
+			["punctuation", "}"]
+		]],
+		["punctuation", ">"]
+	]],
+	["plain-text", "\r\n      "],
+	["tag", [
+		["tag", [
+			["punctuation", "<"],
+			"input"
+		]],
+		["attr-name", [
+			"onChange"
+		]],
+		["script", [
+			["script-punctuation", "="],
+			["punctuation", "{"],
+			"handleChange",
+			["punctuation", "}"]
+		]],
+		["attr-name", [
+			"placeholder"
+		]],
+		["attr-value", [
+			["punctuation", "="],
+			["punctuation", "\""],
+			"Name",
+			["punctuation", "\""]
+		]],
+		["punctuation", "/>"]
+	]],
+	["plain-text", "\r\n      "],
+	["tag", [
+		["tag", [
+			["punctuation", "<"],
+			"button"
+		]],
+		["attr-name", [
+			"onClick"
+		]],
+		["script", [
+			["script-punctuation", "="],
+			["punctuation", "{"],
+			"handleClick",
+			["punctuation", "}"]
+		]],
+		["punctuation", ">"]
+	]],
+	["tag", [
+		["tag", [
+			["punctuation", "</"],
+			"button"
+		]],
+		["punctuation", ">"]
+	]],
+	["plain-text", "\r\n    "],
+	["tag", [
+		["tag", [
+			["punctuation", "</"],
+			"form"
+		]],
+		["punctuation", ">"]
+	]],
+	["punctuation", ")"],
+	["punctuation", ";"],
+	["punctuation", "}"]
+]
\ No newline at end of file