text: /*
  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
  */
  
  import * as ECMAScript from './lib/ecmascript.js';
  import * as regex from '../lib/regex.js';
  
  export default function(hljs) {
    var IDENT_RE = ECMAScript.IDENT_RE;
    var FRAGMENT = {
      begin: '<>',
      end: '</>'
    };
    var XML_TAG = {
      begin: /<[A-Za-z0-9\\._:-]+/,
      end: /\/[A-Za-z0-9\\._:-]+>|\/>/
    };
    var KEYWORDS = {
      $pattern: ECMAScript.IDENT_RE,
      keyword: ECMAScript.KEYWORDS.join(" "),
      literal: ECMAScript.LITERALS.join(" "),
      built_in: ECMAScript.BUILT_INS.join(" ")
    };
    var NUMBER = {
      className: 'number',
      variants: [
        { begin: '\\b(0[bB][01]+)n?' },
        { begin: '\\b(0[oO][0-7]+)n?' },
        { begin: hljs.C_NUMBER_RE + 'n?' }
      ],
      relevance: 0
    };
    var SUBST = {
      className: 'subst',
      begin: '\\$\\{', end: '\\}',
      keywords: KEYWORDS,
      contains: []  // defined later
    };
    var HTML_TEMPLATE = {
      begin: 'html`', end: '',
      starts: {
        end: '`', returnEnd: false,
        contains: [
          hljs.BACKSLASH_ESCAPE,
          SUBST
        ],
        subLanguage: 'xml',
      }
    };
    var CSS_TEMPLATE = {
      begin: 'css`', end: '',
      starts: {
        end: '`', returnEnd: false,
        contains: [
          hljs.BACKSLASH_ESCAPE,
          SUBST
        ],
        subLanguage: 'css',
      }
    };
    var TEMPLATE_STRING = {
      className: 'string',
      begin: '`', end: '`',
      contains: [
        hljs.BACKSLASH_ESCAPE,
        SUBST
      ]
    };
    SUBST.contains = [
      hljs.APOS_STRING_MODE,
      hljs.QUOTE_STRING_MODE,
      HTML_TEMPLATE,
      CSS_TEMPLATE,
      TEMPLATE_STRING,
      NUMBER,
      hljs.REGEXP_MODE
    ];
    var PARAMS_CONTAINS = SUBST.contains.concat([
      // eat recursive parens in sub expressions
      { begin: /\(/, end: /\)/,
        contains: ["self"].concat(SUBST.contains, [hljs.C_BLOCK_COMMENT_MODE, hljs.C_LINE_COMMENT_MODE])
      },
      hljs.C_BLOCK_COMMENT_MODE,
      hljs.C_LINE_COMMENT_MODE
    ]);
    var PARAMS = {
      className: 'params',
      begin: /\(/, end: /\)/,
      excludeBegin: true,
      excludeEnd: true,
      contains: PARAMS_CONTAINS
    };
  
    return {
      name: 'JavaScript',
      aliases: ['js', 'jsx', 'mjs', 'cjs'],
      keywords: KEYWORDS,
      contains: [
        hljs.SHEBANG({
          binary: "node",
          relevance: 5
        }),
        {
          className: 'meta',
          relevance: 10,
          begin: /^\s*['"]use (strict|asm)['"]/
        },
        hljs.APOS_STRING_MODE,
        hljs.QUOTE_STRING_MODE,
        HTML_TEMPLATE,
        CSS_TEMPLATE,
        TEMPLATE_STRING,
        hljs.C_LINE_COMMENT_MODE,
        hljs.COMMENT(
          '/\\*\\*',
          '\\*/',
          {
            relevance : 0,
            contains : [
              {
                className : 'doctag',
                begin : '@[A-Za-z]+',
                contains : [
                  {
                    className: 'type',
                    begin: '\\{',
                    end: '\\}',
                    relevance: 0
                  },
                  {
                    className: 'variable',
                    begin: IDENT_RE + '(?=\\s*(-)|$)',
                    endsParent: true,
                    relevance: 0
                  },
                  // eat spaces (not newlines) so we can find
                  // types or variables
                  {
                    begin: /(?=[^\n])\s/,
                    relevance: 0
                  },
                ]
              }
            ]
          }
        ),
        hljs.C_BLOCK_COMMENT_MODE,
        NUMBER,
        { // object attr container
          begin: regex.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.
            regex.lookahead(regex.concat(
              // we also need to allow for multiple possible comments inbetween
              // the first key:value pairing
              /(((\/\/.*$)|(\/\*(.|\n)*\*\/))\s*)*/,
              IDENT_RE + '\\s*:'))),
          relevance: 0,
          contains: [
            {
              className: 'attr',
              begin: IDENT_RE + regex.lookahead('\\s*:'),
              relevance: 0,
            },
          ]
        },
        { // "value" container
          begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*',
          keywords: 'return throw case',
          contains: [
            hljs.C_LINE_COMMENT_MODE,
            hljs.C_BLOCK_COMMENT_MODE,
            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
                    },
                    {
                      className: null,
                      begin: /\(\s*\)/,
                      skip: true
                    },
                    {
                      begin: /\(/, end: /\)/,
                      excludeBegin: true, excludeEnd: true,
                      keywords: KEYWORDS,
                      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, 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,
          contains: [
            hljs.inherit(hljs.TITLE_MODE, {begin: IDENT_RE}),
            PARAMS
          ],
          illegal: /\[|%/
        },
        {
          begin: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
        },
  
        hljs.METHOD_GUARD,
        { // ES6 class
          className: 'class',
          beginKeywords: 'class', end: /[{;=]/, excludeEnd: true,
          illegal: /[:"\[\]]/,
          contains: [
            {beginKeywords: 'extends'},
            hljs.UNDERSCORE_TITLE_MODE
          ]
        },
        {
          beginKeywords: 'constructor', end: /\{/, excludeEnd: true
        },
        {
          begin: '(get|set)\\s+(?=' + IDENT_RE + '\\()',
          end: /{/,
          keywords: "get set",
          contains: [
            hljs.inherit(hljs.TITLE_MODE, {begin: IDENT_RE}),
            { begin: /\(\)/ }, // eat to avoid empty params
            PARAMS
          ]
  
        }
      ],
      illegal: /#(?!!)/
    };
  }
  