605 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			605 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';
 | |
| const KEYWORDS = [
 | |
|   "as", // for exports
 | |
|   "in",
 | |
|   "of",
 | |
|   "if",
 | |
|   "for",
 | |
|   "while",
 | |
|   "finally",
 | |
|   "var",
 | |
|   "new",
 | |
|   "function",
 | |
|   "do",
 | |
|   "return",
 | |
|   "void",
 | |
|   "else",
 | |
|   "break",
 | |
|   "catch",
 | |
|   "instanceof",
 | |
|   "with",
 | |
|   "throw",
 | |
|   "case",
 | |
|   "default",
 | |
|   "try",
 | |
|   "switch",
 | |
|   "continue",
 | |
|   "typeof",
 | |
|   "delete",
 | |
|   "let",
 | |
|   "yield",
 | |
|   "const",
 | |
|   "class",
 | |
|   // JS handles these with a special rule
 | |
|   // "get",
 | |
|   // "set",
 | |
|   "debugger",
 | |
|   "async",
 | |
|   "await",
 | |
|   "static",
 | |
|   "import",
 | |
|   "from",
 | |
|   "export",
 | |
|   "extends"
 | |
| ];
 | |
| const LITERALS = [
 | |
|   "true",
 | |
|   "false",
 | |
|   "null",
 | |
|   "undefined",
 | |
|   "NaN",
 | |
|   "Infinity"
 | |
| ];
 | |
| 
 | |
| const TYPES = [
 | |
|   "Intl",
 | |
|   "DataView",
 | |
|   "Number",
 | |
|   "Math",
 | |
|   "Date",
 | |
|   "String",
 | |
|   "RegExp",
 | |
|   "Object",
 | |
|   "Function",
 | |
|   "Boolean",
 | |
|   "Error",
 | |
|   "Symbol",
 | |
|   "Set",
 | |
|   "Map",
 | |
|   "WeakSet",
 | |
|   "WeakMap",
 | |
|   "Proxy",
 | |
|   "Reflect",
 | |
|   "JSON",
 | |
|   "Promise",
 | |
|   "Float64Array",
 | |
|   "Int16Array",
 | |
|   "Int32Array",
 | |
|   "Int8Array",
 | |
|   "Uint16Array",
 | |
|   "Uint32Array",
 | |
|   "Float32Array",
 | |
|   "Array",
 | |
|   "Uint8Array",
 | |
|   "Uint8ClampedArray",
 | |
|   "ArrayBuffer",
 | |
|   "BigInt64Array",
 | |
|   "BigUint64Array",
 | |
|   "BigInt"
 | |
| ];
 | |
| 
 | |
| const ERROR_TYPES = [
 | |
|   "EvalError",
 | |
|   "InternalError",
 | |
|   "RangeError",
 | |
|   "ReferenceError",
 | |
|   "SyntaxError",
 | |
|   "TypeError",
 | |
|   "URIError"
 | |
| ];
 | |
| 
 | |
| const BUILT_IN_GLOBALS = [
 | |
|   "setInterval",
 | |
|   "setTimeout",
 | |
|   "clearInterval",
 | |
|   "clearTimeout",
 | |
| 
 | |
|   "require",
 | |
|   "exports",
 | |
| 
 | |
|   "eval",
 | |
|   "isFinite",
 | |
|   "isNaN",
 | |
|   "parseFloat",
 | |
|   "parseInt",
 | |
|   "decodeURI",
 | |
|   "decodeURIComponent",
 | |
|   "encodeURI",
 | |
|   "encodeURIComponent",
 | |
|   "escape",
 | |
|   "unescape"
 | |
| ];
 | |
| 
 | |
| const BUILT_IN_VARIABLES = [
 | |
|   "arguments",
 | |
|   "this",
 | |
|   "super",
 | |
|   "console",
 | |
|   "window",
 | |
|   "document",
 | |
|   "localStorage",
 | |
|   "module",
 | |
|   "global" // Node.js
 | |
| ];
 | |
| 
 | |
| const BUILT_INS = [].concat(
 | |
|   BUILT_IN_GLOBALS,
 | |
|   BUILT_IN_VARIABLES,
 | |
|   TYPES,
 | |
|   ERROR_TYPES
 | |
| );
 | |
| 
 | |
| /**
 | |
|  * @param {string} value
 | |
|  * @returns {RegExp}
 | |
|  * */
 | |
