source: node_modules/refractor/lang/js-templates.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 11.4 KB
Line 
1'use strict'
2
3module.exports = jsTemplates
4jsTemplates.displayName = 'jsTemplates'
5jsTemplates.aliases = []
6function jsTemplates(Prism) {
7 ;(function (Prism) {
8 var templateString = Prism.languages.javascript['template-string'] // see the pattern in prism-javascript.js
9 var templateLiteralPattern = templateString.pattern.source
10 var interpolationObject = templateString.inside['interpolation']
11 var interpolationPunctuationObject =
12 interpolationObject.inside['interpolation-punctuation']
13 var interpolationPattern = interpolationObject.pattern.source
14 /**
15 * Creates a new pattern to match a template string with a special tag.
16 *
17 * This will return `undefined` if there is no grammar with the given language id.
18 *
19 * @param {string} language The language id of the embedded language. E.g. `markdown`.
20 * @param {string} tag The regex pattern to match the tag.
21 * @returns {object | undefined}
22 * @example
23 * createTemplate('css', /\bcss/.source);
24 */
25 function createTemplate(language, tag) {
26 if (!Prism.languages[language]) {
27 return undefined
28 }
29 return {
30 pattern: RegExp('((?:' + tag + ')\\s*)' + templateLiteralPattern),
31 lookbehind: true,
32 greedy: true,
33 inside: {
34 'template-punctuation': {
35 pattern: /^`|`$/,
36 alias: 'string'
37 },
38 'embedded-code': {
39 pattern: /[\s\S]+/,
40 alias: language
41 }
42 }
43 }
44 }
45 Prism.languages.javascript['template-string'] = [
46 // styled-jsx:
47 // css`a { color: #25F; }`
48 // styled-components:
49 // styled.h1`color: red;`
50 createTemplate(
51 'css',
52 /\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/
53 .source
54 ), // html`<p></p>`
55 // div.innerHTML = `<p></p>`
56 createTemplate('html', /\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source), // svg`<path fill="#fff" d="M55.37 ..."/>`
57 createTemplate('svg', /\bsvg/.source), // md`# h1`, markdown`## h2`
58 createTemplate('markdown', /\b(?:markdown|md)/.source), // gql`...`, graphql`...`, graphql.experimental`...`
59 createTemplate(
60 'graphql',
61 /\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source
62 ), // sql`...`
63 createTemplate('sql', /\bsql/.source), // vanilla template string
64 templateString
65 ].filter(Boolean)
66 /**
67 * Returns a specific placeholder literal for the given language.
68 *
69 * @param {number} counter
70 * @param {string} language
71 * @returns {string}
72 */
73 function getPlaceholder(counter, language) {
74 return '___' + language.toUpperCase() + '_' + counter + '___'
75 }
76 /**
77 * Returns the tokens of `Prism.tokenize` but also runs the `before-tokenize` and `after-tokenize` hooks.
78 *
79 * @param {string} code
80 * @param {any} grammar
81 * @param {string} language
82 * @returns {(string|Token)[]}
83 */
84 function tokenizeWithHooks(code, grammar, language) {
85 var env = {
86 code: code,
87 grammar: grammar,
88 language: language
89 }
90 Prism.hooks.run('before-tokenize', env)
91 env.tokens = Prism.tokenize(env.code, env.grammar)
92 Prism.hooks.run('after-tokenize', env)
93 return env.tokens
94 }
95 /**
96 * Returns the token of the given JavaScript interpolation expression.
97 *
98 * @param {string} expression The code of the expression. E.g. `"${42}"`
99 * @returns {Token}
100 */
101 function tokenizeInterpolationExpression(expression) {
102 var tempGrammar = {}
103 tempGrammar['interpolation-punctuation'] = interpolationPunctuationObject
104 /** @type {Array} */
105 var tokens = Prism.tokenize(expression, tempGrammar)
106 if (tokens.length === 3) {
107 /**
108 * The token array will look like this
109 * [
110 * ["interpolation-punctuation", "${"]
111 * "..." // JavaScript expression of the interpolation
112 * ["interpolation-punctuation", "}"]
113 * ]
114 */
115 var args = [1, 1]
116 args.push.apply(
117 args,
118 tokenizeWithHooks(tokens[1], Prism.languages.javascript, 'javascript')
119 )
120 tokens.splice.apply(tokens, args)
121 }
122 return new Prism.Token(
123 'interpolation',
124 tokens,
125 interpolationObject.alias,
126 expression
127 )
128 }
129 /**
130 * Tokenizes the given code with support for JavaScript interpolation expressions mixed in.
131 *
132 * This function has 3 phases:
133 *
134 * 1. Replace all JavaScript interpolation expression with a placeholder.
135 * The placeholder will have the syntax of a identify of the target language.
136 * 2. Tokenize the code with placeholders.
137 * 3. Tokenize the interpolation expressions and re-insert them into the tokenize code.
138 * The insertion only works if a placeholder hasn't been "ripped apart" meaning that the placeholder has been
139 * tokenized as two tokens by the grammar of the embedded language.
140 *
141 * @param {string} code
142 * @param {object} grammar
143 * @param {string} language
144 * @returns {Token}
145 */
146 function tokenizeEmbedded(code, grammar, language) {
147 // 1. First filter out all interpolations
148 // because they might be escaped, we need a lookbehind, so we use Prism
149 /** @type {(Token|string)[]} */
150 var _tokens = Prism.tokenize(code, {
151 interpolation: {
152 pattern: RegExp(interpolationPattern),
153 lookbehind: true
154 }
155 }) // replace all interpolations with a placeholder which is not in the code already
156 var placeholderCounter = 0
157 /** @type {Object<string, string>} */
158 var placeholderMap = {}
159 var embeddedCode = _tokens
160 .map(function (token) {
161 if (typeof token === 'string') {
162 return token
163 } else {
164 var interpolationExpression = token.content
165 var placeholder
166 while (
167 code.indexOf(
168 (placeholder = getPlaceholder(placeholderCounter++, language))
169 ) !== -1
170 ) {
171 /* noop */
172 }
173 placeholderMap[placeholder] = interpolationExpression
174 return placeholder
175 }
176 })
177 .join('') // 2. Tokenize the embedded code
178 var embeddedTokens = tokenizeWithHooks(embeddedCode, grammar, language) // 3. Re-insert the interpolation
179 var placeholders = Object.keys(placeholderMap)
180 placeholderCounter = 0
181 /**
182 *
183 * @param {(Token|string)[]} tokens
184 * @returns {void}
185 */
186 function walkTokens(tokens) {
187 for (var i = 0; i < tokens.length; i++) {
188 if (placeholderCounter >= placeholders.length) {
189 return
190 }
191 var token = tokens[i]
192 if (typeof token === 'string' || typeof token.content === 'string') {
193 var placeholder = placeholders[placeholderCounter]
194 var s =
195 typeof token === 'string'
196 ? token
197 : /** @type {string} */
198 token.content
199 var index = s.indexOf(placeholder)
200 if (index !== -1) {
201 ++placeholderCounter
202 var before = s.substring(0, index)
203 var middle = tokenizeInterpolationExpression(
204 placeholderMap[placeholder]
205 )
206 var after = s.substring(index + placeholder.length)
207 var replacement = []
208 if (before) {
209 replacement.push(before)
210 }
211 replacement.push(middle)
212 if (after) {
213 var afterTokens = [after]
214 walkTokens(afterTokens)
215 replacement.push.apply(replacement, afterTokens)
216 }
217 if (typeof token === 'string') {
218 tokens.splice.apply(tokens, [i, 1].concat(replacement))
219 i += replacement.length - 1
220 } else {
221 token.content = replacement
222 }
223 }
224 } else {
225 var content = token.content
226 if (Array.isArray(content)) {
227 walkTokens(content)
228 } else {
229 walkTokens([content])
230 }
231 }
232 }
233 }
234 walkTokens(embeddedTokens)
235 return new Prism.Token(
236 language,
237 embeddedTokens,
238 'language-' + language,
239 code
240 )
241 }
242 /**
243 * The languages for which JS templating will handle tagged template literals.
244 *
245 * JS templating isn't active for only JavaScript but also related languages like TypeScript, JSX, and TSX.
246 */
247 var supportedLanguages = {
248 javascript: true,
249 js: true,
250 typescript: true,
251 ts: true,
252 jsx: true,
253 tsx: true
254 }
255 Prism.hooks.add('after-tokenize', function (env) {
256 if (!(env.language in supportedLanguages)) {
257 return
258 }
259 /**
260 * Finds and tokenizes all template strings with an embedded languages.
261 *
262 * @param {(Token | string)[]} tokens
263 * @returns {void}
264 */
265 function findTemplateStrings(tokens) {
266 for (var i = 0, l = tokens.length; i < l; i++) {
267 var token = tokens[i]
268 if (typeof token === 'string') {
269 continue
270 }
271 var content = token.content
272 if (!Array.isArray(content)) {
273 if (typeof content !== 'string') {
274 findTemplateStrings([content])
275 }
276 continue
277 }
278 if (token.type === 'template-string') {
279 /**
280 * A JavaScript template-string token will look like this:
281 *
282 * ["template-string", [
283 * ["template-punctuation", "`"],
284 * (
285 * An array of "string" and "interpolation" tokens. This is the simple string case.
286 * or
287 * ["embedded-code", "..."] This is the token containing the embedded code.
288 * It also has an alias which is the language of the embedded code.
289 * ),
290 * ["template-punctuation", "`"]
291 * ]]
292 */
293 var embedded = content[1]
294 if (
295 content.length === 3 &&
296 typeof embedded !== 'string' &&
297 embedded.type === 'embedded-code'
298 ) {
299 // get string content
300 var code = stringContent(embedded)
301 var alias = embedded.alias
302 var language = Array.isArray(alias) ? alias[0] : alias
303 var grammar = Prism.languages[language]
304 if (!grammar) {
305 // the embedded language isn't registered.
306 continue
307 }
308 content[1] = tokenizeEmbedded(code, grammar, language)
309 }
310 } else {
311 findTemplateStrings(content)
312 }
313 }
314 }
315 findTemplateStrings(env.tokens)
316 })
317 /**
318 * Returns the string content of a token or token stream.
319 *
320 * @param {string | Token | (string | Token)[]} value
321 * @returns {string}
322 */
323 function stringContent(value) {
324 if (typeof value === 'string') {
325 return value
326 } else if (Array.isArray(value)) {
327 return value.map(stringContent).join('')
328 } else {
329 return stringContent(value.content)
330 }
331 }
332 })(Prism)
333}
Note: See TracBrowser for help on using the repository browser.