javascript.js 14 KB

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