| 
 | |
| /**
 | |
|  * @param {RegExp | string } re
 | |
|  * @returns {string}
 | |
|  */
 | |
| function source(re) {
 | |
|   if (!re) return null;
 | |
|   if (typeof re === "string") return re;
 | |
| 
 | |
|   return re.source;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @param {RegExp | string } re
 | |
|  * @returns {string}
 | |
|  */
 | |
| function lookahead(re) {
 | |
|   return concat('(?=', re, ')');
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @param {...(RegExp | string) } args
 | |
|  * @returns {string}
 | |
|  */
 | |
| function concat(...args) {
 | |
|   const joined = args.map((x) => source(x)).join("");
 | |
|   return joined;
 | |
| }
 | |
| 
 | |
| /*
 | |
| Language: JavaScript
 | |
| Description: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.
 | |
| Category: common, scripting
 | |
| Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript
 | |
| */
 | |
| 
 | |
| /** @type LanguageFn */
 | |
| function javascript(hljs) {
 | |
|   /**
 | |
|    * Takes a string like "<Booger" and checks to see
 | |
|    * if we can find a matching "</Booger" later in the
 | |
|    * content.
 | |
|    * @param {RegExpMatchArray} match
 | |
|    * @param {{after:number}} param1
 | |
|    */
 | |
|   const hasClosingTag = (match, { after }) => {
 | |
|     const tag = "</" + match[0].slice(1);
 | |
|     const pos = match.input.indexOf(tag, after);
 | |
|     return pos !== -1;
 | |
|   };
 | |
| 
 | |
|   const IDENT_RE$1 = IDENT_RE;
 | |
|   const FRAGMENT = {
 | |
|     begin: '<>',
 | |
|     end: '</>'
 | |
|   };
 | |
|   const XML_TAG = {
 | |
|     begin: /<[A-Za-z0-9\\._:-]+/,
 | |
|     end: /\/[A-Za-z0-9\\._:-]+>|\/>/,
 | |
|     /**
 | |
|      * @param {RegExpMatchArray} match
 | |
|      * @param {CallbackResponse} response
 | |
|      */
 | |
|     isTrulyOpeningTag: (match, response) => {
 | |
|       const afterMatchIndex = match[0].length + match.index;
 | |
|       const nextChar = match.input[afterMatchIndex];
 | |
|       // nested type?
 | |
|       // HTML should not include another raw `<` inside a tag
 | |
|       // But a type might: `<Array<Array<number>>`, etc.
 | |
|       if (nextChar === "<") {
 | |
|         response.ignoreMatch();
 | |
|         return;
 | |
|       }
 | |
|       // <something>
 | |
|       // This is now either a tag or a type.
 | |
|       if (nextChar === ">") {
 | |
|         // if we cannot find a matching closing tag, then we
 | |
|         // will ignore it
 | |
|         if (!hasClosingTag(match, { after: afterMatchIndex })) {
 | |
|           response.ignoreMatch();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   };
 | |
|   const KEYWORDS$1 = {
 | |
|     $pattern: IDENT_RE,
 | |
|     keyword: KEYWORDS,
 | |
|     literal: LITERALS,
 | |
|     built_in: BUILT_INS
 | |
|   };
 | |
| 
 | |
|   // https://tc39.es/ecma262/#sec-literals-numeric-literals
 | |
|   const decimalDigits = '[0-9](_?[0-9])*';
 | |
|   const frac = `\\.(${decimalDigits})`;
 | |
|   // DecimalIntegerLiteral, including Annex B NonOctalDecimalIntegerLiteral
 | |
|   // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
 | |
|   const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`;
 | |
|   const NUMBER = {
 | |
|     className: 'number',
 | |
|     variants: [
 | |
|       // DecimalLiteral
 | |
|       { begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))` +
 | |
|         `[eE][+-]?(${decimalDigits})\\b` },
 | |
|       { begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` },
 | |
| 
 | |
|       // DecimalBigIntegerLiteral
 | |
|       { begin: `\\b(0|[1-9](_?[0-9])*)n\\b` },
 | |
| 
 | |
|       // NonDecimalIntegerLiteral
 | |
|       { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" },
 | |
|       { begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" },
 | |
|       { begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" },
 | |
| 
 | |
|       // LegacyOctalIntegerLiteral (does not include underscore separators)
 | |
|       // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
 | |
|       { begin: "\\b0[0-7]+n?\\b" },
 | |
|     ],
 | |
|     relevance: 0
 | |
|   };
 | |
| 
 | |
|   const SUBST = {
 | |
|     className: 'subst',
 | |
|     begin: '\\$\\{',
 | |
|     end: '\\}',
 | |
|     keywords: KEYWORDS$1,
 | |
|     contains: [] // defined later
 | |
|   };
 | |
|   const HTML_TEMPLATE = {
 | |
|     begin: 'html`',
 | |
|     end: '',
 | |
|     starts: {
 | |
|       end: '`',
 | |
|       returnEnd: false,
 | |
|       contains: [
 | |
|         hljs.BACKSLASH_ESCAPE,
 | |
|         SUBST
 | |
|       ],
 | |
|       subLanguage: 'xml'
 | |
|     }
 | |
|   };
 | |
|   const CSS_TEMPLATE = {
 | |
|     begin: 'css`',
 | |
|     end: '',
 | |
|     starts: {
 | |
|       end: '`',
 | |
|       returnEnd: false,
 | |
|       contains: [
 | |
|         hljs.BACKSLASH_ESCAPE,
 | |
|         SUBST
 | |
|       ],
 | |
|       subLanguage: 'css'
 | |
|     }
 | |
|   };
 | |
|   const TEMPLATE_STRING = {
 | |
|     className: 'string',
 | |
|     begin: '`',
 | |
|     end: '`',
 | |
|     contains: [
 | |
|       hljs.BACKSLASH_ESCAPE,
 | |
|       SUBST
 | |
|     ]
 | |
|   };
 | |
|   const JSDOC_COMMENT = hljs.COMMENT(
 | |
|     /\/\*\*(?!\/)/,
 | |
|     '\\*/',
 | |
|     {
 | |
|       relevance: 0,
 | |
|       contains: [
 | |
|         {
 | |
|           className: 'doctag',
 | |
|           begin: '@[A-Za-z]+',
 | |
|           contains: [
 | |
|             {
 | |
|               className: 'type',
 | |
|               begin: '\\{',
 | |
|               end: '\\}',
 | |
|               relevance: 0
 | |
|             },
 | |
|             {
 | |
|               className: 'variable',
 | |
|               begin: IDENT_RE$1 + '(?=\\s*(-)|$)',
 | |
|               endsParent: true,
 | |
|               relevance: 0
 | |
|             },
 | |
|             // eat spaces (not newlines) so we can find
 | |
|             // types or variables
 | |
|             {
 | |
|               begin: /(?=[^\n])\s/,
 | |
|               relevance: 0
 | |
|             }
 | |
|           ]
 | |
|         }
 | |
|       ]
 | |
|     }
 | |
|   );
 | |
|   const COMMENT = {
 | |
|     className: "comment",
 | |
|     variants: [
 | |
|       JSDOC_COMMENT,
 | |
|       hljs.C_BLOCK_COMMENT_MODE,
 | |
|       hljs.C_LINE_COMMENT_MODE
 | |
|     ]
 | |
|   };
 | |
|   const SUBST_INTERNALS = [
 | |
|     hljs.APOS_STRING_MODE,
 | |
|     hljs.QUOTE_STRING_MODE,
 | |
|     HTML_TEMPLATE,
 | |
|     CSS_TEMPLATE,
 | |
|     TEMPLATE_STRING,
 | |
|     NUMBER,
 | |
|     hljs.REGEXP_MODE
 | |
|   ];
 | |
|   SUBST.contains = SUBST_INTERNALS
 | |
|     .concat({
 | |
|       // we need to pair up {} inside our subst to prevent
 | |
|       // it from ending too early by matching another }
 | |
|       begin: /\{/,
 | |
|       end: /\}/,
 | |
|       keywords: KEYWORDS$1,
 | |
|       contains: [
 | |
|         "self"
 | |
|       ].concat(SUBST_INTERNALS)
 | |
|     });
 | |
|   const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains);
 | |
|   const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([
 | |
|     // eat recursive parens in sub expressions
 | |
|     {
 | |
|       begin: /\(/,
 | |
|       end: /\)/,
 | |
|       keywords: KEYWORDS$1,
 | |
|       contains: ["self"].concat(SUBST_AND_COMMENTS)
 | |
|     }
 | |
|   ]);
 | |
|   const PARAMS = {
 | |
|     className: 'params',
 | |
|     begin: /\(/,
 | |
|     end: /\)/,
 | |
|     excludeBegin: true,
 | |
|     excludeEnd: true,
 | |
|     keywords: KEYWORDS$1,
 | |
|     contains: PARAMS_CONTAINS
 | |
|   };
 | |
| 
 | |
|   return {
 | |
|     name: 'Javascript',
 | |
|     aliases: ['js', 'jsx', 'mjs', 'cjs'],
 | |
|     keywords: KEYWORDS$1,
 | |
|     // this will be extended by TypeScript
 | |
|     exports: { PARAMS_CONTAINS },
 | |
|     illegal: /#(?![$_A-z])/,
 | |
|     contains: [
 | |
|       hljs.SHEBANG({
 | |
|         label: "shebang",
 | |
|         binary: "node",
 | |
|         relevance: 5
 | |
|       }),
 | |
|       {
 | |
|         label: "use_strict",
 | |
|         className: 'meta',
 | |
|         relevance: 10,
 | |
|         begin: /^\s*['"]use (strict|asm)['"]/
 | |
|       },
 | |
|       hljs.APOS_STRING_MODE,
 | |
|       hljs.QUOTE_STRING_MODE,
 | |
|       HTML_TEMPLATE,
 | |
|       CSS_TEMPLATE,
 | |
|       TEMPLATE_STRING,
 | |
|       COMMENT,
 | |
|       NUMBER,
 | |
|       { // object attr container
 | |
|         begin: concat(/[{,\n]\s*/,
 | |
|           // we need to look ahead to make sure that we actually have an
 | |
|           // attribute coming up so we don't steal a comma from a potential
 | |
|           // "value" container
 | |
|           //
 | |
|           // NOTE: this might not work how you think.  We don't actually always
 | |
|           // enter this mode and stay.  Instead it might merely match `,
 | |
|           // <comments up next>` and then immediately end after the , because it
 | |
|           // fails to find any actual attrs. But this still does the job because
 | |
|           // it prevents the value contain rule from grabbing this instead and
 | |
|           // prevening this rule from firing when we actually DO have keys.
 | |
|           lookahead(concat(
 | |
|             // we also need to allow for multiple possible comments inbetween
 | |
|             // the first key:value pairing
 | |
|             /(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,
 | |
|             IDENT_RE$1 + '\\s*:'))),
 | |
|         relevance: 0,
 | |
|         contains: [
 | |
|           {
 | |
|             className: 'attr',
 | |
|             begin: IDENT_RE$1 + lookahead('\\s*:'),
 | |
|             relevance: 0
 | |
|           }
 | |
|         ]
 | |
|       },
 | |
|       { // "value" container
 | |
|         begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*',
 | |
|         keywords: 'return throw case',
 | |
|         contains: [
 | |
|           COMMENT,
 | |
|           hljs.REGEXP_MODE,
 | |
|           {
 | |
|             className: 'function',
 | |
|             // we have to count the parens to make sure we actually have the
 | |
|             // correct bounding ( ) before the =>.  There could be any number of
 | |
|             // sub-expressions inside also surrounded by parens.
 | |
|             begin: '(\\(' +
 | |
|             '[^()]*(\\(' +
 | |
|             '[^()]*(\\(' +
 | |
|             '[^()]*' +
 | |
|             '\\)[^()]*)*' +
 | |
|             '\\)[^()]*)*' +
 | |
|             '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>',
 | |
|             returnBegin: true,
 | |
|             end: '\\s*=>',
 | |
|             contains: [
 | |
|               {
 | |
|                 className: 'params',
 | |
|                 variants: [
 | |
|                   {
 | |
|                     begin: hljs.UNDERSCORE_IDENT_RE,
 | |
|                     relevance: 0
 | |
|                   },
 | |
|                   {
 | |
|                     className: null,
 | |
|                     begin: /\(\s*\)/,
 | |
|                     skip: true
 | |
|                   },
 | |
|                   {
 | |
|                     begin: /\(/,
 | |
|                     end: /\)/,
 | |
|                     excludeBegin: true,
 | |
|                     excludeEnd: true,
 | |
|                     keywords: KEYWORDS$1,
 | |
|                     contains: PARAMS_CONTAINS
 | |
|                   }
 | |
|                 ]
 | |
|               }
 | |
|             ]
 | |
|           },
 | |
|           { // could be a comma delimited list of params to a function call
 | |
|             begin: /,/, relevance: 0
 | |
|           },
 | |
|           {
 | |
|             className: '',
 | |
|             begin: /\s/,
 | |
|             end: /\s*/,
 | |
|             skip: true
 | |
|           },
 | |
|           { // JSX
 | |
|             variants: [
 | |
|               { begin: FRAGMENT.begin, end: FRAGMENT.end },
 | |
|               {
 | |
|                 begin: XML_TAG.begin,
 | |
|                 // we carefully check the opening tag to see if it truly
 | |
|                 // is a tag and not a false positive
 | |
|                 'on:begin': XML_TAG.isTrulyOpeningTag,
 | |
|                 end: XML_TAG.end
 | |
|               }
 | |
|             ],
 | |
|             subLanguage: 'xml',
 | |
|             contains: [
 | |
|               {
 | |
|                 begin: XML_TAG.begin,
 | |
|                 end: XML_TAG.end,
 | |
|                 skip: true,
 | |
|                 contains: ['self']
 | |
|               }
 | |
|             ]
 | |
|           }
 | |
|         ],
 | |
|         relevance: 0
 | |
|       },
 | |
|       {
 | |
|         className: 'function',
 | |
|         beginKeywords: 'function',
 | |
|         end: /[{;]/,
 | |
|         excludeEnd: true,
 | |
|         keywords: KEYWORDS$1,
 | |
|         contains: [
 | |
|           'self',
 | |
|           hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
 | |
|           PARAMS
 | |
|         ],
 | |
|         illegal: /%/
 | |
|       },
 | |
|       {
 | |
|         // prevent this from getting swallowed up by function
 | |
|         // since they appear "function like"
 | |
|         beginKeywords: "while if switch catch for"
 | |
|       },
 | |
|       {
 | |
|         className: 'function',
 | |
|         // we have to count the parens to make sure we actually have the correct
 | |
|         // bounding ( ).  There could be any number of sub-expressions inside
 | |
|         // also surrounded by parens.
 | |
|         begin: hljs.UNDERSCORE_IDENT_RE +
 | |
|           '\\(' + // first parens
 | |
|           '[^()]*(\\(' +
 | |
|             '[^()]*(\\(' +
 | |
|               '[^()]*' +
 | |
|             '\\)[^()]*)*' +
 | |
|           '\\)[^()]*)*' +
 | |
|           '\\)\\s*\\{', // end parens
 | |
|         returnBegin:true,
 | |
|         contains: [
 | |
|           PARAMS,
 | |
|           hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
 | |
|         ]
 | |
|       },
 | |
|       // hack: prevents detection of keywords in some circumstances
 | |
|       // .keyword()
 | |
|       // $keyword = x
 | |
|       {
 | |
|         variants: [
 | |
|           { begin: '\\.' + IDENT_RE$1 },
 | |
|           { begin: '\\$' + IDENT_RE$1 }
 | |
|         ],
 | |
|         relevance: 0
 | |
|       },
 | |
|       { // ES6 class
 | |
|         className: 'class',
 | |
|         beginKeywords: 'class',
 | |
|         end: /[{;=]/,
 | |
|         excludeEnd: true,
 | |
|         illegal: /[:"[\]]/,
 | |
|         contains: [
 | |
|           { beginKeywords: 'extends' },
 | |
|           hljs.UNDERSCORE_TITLE_MODE
 | |
|         ]
 | |
|       },
 | |
|       {
 | |
|         begin: /\b(?=constructor)/,
 | |
|         end: /[{;]/,
 | |
|         excludeEnd: true,
 | |
|         contains: [
 | |
|           hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
 | |
|           'self',
 | |
|           PARAMS
 | |
|         ]
 | |
|       },
 | |
|       {
 | |
|         begin: '(get|set)\\s+(?=' + IDENT_RE$1 + '\\()',
 | |
|         end: /\{/,
 | |
|         keywords: "get set",
 | |
|         contains: [
 | |
|           hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
 | |
|           { begin: /\(\)/ }, // eat to avoid empty params
 | |
|           PARAMS
 | |
|         ]
 | |
|       },
 | |
|       {
 | |
|         begin: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
 | |
|       }
 | |
|     ]
 | |
|   };
 | |
| }
 | |
| 
 | |
| module.exports = javascript;
 |