You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
14 KiB
604 lines
14 KiB
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;
|
|
|