UNPKG

16.6 kBJavaScriptView Raw
1const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';
2const KEYWORDS = [
3 "as", // for exports
4 "in",
5 "of",
6 "if",
7 "for",
8 "while",
9 "finally",
10 "var",
11 "new",
12 "function",
13 "do",
14 "return",
15 "void",
16 "else",
17 "break",
18 "catch",
19 "instanceof",
20 "with",
21 "throw",
22 "case",
23 "default",
24 "try",
25 "switch",
26 "continue",
27 "typeof",
28 "delete",
29 "let",
30 "yield",
31 "const",
32 "class",
33 // JS handles these with a special rule
34 // "get",
35 // "set",
36 "debugger",
37 "async",
38 "await",
39 "static",
40 "import",
41 "from",
42 "export",
43 "extends"
44];
45const LITERALS = [
46 "true",
47 "false",
48 "null",
49 "undefined",
50 "NaN",
51 "Infinity"
52];
53
54// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
55const TYPES = [
56 // Fundamental objects
57 "Object",
58 "Function",
59 "Boolean",
60 "Symbol",
61 // numbers and dates
62 "Math",
63 "Date",
64 "Number",
65 "BigInt",
66 // text
67 "String",
68 "RegExp",
69 // Indexed collections
70 "Array",
71 "Float32Array",
72 "Float64Array",
73 "Int8Array",
74 "Uint8Array",
75 "Uint8ClampedArray",
76 "Int16Array",
77 "Int32Array",
78 "Uint16Array",
79 "Uint32Array",
80 "BigInt64Array",
81 "BigUint64Array",
82 // Keyed collections
83 "Set",
84 "Map",
85 "WeakSet",
86 "WeakMap",
87 // Structured data
88 "ArrayBuffer",
89 "SharedArrayBuffer",
90 "Atomics",
91 "DataView",
92 "JSON",
93 // Control abstraction objects
94 "Promise",
95 "Generator",
96 "GeneratorFunction",
97 "AsyncFunction",
98 // Reflection
99 "Reflect",
100 "Proxy",
101 // Internationalization
102 "Intl",
103 // WebAssembly
104 "WebAssembly"
105];
106
107const ERROR_TYPES = [
108 "Error",
109 "EvalError",
110 "InternalError",
111 "RangeError",
112 "ReferenceError",
113 "SyntaxError",
114 "TypeError",
115 "URIError"
116];
117
118const BUILT_IN_GLOBALS = [
119 "setInterval",
120 "setTimeout",
121 "clearInterval",
122 "clearTimeout",
123
124 "require",
125 "exports",
126
127 "eval",
128 "isFinite",
129 "isNaN",
130 "parseFloat",
131 "parseInt",
132 "decodeURI",
133 "decodeURIComponent",
134 "encodeURI",
135 "encodeURIComponent",
136 "escape",
137 "unescape"
138];
139
140const BUILT_IN_VARIABLES = [
141 "arguments",
142 "this",
143 "super",
144 "console",
145 "window",
146 "document",
147 "localStorage",
148 "module",
149 "global" // Node.js
150];
151
152const BUILT_INS = [].concat(
153 BUILT_IN_GLOBALS,
154 TYPES,
155 ERROR_TYPES
156);
157
158/*
159Language: JavaScript
160Description: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.
161Category: common, scripting, web
162Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript
163*/
164
165/** @type LanguageFn */
166function javascript(hljs) {
167 const regex = hljs.regex;
168 /**
169 * Takes a string like "<Booger" and checks to see
170 * if we can find a matching "</Booger" later in the
171 * content.
172 * @param {RegExpMatchArray} match
173 * @param {{after:number}} param1
174 */
175 const hasClosingTag = (match, { after }) => {
176 const tag = "</" + match[0].slice(1);
177 const pos = match.input.indexOf(tag, after);
178 return pos !== -1;
179 };
180
181 const IDENT_RE$1 = IDENT_RE;
182 const FRAGMENT = {
183 begin: '<>',
184 end: '</>'
185 };
186 // to avoid some special cases inside isTrulyOpeningTag
187 const XML_SELF_CLOSING = /<[A-Za-z0-9\\._:-]+\s*\/>/;
188 const XML_TAG = {
189 begin: /<[A-Za-z0-9\\._:-]+/,
190 end: /\/[A-Za-z0-9\\._:-]+>|\/>/,
191 /**
192 * @param {RegExpMatchArray} match
193 * @param {CallbackResponse} response
194 */
195 isTrulyOpeningTag: (match, response) => {
196 const afterMatchIndex = match[0].length + match.index;
197 const nextChar = match.input[afterMatchIndex];
198 if (
199 // HTML should not include another raw `<` inside a tag
200 // nested type?
201 // `<Array<Array<number>>`, etc.
202 nextChar === "<" ||
203 // the , gives away that this is not HTML
204 // `<T, A extends keyof T, V>`
205 nextChar === ",") {
206 response.ignoreMatch();
207 return;
208 }
209
210 // `<something>`
211 // Quite possibly a tag, lets look for a matching closing tag...
212 if (nextChar === ">") {
213 // if we cannot find a matching closing tag, then we
214 // will ignore it
215 if (!hasClosingTag(match, { after: afterMatchIndex })) {
216 response.ignoreMatch();
217 }
218 }
219
220 // `<blah />` (self-closing)
221 // handled by simpleSelfClosing rule
222
223 // `<From extends string>`
224 // technically this could be HTML, but it smells like a type
225 let m;
226 const afterMatch = match.input.substr(afterMatchIndex);
227 // NOTE: This is ugh, but added specifically for https://github.com/highlightjs/highlight.js/issues/3276
228 if ((m = afterMatch.match(/^\s+extends\s+/))) {
229 if (m.index === 0) {
230 response.ignoreMatch();
231 // eslint-disable-next-line no-useless-return
232 return;
233 }
234 }
235 }
236 };
237 const KEYWORDS$1 = {
238 $pattern: IDENT_RE,
239 keyword: KEYWORDS,
240 literal: LITERALS,
241 built_in: BUILT_INS,
242 "variable.language": BUILT_IN_VARIABLES
243 };
244
245 // https://tc39.es/ecma262/#sec-literals-numeric-literals
246 const decimalDigits = '[0-9](_?[0-9])*';
247 const frac = `\\.(${decimalDigits})`;
248 // DecimalIntegerLiteral, including Annex B NonOctalDecimalIntegerLiteral
249 // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
250 const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`;
251 const NUMBER = {
252 className: 'number',
253 variants: [
254 // DecimalLiteral
255 { begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))` +
256 `[eE][+-]?(${decimalDigits})\\b` },
257 { begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` },
258
259 // DecimalBigIntegerLiteral
260 { begin: `\\b(0|[1-9](_?[0-9])*)n\\b` },
261
262 // NonDecimalIntegerLiteral
263 { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" },
264 { begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" },
265 { begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" },
266
267 // LegacyOctalIntegerLiteral (does not include underscore separators)
268 // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
269 { begin: "\\b0[0-7]+n?\\b" },
270 ],
271 relevance: 0
272 };
273
274 const SUBST = {
275 className: 'subst',
276 begin: '\\$\\{',
277 end: '\\}',
278 keywords: KEYWORDS$1,
279 contains: [] // defined later
280 };
281 const HTML_TEMPLATE = {
282 begin: 'html`',
283 end: '',
284 starts: {
285 end: '`',
286 returnEnd: false,
287 contains: [
288 hljs.BACKSLASH_ESCAPE,
289 SUBST
290 ],
291 subLanguage: 'xml'
292 }
293 };
294 const CSS_TEMPLATE = {
295 begin: 'css`',
296 end: '',
297 starts: {
298 end: '`',
299 returnEnd: false,
300 contains: [
301 hljs.BACKSLASH_ESCAPE,
302 SUBST
303 ],
304 subLanguage: 'css'
305 }
306 };
307 const TEMPLATE_STRING = {
308 className: 'string',
309 begin: '`',
310 end: '`',
311 contains: [
312 hljs.BACKSLASH_ESCAPE,
313 SUBST
314 ]
315 };
316 const JSDOC_COMMENT = hljs.COMMENT(
317 /\/\*\*(?!\/)/,
318 '\\*/',
319 {
320 relevance: 0,
321 contains: [
322 {
323 begin: '(?=@[A-Za-z]+)',
324 relevance: 0,
325 contains: [
326 {
327 className: 'doctag',
328 begin: '@[A-Za-z]+'
329 },
330 {
331 className: 'type',
332 begin: '\\{',
333 end: '\\}',
334 excludeEnd: true,
335 excludeBegin: true,
336 relevance: 0
337 },
338 {
339 className: 'variable',
340 begin: IDENT_RE$1 + '(?=\\s*(-)|$)',
341 endsParent: true,
342 relevance: 0
343 },
344 // eat spaces (not newlines) so we can find
345 // types or variables
346 {
347 begin: /(?=[^\n])\s/,
348 relevance: 0
349 }
350 ]
351 }
352 ]
353 }
354 );
355 const COMMENT = {
356 className: "comment",
357 variants: [
358 JSDOC_COMMENT,
359 hljs.C_BLOCK_COMMENT_MODE,
360 hljs.C_LINE_COMMENT_MODE
361 ]
362 };
363 const SUBST_INTERNALS = [
364 hljs.APOS_STRING_MODE,
365 hljs.QUOTE_STRING_MODE,
366 HTML_TEMPLATE,
367 CSS_TEMPLATE,
368 TEMPLATE_STRING,
369 NUMBER,
370 // This is intentional:
371 // See https://github.com/highlightjs/highlight.js/issues/3288
372 // hljs.REGEXP_MODE
373 ];
374 SUBST.contains = SUBST_INTERNALS
375 .concat({
376 // we need to pair up {} inside our subst to prevent
377 // it from ending too early by matching another }
378 begin: /\{/,
379 end: /\}/,
380 keywords: KEYWORDS$1,
381 contains: [
382 "self"
383 ].concat(SUBST_INTERNALS)
384 });
385 const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains);
386 const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([
387 // eat recursive parens in sub expressions
388 {
389 begin: /\(/,
390 end: /\)/,
391 keywords: KEYWORDS$1,
392 contains: ["self"].concat(SUBST_AND_COMMENTS)
393 }
394 ]);
395 const PARAMS = {
396 className: 'params',
397 begin: /\(/,
398 end: /\)/,
399 excludeBegin: true,
400 excludeEnd: true,
401 keywords: KEYWORDS$1,
402 contains: PARAMS_CONTAINS
403 };
404
405 // ES6 classes
406 const CLASS_OR_EXTENDS = {
407 variants: [
408 // class Car extends vehicle
409 {
410 match: [
411 /class/,
412 /\s+/,
413 IDENT_RE$1,
414 /\s+/,
415 /extends/,
416 /\s+/,
417 regex.concat(IDENT_RE$1, "(", regex.concat(/\./, IDENT_RE$1), ")*")
418 ],
419 scope: {
420 1: "keyword",
421 3: "title.class",
422 5: "keyword",
423 7: "title.class.inherited"
424 }
425 },
426 // class Car
427 {
428 match: [
429 /class/,
430 /\s+/,
431 IDENT_RE$1
432 ],
433 scope: {
434 1: "keyword",
435 3: "title.class"
436 }
437 },
438
439 ]
440 };
441
442 const CLASS_REFERENCE = {
443 relevance: 0,
444 match:
445 regex.either(
446 // Hard coded exceptions
447 /\bJSON/,
448 // Float32Array
449 /\b[A-Z][a-z]+([A-Z][a-z]+|\d)*/,
450 // CSSFactory
451 /\b[A-Z]{2,}([A-Z][a-z]+|\d)+/,
452 // BLAH
453 // this will be flagged as a UPPER_CASE_CONSTANT instead
454 ),
455 className: "title.class",
456 keywords: {
457 _: [
458 // se we still get relevance credit for JS library classes
459 ...TYPES,
460 ...ERROR_TYPES
461 ]
462 }
463 };
464
465 const USE_STRICT = {
466 label: "use_strict",
467 className: 'meta',
468 relevance: 10,
469 begin: /^\s*['"]use (strict|asm)['"]/
470 };
471
472 const FUNCTION_DEFINITION = {
473 variants: [
474 {
475 match: [
476 /function/,
477 /\s+/,
478 IDENT_RE$1,
479 /(?=\s*\()/
480 ]
481 },
482 // anonymous function
483 {
484 match: [
485 /function/,
486 /\s*(?=\()/
487 ]
488 }
489 ],
490 className: {
491 1: "keyword",
492 3: "title.function"
493 },
494 label: "func.def",
495 contains: [ PARAMS ],
496 illegal: /%/
497 };
498
499 const UPPER_CASE_CONSTANT = {
500 relevance: 0,
501 match: /\b[A-Z][A-Z_0-9]+\b/,
502 className: "variable.constant"
503 };
504
505 function noneOf(list) {
506 return regex.concat("(?!", list.join("|"), ")");
507 }
508
509 const FUNCTION_CALL = {
510 match: regex.concat(
511 /\b/,
512 noneOf([
513 ...BUILT_IN_GLOBALS,
514 "super"
515 ]),
516 IDENT_RE$1, regex.lookahead(/\(/)),
517 className: "title.function",
518 relevance: 0
519 };
520
521 const PROPERTY_ACCESS = {
522 begin: regex.concat(/\./, regex.lookahead(
523 regex.concat(IDENT_RE$1, /(?![0-9A-Za-z$_(])/)
524 )),
525 end: IDENT_RE$1,
526 excludeBegin: true,
527 keywords: "prototype",
528 className: "property",
529 relevance: 0
530 };
531
532 const GETTER_OR_SETTER = {
533 match: [
534 /get|set/,
535 /\s+/,
536 IDENT_RE$1,
537 /(?=\()/
538 ],
539 className: {
540 1: "keyword",
541 3: "title.function"
542 },
543 contains: [
544 { // eat to avoid empty params
545 begin: /\(\)/
546 },
547 PARAMS
548 ]
549 };
550
551 const FUNC_LEAD_IN_RE = '(\\(' +
552 '[^()]*(\\(' +
553 '[^()]*(\\(' +
554 '[^()]*' +
555 '\\)[^()]*)*' +
556 '\\)[^()]*)*' +
557 '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>';
558
559 const FUNCTION_VARIABLE = {
560 match: [
561 /const|var|let/, /\s+/,
562 IDENT_RE$1, /\s*/,
563 /=\s*/,
564 regex.lookahead(FUNC_LEAD_IN_RE)
565 ],
566 className: {
567 1: "keyword",
568 3: "title.function"
569 },
570 contains: [
571 PARAMS
572 ]
573 };
574
575 return {
576 name: 'Javascript',
577 aliases: ['js', 'jsx', 'mjs', 'cjs'],
578 keywords: KEYWORDS$1,
579 // this will be extended by TypeScript
580 exports: { PARAMS_CONTAINS, CLASS_REFERENCE },
581 illegal: /#(?![$_A-z])/,
582 contains: [
583 hljs.SHEBANG({
584 label: "shebang",
585 binary: "node",
586 relevance: 5
587 }),
588 USE_STRICT,
589 hljs.APOS_STRING_MODE,
590 hljs.QUOTE_STRING_MODE,
591 HTML_TEMPLATE,
592 CSS_TEMPLATE,
593 TEMPLATE_STRING,
594 COMMENT,
595 NUMBER,
596 CLASS_REFERENCE,
597 {
598 className: 'attr',
599 begin: IDENT_RE$1 + regex.lookahead(':'),
600 relevance: 0
601 },
602 FUNCTION_VARIABLE,
603 { // "value" container
604 begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*',
605 keywords: 'return throw case',
606 relevance: 0,
607 contains: [
608 COMMENT,
609 hljs.REGEXP_MODE,
610 {
611 className: 'function',
612 // we have to count the parens to make sure we actually have the
613 // correct bounding ( ) before the =>. There could be any number of
614 // sub-expressions inside also surrounded by parens.
615 begin: FUNC_LEAD_IN_RE,
616 returnBegin: true,
617 end: '\\s*=>',
618 contains: [
619 {
620 className: 'params',
621 variants: [
622 {
623 begin: hljs.UNDERSCORE_IDENT_RE,
624 relevance: 0
625 },
626 {
627 className: null,
628 begin: /\(\s*\)/,
629 skip: true
630 },
631 {
632 begin: /\(/,
633 end: /\)/,
634 excludeBegin: true,
635 excludeEnd: true,
636 keywords: KEYWORDS$1,
637 contains: PARAMS_CONTAINS
638 }
639 ]
640 }
641 ]
642 },
643 { // could be a comma delimited list of params to a function call
644 begin: /,/,
645 relevance: 0
646 },
647 {
648 match: /\s+/,
649 relevance: 0
650 },
651 { // JSX
652 variants: [
653 { begin: FRAGMENT.begin, end: FRAGMENT.end },
654 { match: XML_SELF_CLOSING },
655 {
656 begin: XML_TAG.begin,
657 // we carefully check the opening tag to see if it truly
658 // is a tag and not a false positive
659 'on:begin': XML_TAG.isTrulyOpeningTag,
660 end: XML_TAG.end
661 }
662 ],
663 subLanguage: 'xml',
664 contains: [
665 {
666 begin: XML_TAG.begin,
667 end: XML_TAG.end,
668 skip: true,
669 contains: ['self']
670 }
671 ]
672 }
673 ],
674 },
675 FUNCTION_DEFINITION,
676 {
677 // prevent this from getting swallowed up by function
678 // since they appear "function like"
679 beginKeywords: "while if switch catch for"
680 },
681 {
682 // we have to count the parens to make sure we actually have the correct
683 // bounding ( ). There could be any number of sub-expressions inside
684 // also surrounded by parens.
685 begin: '\\b(?!function)' + hljs.UNDERSCORE_IDENT_RE +
686 '\\(' + // first parens
687 '[^()]*(\\(' +
688 '[^()]*(\\(' +
689 '[^()]*' +
690 '\\)[^()]*)*' +
691 '\\)[^()]*)*' +
692 '\\)\\s*\\{', // end parens
693 returnBegin:true,
694 label: "func.def",
695 contains: [
696 PARAMS,
697 hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1, className: "title.function" })
698 ]
699 },
700 // catch ... so it won't trigger the property rule below
701 {
702 match: /\.\.\./,
703 relevance: 0
704 },
705 PROPERTY_ACCESS,
706 // hack: prevents detection of keywords in some circumstances
707 // .keyword()
708 // $keyword = x
709 {
710 match: '\\$' + IDENT_RE$1,
711 relevance: 0
712 },
713 {
714 match: [ /\bconstructor(?=\s*\()/ ],
715 className: { 1: "title.function" },
716 contains: [ PARAMS ]
717 },
718 FUNCTION_CALL,
719 UPPER_CASE_CONSTANT,
720 CLASS_OR_EXTENDS,
721 GETTER_OR_SETTER,
722 {
723 match: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
724 }
725 ]
726 };
727}
728
729export { javascript as default };