Commit c51ababba6c344673c910046e4620a08d5bef7e9

Michael Schmidt 2020-08-27T23:51:16

JS Extras: Highlight import and export bindings (#2533)

diff --git a/components/prism-js-extras.js b/components/prism-js-extras.js
index b2a7fca..552ee59 100644
--- a/components/prism-js-extras.js
+++ b/components/prism-js-extras.js
@@ -32,6 +32,33 @@
 		]
 	});
 
+	/**
+	 * Replaces the `<ID>` placeholder in the given pattern with a pattern for general JS identifiers.
+	 *
+	 * @param {string} source
+	 * @param {string} [flags]
+	 * @returns {RegExp}
+	 */
+	function withId(source, flags) {
+		return RegExp(
+			source.replace(/<ID>/g, function () { return /[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*/.source; }),
+			flags);
+	}
+	Prism.languages.insertBefore('javascript', 'keyword', {
+		'imports': {
+			// https://tc39.es/ecma262/#sec-imports
+			pattern: withId(/(\bimport\b\s*)(?:<ID>(?:\s*,\s*(?:\*\s*as\s+<ID>|\{[^{}]*\}))?|\*\s*as\s+<ID>|\{[^{}]*\})(?=\s*\bfrom\b)/.source),
+			lookbehind: true,
+			inside: Prism.languages.javascript
+		},
+		'exports': {
+			// https://tc39.es/ecma262/#sec-exports
+			pattern: withId(/(\bexport\b\s*)(?:\*(?:\s*as\s+<ID>)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),
+			lookbehind: true,
+			inside: Prism.languages.javascript
+		}
+	});
+
 	Prism.languages.javascript['keyword'].unshift(
 		{
 			pattern: /\b(?:as|default|export|from|import)\b/,
@@ -64,7 +91,7 @@
 
 	Prism.languages.insertBefore('javascript', 'punctuation', {
 		'property-access': {
-			pattern: /(\.\s*)#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*/,
+			pattern: withId(/(\.\s*)#?<ID>/.source),
 			lookbehind: true
 		},
 		'maybe-class-name': {
diff --git a/components/prism-js-extras.min.js b/components/prism-js-extras.min.js
index 366f0e2..0505140 100644
--- a/components/prism-js-extras.min.js
+++ b/components/prism-js-extras.min.js
@@ -1 +1 @@
-!function(a){a.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+a.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),a.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+a.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),a.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:(?:Uint|Int)(?:8|16|32)|Uint8Clamped|Float(?:32|64))?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|(?:Weak)?(?:Set|Map)|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),a.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|for|finally|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),a.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),a.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:/(\.\s*)#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*/,lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|location|navigator|performance|(?:local|session)Storage|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var e=["function","function-variable","method","method-variable","property-access"],t=0;t<e.length;t++){var n=e[t],r=a.languages.javascript[n];"RegExp"===a.util.type(r)&&(r=a.languages.javascript[n]={pattern:r});var s=r.inside||{};(r.inside=s)["maybe-class-name"]=/^[A-Z][\s\S]*/}}(Prism);
\ No newline at end of file
+!function(a){function e(a,e){return RegExp(a.replace(/<ID>/g,function(){return"[_$a-zA-Z\\xA0-\\uFFFF][$\\w\\xA0-\\uFFFF]*"}),e)}a.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+a.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),a.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+a.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),a.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:(?:Uint|Int)(?:8|16|32)|Uint8Clamped|Float(?:32|64))?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|(?:Weak)?(?:Set|Map)|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),a.languages.insertBefore("javascript","keyword",{imports:{pattern:e("(\\bimport\\b\\s*)(?:<ID>(?:\\s*,\\s*(?:\\*\\s*as\\s+<ID>|\\{[^{}]*\\}))?|\\*\\s*as\\s+<ID>|\\{[^{}]*\\})(?=\\s*\\bfrom\\b)"),lookbehind:!0,inside:a.languages.javascript},exports:{pattern:e("(\\bexport\\b\\s*)(?:\\*(?:\\s*as\\s+<ID>)?(?=\\s*\\bfrom\\b)|\\{[^{}]*\\})"),lookbehind:!0,inside:a.languages.javascript}}),a.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|for|finally|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),a.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),a.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:e("(\\.\\s*)#?<ID>"),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|location|navigator|performance|(?:local|session)Storage|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var t=["function","function-variable","method","method-variable","property-access"],r=0;r<t.length;r++){var n=t[r],s=a.languages.javascript[n];"RegExp"===a.util.type(s)&&(s=a.languages.javascript[n]={pattern:s});var o=s.inside||{};(s.inside=o)["maybe-class-name"]=/^[A-Z][\s\S]*/}}(Prism);
\ No newline at end of file
diff --git a/tests/languages/javascript!+js-extras/exports_feature.test b/tests/languages/javascript!+js-extras/exports_feature.test
new file mode 100644
index 0000000..67b7647
--- /dev/null
+++ b/tests/languages/javascript!+js-extras/exports_feature.test
@@ -0,0 +1,151 @@
+export * from "mod";
+export * as Foo from "mod";
+export {} from "mod";
+export {x} from "mod";
+export {d} from "mod";
+export {d,} from "mod";
+export {d as Foo, b as Bar} from "mod";
+export {d as Foo, b as Bar,} from "mod";
+export {}
+export {x}
+export {d}
+export {d,}
+export {d as Foo, b as Bar}
+export {d as Foo, b as Bar,}
+
+----------------------------------------------------
+
+[
+	["keyword", "export"],
+	["exports", [
+		["operator", "*"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "export"],
+	["exports", [
+		["operator", "*"],
+		["keyword", "as"],
+		["maybe-class-name", "Foo"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"x",
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"d",
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"d",
+		["punctuation", ","],
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"d ",
+		["keyword", "as"],
+		["maybe-class-name", "Foo"],
+		["punctuation", ","],
+		" b ",
+		["keyword", "as"],
+		["maybe-class-name", "Bar"],
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"d ",
+		["keyword", "as"],
+		["maybe-class-name", "Foo"],
+		["punctuation", ","],
+		" b ",
+		["keyword", "as"],
+		["maybe-class-name", "Bar"],
+		["punctuation", ","],
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		["punctuation", "}"]
+	]],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"x",
+		["punctuation", "}"]
+	]],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"d",
+		["punctuation", "}"]
+	]],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"d",
+		["punctuation", ","],
+		["punctuation", "}"]
+	]],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"d ",
+		["keyword", "as"],
+		["maybe-class-name", "Foo"],
+		["punctuation", ","],
+		" b ",
+		["keyword", "as"],
+		["maybe-class-name", "Bar"],
+		["punctuation", "}"]
+	]],
+	["keyword", "export"],
+	["exports", [
+		["punctuation", "{"],
+		"d ",
+		["keyword", "as"],
+		["maybe-class-name", "Foo"],
+		["punctuation", ","],
+		" b ",
+		["keyword", "as"],
+		["maybe-class-name", "Bar"],
+		["punctuation", ","],
+		["punctuation", "}"]
+	]]
+]
\ No newline at end of file
diff --git a/tests/languages/javascript!+js-extras/imports_feature.test b/tests/languages/javascript!+js-extras/imports_feature.test
new file mode 100644
index 0000000..6e69c6f
--- /dev/null
+++ b/tests/languages/javascript!+js-extras/imports_feature.test
@@ -0,0 +1,124 @@
+import React from 'react';
+import x from "mod";
+import * as Foo from "mod";
+import {} from "mod";
+import {d} from "mod";
+import {d,} from "mod";
+import {d as Foo, b as Bar} from "mod";
+import {d as Foo, b as Bar,} from "mod";
+import Foo, { Bar } from "mod";
+import Foo, * as Bar from "mod";
+
+import "mod";
+
+----------------------------------------------------
+
+[
+	["keyword", "import"],
+	["imports", [
+		["maybe-class-name", "React"]
+	]],
+	["keyword", "from"],
+	["string", "'react'"],
+	["punctuation", ";"],
+	["keyword", "import"],
+	["imports", [
+		"x"
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "import"],
+	["imports", [
+		["operator", "*"],
+		["keyword", "as"],
+		["maybe-class-name", "Foo"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "import"],
+	["imports", [
+		["punctuation", "{"],
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "import"],
+	["imports", [
+		["punctuation", "{"],
+		"d",
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "import"],
+	["imports", [
+		["punctuation", "{"],
+		"d",
+		["punctuation", ","],
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "import"],
+	["imports", [
+		["punctuation", "{"],
+		"d ",
+		["keyword", "as"],
+		["maybe-class-name", "Foo"],
+		["punctuation", ","],
+		" b ",
+		["keyword", "as"],
+		["maybe-class-name", "Bar"],
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "import"],
+	["imports", [
+		["punctuation", "{"],
+		"d ",
+		["keyword", "as"],
+		["maybe-class-name", "Foo"],
+		["punctuation", ","],
+		" b ",
+		["keyword", "as"],
+		["maybe-class-name", "Bar"],
+		["punctuation", ","],
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "import"],
+	["imports", [
+		["maybe-class-name", "Foo"],
+		["punctuation", ","],
+		["punctuation", "{"],
+		["maybe-class-name", "Bar"],
+		["punctuation", "}"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+	["keyword", "import"],
+	["imports", [
+		["maybe-class-name", "Foo"],
+		["punctuation", ","],
+		["operator", "*"],
+		["keyword", "as"],
+		["maybe-class-name", "Bar"]
+	]],
+	["keyword", "from"],
+	["string", "\"mod\""],
+	["punctuation", ";"],
+
+	["keyword", "import"],
+	["string", "\"mod\""],
+	["punctuation", ";"]
+]
\ No newline at end of file