UNPKG

16.9 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, OutT
449 /\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,
450 // CSSFactory, CSSFactoryT
451 /\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,
452 // FPs, FPsT
453 /\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/,
454 // P
455 // single letters are not highlighted
456 // BLAH
457 // this will be flagged as a UPPER_CASE_CONSTANT instead
458 ),
459 className: "title.class",
460 keywords: {
461 _: [
462 // se we still get relevance credit for JS library classes
463 ...TYPES,
464 ...ERROR_TYPES
465 ]
466 }
467 };
468
469 const USE_STRICT = {
470 label: "use_strict",
471 className: 'meta',
472 relevance: 10,
473 begin: /^\s*['"]use (strict|asm)['"]/
474 };
475
476 const FUNCTION_DEFINITION = {
477 variants: [
478 {
479 match: [
480 /function/,
481 /\s+/,
482 IDENT_RE$1,
483 /(?=\s*\()/
484 ]
485 },
486 // anonymous function
487 {
488 match: [
489 /function/,
490 /\s*(?=\()/
491 ]
492 }
493 ],
494 className: {
495 1: "keyword",
496 3: "title.function"
497 },
498 label: "func.def",
499 contains: [ PARAMS ],
500 illegal: /%/
501 };
502
503 const UPPER_CASE_CONSTANT = {
504 relevance: 0,
505 match: /\b[A-Z][A-Z_0-9]+\b/,
506 className: "variable.constant"
507 };
508
509 function noneOf(list) {
510 return regex.concat("(?!", list.join("|"), ")");
511 }
512
513 const FUNCTION_CALL = {
514 match: regex.concat(
515 /\b/,
516 noneOf([
517 ...BUILT_IN_GLOBALS,
518 "super"
519 ]),
520 IDENT_RE$1, regex.lookahead(/\(/)),
521 className: "title.function",
522 relevance: 0
523 };
524
525 const PROPERTY_ACCESS = {
526 begin: regex.concat(/\./, regex.lookahead(
527 regex.concat(IDENT_RE$1, /(?![0-9A-Za-z$_(])/)
528 )),
529 end: IDENT_RE$1,
530 excludeBegin: true,
531 keywords: "prototype",
532 className: "property",
533 relevance: 0
534 };
535
536 const GETTER_OR_SETTER = {
537 match: [
538 /get|set/,
539 /\s+/,
540 IDENT_RE$1,
541 /(?=\()/
542 ],
543 className: {
544 1: "keyword",
545 3: "title.function"
546 },
547 contains: [
548 { // eat to avoid empty params
549 begin: /\(\)/
550 },
551 PARAMS
552 ]
553 };
554
555 const FUNC_LEAD_IN_RE = '(\\(' +
556 '[^()]*(\\(' +
557 '[^()]*(\\(' +
558 '[^()]*' +
559 '\\)[^()]*)*' +
560 '\\)[^()]*)*' +
561 '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>';
562
563 const FUNCTION_VARIABLE = {
564 match: [
565 /const|var|let/, /\s+/,
566 IDENT_RE$1, /\s*/,
567 /=\s*/,
568 /(async\s*)?/, // async is optional
569 regex.lookahead(FUNC_LEAD_IN_RE)
570 ],
571 keywords: "async",
572 className: {
573 1: "keyword",
574 3: "title.function"
575 },
576 contains: [
577 PARAMS
578 ]
579 };
580
581 return {
582 name: 'Javascript',
583 aliases: ['js', 'jsx', 'mjs', 'cjs'],
584 keywords: KEYWORDS$1,
585 // this will be extended by TypeScript
586 exports: { PARAMS_CONTAINS, CLASS_REFERENCE },
587 illegal: /#(?![$_A-z])/,
588 contains: [
589 hljs.SHEBANG({
590 label: "shebang",
591 binary: "node",
592 relevance: 5
593 }),
594 USE_STRICT,
595 hljs.APOS_STRING_MODE,
596 hljs.QUOTE_STRING_MODE,
597 HTML_TEMPLATE,
598 CSS_TEMPLATE,
599 TEMPLATE_STRING,
600 COMMENT,
601 NUMBER,
602 CLASS_REFERENCE,
603 {
604 className: 'attr',
605 begin: IDENT_RE$1 + regex.lookahead(':'),
606 relevance: 0
607 },
608 FUNCTION_VARIABLE,
609 { // "value" container
610 begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*',
611 keywords: 'return throw case',
612 relevance: 0,
613 contains: [
614 COMMENT,
615 hljs.REGEXP_MODE,
616 {
617 className: 'function',
618 // we have to count the parens to make sure we actually have the
619 // correct bounding ( ) before the =>. There could be any number of
620 // sub-expressions inside also surrounded by parens.
621 begin: FUNC_LEAD_IN_RE,
622 returnBegin: true,
623 end: '\\s*=>',
624 contains: [
625 {
626 className: 'params',
627 variants: [
628 {
629 begin: hljs.UNDERSCORE_IDENT_RE,
630 relevance: 0
631 },
632 {
633 className: null,
634 begin: /\(\s*\)/,
635 skip: true
636 },
637 {
638 begin: /\(/,
639 end: /\)/,
640 excludeBegin: true,
641 excludeEnd: true,
642 keywords: KEYWORDS$1,
643 contains: PARAMS_CONTAINS
644 }
645 ]
646 }
647 ]
648 },
649 { // could be a comma delimited list of params to a function call
650 begin: /,/,
651 relevance: 0
652 },
653 {
654 match: /\s+/,
655 relevance: 0
656 },
657 { // JSX
658 variants: [
659 { begin: FRAGMENT.begin, end: FRAGMENT.end },
660 { match: XML_SELF_CLOSING },
661 {
662 begin: XML_TAG.begin,
663 // we carefully check the opening tag to see if it truly
664 // is a tag and not a false positive
665 'on:begin': XML_TAG.isTrulyOpeningTag,
666 end: XML_TAG.end
667 }
668 ],
669 subLanguage: 'xml',
670 contains: [
671 {
672 begin: XML_TAG.begin,
673 end: XML_TAG.end,
674 skip: true,
675 contains: ['self']
676 }
677 ]
678 }
679 ],
680 },
681 FUNCTION_DEFINITION,
682 {
683 // prevent this from getting swallowed up by function
684 // since they appear "function like"
685 beginKeywords: "while if switch catch for"
686 },
687 {
688 // we have to count the parens to make sure we actually have the correct
689 // bounding ( ). There could be any number of sub-expressions inside
690 // also surrounded by parens.
691 begin: '\\b(?!function)' + hljs.UNDERSCORE_IDENT_RE +
692 '\\(' + // first parens
693 '[^()]*(\\(' +
694 '[^()]*(\\(' +
695 '[^()]*' +
696 '\\)[^()]*)*' +
697 '\\)[^()]*)*' +
698 '\\)\\s*\\{', // end parens
699 returnBegin:true,
700 label: "func.def",
701 contains: [
702 PARAMS,
703 hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1, className: "title.function" })
704 ]
705 },
706 // catch ... so it won't trigger the property rule below
707 {
708 match: /\.\.\./,
709 relevance: 0
710 },
711 PROPERTY_ACCESS,
712 // hack: prevents detection of keywords in some circumstances
713 // .keyword()
714 // $keyword = x
715 {
716 match: '\\$' + IDENT_RE$1,
717 relevance: 0
718 },
719 {
720 match: [ /\bconstructor(?=\s*\()/ ],
721 className: { 1: "title.function" },
722 contains: [ PARAMS ]
723 },
724 FUNCTION_CALL,
725 UPPER_CASE_CONSTANT,
726 CLASS_OR_EXTENDS,
727 GETTER_OR_SETTER,
728 {
729 match: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
730 }
731 ]
732 };
733}
734
735module.exports = javascript;