javascript.js 17 KB

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