javascript.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';
  2. const 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. ];
  45. const LITERALS = [
  46. "true",
  47. "false",
  48. "null",
  49. "undefined",
  50. "NaN",
  51. "Infinity"
  52. ];
  53. const TYPES = [
  54. "Intl",
  55. "DataView",
  56. "Number",
  57. "Math",
  58. "Date",
  59. "String",
  60. "RegExp",
  61. "Object",
  62. "Function",
  63. "Boolean",
  64. "Error",
  65. "Symbol",
  66. "Set",
  67. "Map",
  68. "WeakSet",
  69. "WeakMap",
  70. "Proxy",
  71. "Reflect",
  72. "JSON",
  73. "Promise",
  74. "Float64Array",
  75. "Int16Array",
  76. "Int32Array",
  77. "Int8Array",
  78. "Uint16Array",
  79. "Uint32Array",
  80. "Float32Array",
  81. "Array",
  82. "Uint8Array",
  83. "Uint8ClampedArray",
  84. "ArrayBuffer",
  85. "BigInt64Array",
  86. "BigUint64Array",
  87. "BigInt"
  88. ];
  89. const ERROR_TYPES = [
  90. "EvalError",
  91. "InternalError",
  92. "RangeError",
  93. "ReferenceError",
  94. "SyntaxError",
  95. "TypeError",
  96. "URIError"
  97. ];
  98. const BUILT_IN_GLOBALS = [
  99. "setInterval",
  100. "setTimeout",
  101. "clearInterval",
  102. "clearTimeout",
  103. "require",
  104. "exports",
  105. "eval",
  106. "isFinite",
  107. "isNaN",
  108. "parseFloat",
  109. "parseInt",
  110. "decodeURI",
  111. "decodeURIComponent",
  112. "encodeURI",
  113. "encodeURIComponent",
  114. "escape",
  115. "unescape"
  116. ];
  117. const BUILT_IN_VARIABLES = [
  118. "arguments",
  119. "this",
  120. "super",
  121. "console",
  122. "window",
  123. "document",
  124. "localStorage",
  125. "module",
  126. "global" // Node.js
  127. ];
  128. const BUILT_INS = [].concat(
  129. BUILT_IN_GLOBALS,
  130. BUILT_IN_VARIABLES,
  131. TYPES,
  132. ERROR_TYPES
  133. );
  134. /**
  135. * @param {string} value
  136. * @returns {RegExp}
  137. * */
  138. /**
  139. * @param {RegExp | string } re
  140. * @returns {string}
  141. */
  142. function source(re) {
  143. if (!re) return null;
  144. if (typeof re === "string") return re;
  145. return re.source;
  146. }
  147. /**
  148. * @param {RegExp | string } re
  149. * @returns {string}
  150. */
  151. function lookahead(re) {
  152. return concat('(?=', re, ')');
  153. }
  154. /**
  155. * @param {...(RegExp | string) } args
  156. * @returns {string}
  157. */
  158. function concat(...args) {
  159. const joined = args.map((x) => source(x)).join("");
  160. return joined;
  161. }
  162. /*
  163. Language: JavaScript
  164. Description: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.
  165. Category: common, scripting
  166. Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript
  167. */
  168. /** @type LanguageFn */
  169. function javascript(hljs) {
  170. /**
  171. * Takes a string like "<Booger" and checks to see
  172. * if we can find a matching "</Booger" later in the
  173. * content.
  174. * @param {RegExpMatchArray} match
  175. * @param {{after:number}} param1
  176. */
  177. const hasClosingTag = (match, { after }) => {
  178. const tag = "</" + match[0].slice(1);
  179. const pos = match.input.indexOf(tag, after);
  180. return pos !== -1;
  181. };
  182. const IDENT_RE$1 = IDENT_RE;
  183. const FRAGMENT = {
  184. begin: '<>',
  185. end: '</>'
  186. };
  187. const XML_TAG = {
  188. begin: /<[A-Za-z0-9\\._:-]+/,
  189. end: /\/[A-Za-z0-9\\._:-]+>|\/>/,
  190. /**
  191. * @param {RegExpMatchArray} match
  192. * @param {CallbackResponse} response
  193. */
  194. isTrulyOpeningTag: (match, response) => {
  195. const afterMatchIndex = match[0].length + match.index;
  196. const nextChar = match.input[afterMatchIndex];
  197. // nested type?
  198. // HTML should not include another raw `<` inside a tag
  199. // But a type might: `<Array<Array<number>>`, etc.
  200. if (nextChar === "<") {
  201. response.ignoreMatch();
  202. return;
  203. }
  204. // <something>
  205. // This is now either a tag or a type.
  206. if (nextChar === ">") {
  207. // if we cannot find a matching closing tag, then we
  208. // will ignore it
  209. if (!hasClosingTag(match, { after: afterMatchIndex })) {
  210. response.ignoreMatch();
  211. }
  212. }
  213. }
  214. };
  215. const KEYWORDS$1 = {
  216. $pattern: IDENT_RE,
  217. keyword: KEYWORDS,
  218. literal: LITERALS,
  219. built_in: BUILT_INS
  220. };
  221. // https://tc39.es/ecma262/#sec-literals-numeric-literals
  222. const decimalDigits = '[0-9](_?[0-9])*';
  223. const frac = `\\.(${decimalDigits})`;
  224. // DecimalIntegerLiteral, including Annex B NonOctalDecimalIntegerLiteral
  225. // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
  226. const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`;
  227. const NUMBER = {
  228. className: 'number',
  229. variants: [
  230. // DecimalLiteral
  231. { begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))` +
  232. `[eE][+-]?(${decimalDigits})\\b` },
  233. { begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` },
  234. // DecimalBigIntegerLiteral
  235. { begin: `\\b(0|[1-9](_?[0-9])*)n\\b` },
  236. // NonDecimalIntegerLiteral
  237. { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" },
  238. { begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" },
  239. { begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" },
  240. // LegacyOctalIntegerLiteral (does not include underscore separators)
  241. // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
  242. { begin: "\\b0[0-7]+n?\\b" },
  243. ],
  244. relevance: 0
  245. };
  246. const SUBST = {
  247. className: 'subst',
  248. begin: '\\$\\{',
  249. end: '\\}',
  250. keywords: KEYWORDS$1,
  251. contains: [] // defined later
  252. };
  253. const HTML_TEMPLATE = {
  254. begin: 'html`',
  255. end: '',
  256. starts: {
  257. end: '`',
  258. returnEnd: false,
  259. contains: [
  260. hljs.BACKSLASH_ESCAPE,
  261. SUBST
  262. ],
  263. subLanguage: 'xml'
  264. }
  265. };
  266. const CSS_TEMPLATE = {
  267. begin: 'css`',
  268. end: '',
  269. starts: {
  270. end: '`',
  271. returnEnd: false,
  272. contains: [
  273. hljs.BACKSLASH_ESCAPE,
  274. SUBST
  275. ],
  276. subLanguage: 'css'
  277. }
  278. };
  279. const TEMPLATE_STRING = {
  280. className: 'string',
  281. begin: '`',
  282. end: '`',
  283. contains: [
  284. hljs.BACKSLASH_ESCAPE,
  285. SUBST
  286. ]
  287. };
  288. const JSDOC_COMMENT = hljs.COMMENT(
  289. /\/\*\*(?!\/)/,
  290. '\\*/',
  291. {
  292. relevance: 0,
  293. contains: [
  294. {
  295. className: 'doctag',
  296. begin: '@[A-Za-z]+',
  297. contains: [
  298. {
  299. className: 'type',
  300. begin: '\\{',
  301. end: '\\}',
  302. relevance: 0
  303. },
  304. {
  305. className: 'variable',
  306. begin: IDENT_RE$1 + '(?=\\s*(-)|$)',
  307. endsParent: true,
  308. relevance: 0
  309. },
  310. // eat spaces (not newlines) so we can find
  311. // types or variables
  312. {
  313. begin: /(?=[^\n])\s/,
  314. relevance: 0
  315. }
  316. ]
  317. }
  318. ]
  319. }
  320. );
  321. const COMMENT = {
  322. className: "comment",
  323. variants: [
  324. JSDOC_COMMENT,
  325. hljs.C_BLOCK_COMMENT_MODE,
  326. hljs.C_LINE_COMMENT_MODE
  327. ]
  328. };
  329. const SUBST_INTERNALS = [
  330. hljs.APOS_STRING_MODE,
  331. hljs.QUOTE_STRING_MODE,
  332. HTML_TEMPLATE,
  333. CSS_TEMPLATE,
  334. TEMPLATE_STRING,
  335. NUMBER,
  336. hljs.REGEXP_MODE
  337. ];
  338. SUBST.contains = SUBST_INTERNALS
  339. .concat({
  340. // we need to pair up {} inside our subst to prevent
  341. // it from ending too early by matching another }
  342. begin: /\{/,
  343. end: /\}/,
  344. keywords: KEYWORDS$1,
  345. contains: [
  346. "self"
  347. ].concat(SUBST_INTERNALS)
  348. });
  349. const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains);
  350. const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([
  351. // eat recursive parens in sub expressions
  352. {
  353. begin: /\(/,
  354. end: /\)/,
  355. keywords: KEYWORDS$1,
  356. contains: ["self"].concat(SUBST_AND_COMMENTS)
  357. }
  358. ]);
  359. const PARAMS = {
  360. className: 'params',
  361. begin: /\(/,
  362. end: /\)/,
  363. excludeBegin: true,
  364. excludeEnd: true,
  365. keywords: KEYWORDS$1,
  366. contains: PARAMS_CONTAINS
  367. };
  368. return {
  369. name: 'Javascript',
  370. aliases: ['js', 'jsx', 'mjs', 'cjs'],
  371. keywords: KEYWORDS$1,
  372. // this will be extended by TypeScript
  373. exports: { PARAMS_CONTAINS },
  374. illegal: /#(?![$_A-z])/,
  375. contains: [
  376. hljs.SHEBANG({
  377. label: "shebang",
  378. binary: "node",
  379. relevance: 5
  380. }),
  381. {
  382. label: "use_strict",
  383. className: 'meta',
  384. relevance: 10,
  385. begin: /^\s*['"]use (strict|asm)['"]/
  386. },
  387. hljs.APOS_STRING_MODE,
  388. hljs.QUOTE_STRING_MODE,
  389. HTML_TEMPLATE,
  390. CSS_TEMPLATE,
  391. TEMPLATE_STRING,
  392. COMMENT,
  393. NUMBER,
  394. { // object attr container
  395. begin: concat(/[{,\n]\s*/,
  396. // we need to look ahead to make sure that we actually have an
  397. // attribute coming up so we don't steal a comma from a potential
  398. // "value" container
  399. //
  400. // NOTE: this might not work how you think. We don't actually always
  401. // enter this mode and stay. Instead it might merely match `,
  402. // <comments up next>` and then immediately end after the , because it
  403. // fails to find any actual attrs. But this still does the job because
  404. // it prevents the value contain rule from grabbing this instead and
  405. // prevening this rule from firing when we actually DO have keys.
  406. lookahead(concat(
  407. // we also need to allow for multiple possible comments inbetween
  408. // the first key:value pairing
  409. /(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,
  410. IDENT_RE$1 + '\\s*:'))),
  411. relevance: 0,
  412. contains: [
  413. {
  414. className: 'attr',
  415. begin: IDENT_RE$1 + lookahead('\\s*:'),
  416. relevance: 0
  417. }
  418. ]
  419. },
  420. { // "value" container
  421. begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*',
  422. keywords: 'return throw case',
  423. contains: [
  424. COMMENT,
  425. hljs.REGEXP_MODE,
  426. {
  427. className: 'function',
  428. // we have to count the parens to make sure we actually have the
  429. // correct bounding ( ) before the =>. There could be any number of
  430. // sub-expressions inside also surrounded by parens.
  431. begin: '(\\(' +
  432. '[^()]*(\\(' +
  433. '[^()]*(\\(' +
  434. '[^()]*' +
  435. '\\)[^()]*)*' +
  436. '\\)[^()]*)*' +
  437. '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>',
  438. returnBegin: true,
  439. end: '\\s*=>',
  440. contains: [
  441. {
  442. className: 'params',
  443. variants: [
  444. {
  445. begin: hljs.UNDERSCORE_IDENT_RE,
  446. relevance: 0
  447. },
  448. {
  449. className: null,
  450. begin: /\(\s*\)/,
  451. skip: true
  452. },
  453. {
  454. begin: /\(/,
  455. end: /\)/,
  456. excludeBegin: true,
  457. excludeEnd: true,
  458. keywords: KEYWORDS$1,
  459. contains: PARAMS_CONTAINS
  460. }
  461. ]
  462. }
  463. ]
  464. },
  465. { // could be a comma delimited list of params to a function call
  466. begin: /,/, relevance: 0
  467. },
  468. {
  469. className: '',
  470. begin: /\s/,
  471. end: /\s*/,
  472. skip: true
  473. },
  474. { // JSX
  475. variants: [
  476. { begin: FRAGMENT.begin, end: FRAGMENT.end },
  477. {
  478. begin: XML_TAG.begin,
  479. // we carefully check the opening tag to see if it truly
  480. // is a tag and not a false positive
  481. 'on:begin': XML_TAG.isTrulyOpeningTag,
  482. end: XML_TAG.end
  483. }
  484. ],
  485. subLanguage: 'xml',
  486. contains: [
  487. {
  488. begin: XML_TAG.begin,
  489. end: XML_TAG.end,
  490. skip: true,
  491. contains: ['self']
  492. }
  493. ]
  494. }
  495. ],
  496. relevance: 0
  497. },
  498. {
  499. className: 'function',
  500. beginKeywords: 'function',
  501. end: /[{;]/,
  502. excludeEnd: true,
  503. keywords: KEYWORDS$1,
  504. contains: [
  505. 'self',
  506. hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
  507. PARAMS
  508. ],
  509. illegal: /%/
  510. },
  511. {
  512. // prevent this from getting swallowed up by function
  513. // since they appear "function like"
  514. beginKeywords: "while if switch catch for"
  515. },
  516. {
  517. className: 'function',
  518. // we have to count the parens to make sure we actually have the correct
  519. // bounding ( ). There could be any number of sub-expressions inside
  520. // also surrounded by parens.
  521. begin: hljs.UNDERSCORE_IDENT_RE +
  522. '\\(' + // first parens
  523. '[^()]*(\\(' +
  524. '[^()]*(\\(' +
  525. '[^()]*' +
  526. '\\)[^()]*)*' +
  527. '\\)[^()]*)*' +
  528. '\\)\\s*\\{', // end parens
  529. returnBegin:true,
  530. contains: [
  531. PARAMS,
  532. hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
  533. ]
  534. },
  535. // hack: prevents detection of keywords in some circumstances
  536. // .keyword()
  537. // $keyword = x
  538. {
  539. variants: [
  540. { begin: '\\.' + IDENT_RE$1 },
  541. { begin: '\\$' + IDENT_RE$1 }
  542. ],
  543. relevance: 0
  544. },
  545. { // ES6 class
  546. className: 'class',
  547. beginKeywords: 'class',
  548. end: /[{;=]/,
  549. excludeEnd: true,
  550. illegal: /[:"[\]]/,
  551. contains: [
  552. { beginKeywords: 'extends' },
  553. hljs.UNDERSCORE_TITLE_MODE
  554. ]
  555. },
  556. {
  557. begin: /\b(?=constructor)/,
  558. end: /[{;]/,
  559. excludeEnd: true,
  560. contains: [
  561. hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
  562. 'self',
  563. PARAMS
  564. ]
  565. },
  566. {
  567. begin: '(get|set)\\s+(?=' + IDENT_RE$1 + '\\()',
  568. end: /\{/,
  569. keywords: "get set",
  570. contains: [
  571. hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1 }),
  572. { begin: /\(\)/ }, // eat to avoid empty params
  573. PARAMS
  574. ]
  575. },
  576. {
  577. begin: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
  578. }
  579. ]
  580. };
  581. }
  582. module.exports = javascript;