mirror of https://github.com/jkjoy/sunpeiwen.git
750 lines
18 KiB
JavaScript
750 lines
18 KiB
JavaScript
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"
|
|
];
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
|
|
const TYPES = [
|
|
// Fundamental objects
|
|
"Object",
|
|
"Function",
|
|
"Boolean",
|
|
"Symbol",
|
|
// numbers and dates
|
|
"Math",
|
|
"Date",
|
|
"Number",
|
|
"BigInt",
|
|
// text
|
|
"String",
|
|
"RegExp",
|
|
// Indexed collections
|
|
"Array",
|
|
"Float32Array",
|
|
"Float64Array",
|
|
"Int8Array",
|
|
"Uint8Array",
|
|
"Uint8ClampedArray",
|
|
"Int16Array",
|
|
"Int32Array",
|
|
"Uint16Array",
|
|
"Uint32Array",
|
|
"BigInt64Array",
|
|
"BigUint64Array",
|
|
// Keyed collections
|
|
"Set",
|
|
"Map",
|
|
"WeakSet",
|
|
"WeakMap",
|
|
// Structured data
|
|
"ArrayBuffer",
|
|
"SharedArrayBuffer",
|
|
"Atomics",
|
|
"DataView",
|
|
"JSON",
|
|
// Control abstraction objects
|
|
"Promise",
|
|
"Generator",
|
|
"GeneratorFunction",
|
|
"AsyncFunction",
|
|
// Reflection
|
|
"Reflect",
|
|
"Proxy",
|
|
// Internationalization
|
|
"Intl",
|
|
// WebAssembly
|
|
"WebAssembly"
|
|
];
|
|
|
|
const ERROR_TYPES = [
|
|
"Error",
|
|
"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,
|
|
TYPES,
|
|
ERROR_TYPES
|
|
);
|
|
|
|
/*
|
|
Language: JavaScript
|
|
Description: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.
|
|
Category: common, scripting, web
|
|
Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript
|
|
*/
|
|
|
|
/** @type LanguageFn */
|
|
function javascript(hljs) {
|
|
const regex = hljs.regex;
|
|
/**
|
|
* 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: '</>'
|
|
};
|
|
// to avoid some special cases inside isTrulyOpeningTag
|
|
const XML_SELF_CLOSING = /<[A-Za-z0-9\\._:-]+\s*\/>/;
|
|
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];
|
|
if (
|
|
// HTML should not include another raw `<` inside a tag
|
|
// nested type?
|
|
// `<Array<Array<number>>`, etc.
|
|
nextChar === "<" ||
|
|
// the , gives away that this is not HTML
|
|
// `<T, A extends keyof T, V>`
|
|
nextChar === ","
|
|
) {
|
|
response.ignoreMatch();
|
|
return;
|
|
}
|
|
|
|
// `<something>`
|
|
// Quite possibly a tag, lets look for a matching closing tag...
|
|
if (nextChar === ">") {
|
|
// if we cannot find a matching closing tag, then we
|
|
// will ignore it
|
|
if (!hasClosingTag(match, { after: afterMatchIndex })) {
|
|
response.ignoreMatch();
|
|
}
|
|
}
|
|
|
|
// `<blah />` (self-closing)
|
|
// handled by simpleSelfClosing rule
|
|
|
|
let m;
|
|
const afterMatch = match.input.substring(afterMatchIndex);
|
|
|
|
// some more template typing stuff
|
|
// <T = any>(key?: string) => Modify<
|
|
if ((m = afterMatch.match(/^\s*=/))) {
|
|
response.ignoreMatch();
|
|
return;
|
|
}
|
|
|
|
// `<From extends string>`
|
|
// technically this could be HTML, but it smells like a type
|
|
// NOTE: This is ugh, but added specifically for https://github.com/highlightjs/highlight.js/issues/3276
|
|
if ((m = afterMatch.match(/^\s+extends\s+/))) {
|
|
if (m.index === 0) {
|
|
response.ignoreMatch();
|
|
// eslint-disable-next-line no-useless-return
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const KEYWORDS$1 = {
|
|
$pattern: IDENT_RE,
|
|
keyword: KEYWORDS,
|
|
literal: LITERALS,
|
|
built_in: BUILT_INS,
|
|
"variable.language": BUILT_IN_VARIABLES
|
|
};
|
|
|
|
// 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: [
|
|
{
|
|
begin: '(?=@[A-Za-z]+)',
|
|
relevance: 0,
|
|
contains: [
|
|
{
|
|
className: 'doctag',
|
|
begin: '@[A-Za-z]+'
|
|
},
|
|
{
|
|
className: 'type',
|
|
begin: '\\{',
|
|
end: '\\}',
|
|
excludeEnd: true,
|
|
excludeBegin: true,
|
|
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,
|
|
// Skip numbers when they are part of a variable name
|
|
{ match: /\$\d+/ },
|
|
NUMBER,
|
|
// This is intentional:
|
|
// See https://github.com/highlightjs/highlight.js/issues/3288
|
|
// 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
|
|
};
|
|
|
|
// ES6 classes
|
|
const CLASS_OR_EXTENDS = {
|
|
variants: [
|
|
// class Car extends vehicle
|
|
{
|
|
match: [
|
|
/class/,
|
|
/\s+/,
|
|
IDENT_RE$1,
|
|
/\s+/,
|
|
/extends/,
|
|
/\s+/,
|
|
regex.concat(IDENT_RE$1, "(", regex.concat(/\./, IDENT_RE$1), ")*")
|
|
],
|
|
scope: {
|
|
1: "keyword",
|
|
3: "title.class",
|
|
5: "keyword",
|
|
7: "title.class.inherited"
|
|
}
|
|
},
|
|
// class Car
|
|
{
|
|
match: [
|
|
/class/,
|
|
/\s+/,
|
|
IDENT_RE$1
|
|
],
|
|
scope: {
|
|
1: "keyword",
|
|
3: "title.class"
|
|
}
|
|
},
|
|
|
|
]
|
|
};
|
|
|
|
const CLASS_REFERENCE = {
|
|
relevance: 0,
|
|
match:
|
|
regex.either(
|
|
// Hard coded exceptions
|
|
/\bJSON/,
|
|
// Float32Array, OutT
|
|
/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,
|
|
// CSSFactory, CSSFactoryT
|
|
/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,
|
|
// FPs, FPsT
|
|
/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/,
|
|
// P
|
|
// single letters are not highlighted
|
|
// BLAH
|
|
// this will be flagged as a UPPER_CASE_CONSTANT instead
|
|
),
|
|
className: "title.class",
|
|
keywords: {
|
|
_: [
|
|
// se we still get relevance credit for JS library classes
|
|
...TYPES,
|
|
...ERROR_TYPES
|
|
]
|
|
}
|
|
};
|
|
|
|
const USE_STRICT = {
|
|
label: "use_strict",
|
|
className: 'meta',
|
|
relevance: 10,
|
|
begin: /^\s*['"]use (strict|asm)['"]/
|
|
};
|
|
|
|
const FUNCTION_DEFINITION = {
|
|
variants: [
|
|
{
|
|
match: [
|
|
/function/,
|
|
/\s+/,
|
|
IDENT_RE$1,
|
|
/(?=\s*\()/
|
|
]
|
|
},
|
|
// anonymous function
|
|
{
|
|
match: [
|
|
/function/,
|
|
/\s*(?=\()/
|
|
]
|
|
}
|
|
],
|
|
className: {
|
|
1: "keyword",
|
|
3: "title.function"
|
|
},
|
|
label: "func.def",
|
|
contains: [ PARAMS ],
|
|
illegal: /%/
|
|
};
|
|
|
|
const UPPER_CASE_CONSTANT = {
|
|
relevance: 0,
|
|
match: /\b[A-Z][A-Z_0-9]+\b/,
|
|
className: "variable.constant"
|
|
};
|
|
|
|
function noneOf(list) {
|
|
return regex.concat("(?!", list.join("|"), ")");
|
|
}
|
|
|
|
const FUNCTION_CALL = {
|
|
match: regex.concat(
|
|
/\b/,
|
|
noneOf([
|
|
...BUILT_IN_GLOBALS,
|
|
"super",
|
|
"import"
|
|
]),
|
|
IDENT_RE$1, regex.lookahead(/\(/)),
|
|
className: "title.function",
|
|
relevance: 0
|
|
};
|
|
|
|
const PROPERTY_ACCESS = {
|
|
begin: regex.concat(/\./, regex.lookahead(
|
|
regex.concat(IDENT_RE$1, /(?![0-9A-Za-z$_(])/)
|
|
)),
|
|
end: IDENT_RE$1,
|
|
excludeBegin: true,
|
|
keywords: "prototype",
|
|
className: "property",
|
|
relevance: 0
|
|
};
|
|
|
|
const GETTER_OR_SETTER = {
|
|
match: [
|
|
/get|set/,
|
|
/\s+/,
|
|
IDENT_RE$1,
|
|
/(?=\()/
|
|
],
|
|
className: {
|
|
1: "keyword",
|
|
3: "title.function"
|
|
},
|
|
contains: [
|
|
{ // eat to avoid empty params
|
|
begin: /\(\)/
|
|
},
|
|
PARAMS
|
|
]
|
|
};
|
|
|
|
const FUNC_LEAD_IN_RE = '(\\(' +
|
|
'[^()]*(\\(' +
|
|
'[^()]*(\\(' +
|
|
'[^()]*' +
|
|
'\\)[^()]*)*' +
|
|
'\\)[^()]*)*' +
|
|
'\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>';
|
|
|
|
const FUNCTION_VARIABLE = {
|
|
match: [
|
|
/const|var|let/, /\s+/,
|
|
IDENT_RE$1, /\s*/,
|
|
/=\s*/,
|
|
/(async\s*)?/, // async is optional
|
|
regex.lookahead(FUNC_LEAD_IN_RE)
|
|
],
|
|
keywords: "async",
|
|
className: {
|
|
1: "keyword",
|
|
3: "title.function"
|
|
},
|
|
contains: [
|
|
PARAMS
|
|
]
|
|
};
|
|
|
|
return {
|
|
name: 'Javascript',
|
|
aliases: ['js', 'jsx', 'mjs', 'cjs'],
|
|
keywords: KEYWORDS$1,
|
|
// this will be extended by TypeScript
|
|
exports: { PARAMS_CONTAINS, CLASS_REFERENCE },
|
|
illegal: /#(?![$_A-z])/,
|
|
contains: [
|
|
hljs.SHEBANG({
|
|
label: "shebang",
|
|
binary: "node",
|
|
relevance: 5
|
|
}),
|
|
USE_STRICT,
|
|
hljs.APOS_STRING_MODE,
|
|
hljs.QUOTE_STRING_MODE,
|
|
HTML_TEMPLATE,
|
|
CSS_TEMPLATE,
|
|
TEMPLATE_STRING,
|
|
COMMENT,
|
|
// Skip numbers when they are part of a variable name
|
|
{ match: /\$\d+/ },
|
|
NUMBER,
|
|
CLASS_REFERENCE,
|
|
{
|
|
className: 'attr',
|
|
begin: IDENT_RE$1 + regex.lookahead(':'),
|
|
relevance: 0
|
|
},
|
|
FUNCTION_VARIABLE,
|
|
{ // "value" container
|
|
begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*',
|
|
keywords: 'return throw case',
|
|
relevance: 0,
|
|
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: FUNC_LEAD_IN_RE,
|
|
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
|
|
},
|
|
{
|
|
match: /\s+/,
|
|
relevance: 0
|
|
},
|
|
{ // JSX
|
|
variants: [
|
|
{ begin: FRAGMENT.begin, end: FRAGMENT.end },
|
|
{ match: XML_SELF_CLOSING },
|
|
{
|
|
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']
|
|
}
|
|
]
|
|
}
|
|
],
|
|
},
|
|
FUNCTION_DEFINITION,
|
|
{
|
|
// prevent this from getting swallowed up by function
|
|
// since they appear "function like"
|
|
beginKeywords: "while if switch catch for"
|
|
},
|
|
{
|
|
// 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: '\\b(?!function)' + hljs.UNDERSCORE_IDENT_RE +
|
|
'\\(' + // first parens
|
|
'[^()]*(\\(' +
|
|
'[^()]*(\\(' +
|
|
'[^()]*' +
|
|
'\\)[^()]*)*' +
|
|
'\\)[^()]*)*' +
|
|
'\\)\\s*\\{', // end parens
|
|
returnBegin:true,
|
|
label: "func.def",
|
|
contains: [
|
|
PARAMS,
|
|
hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1, className: "title.function" })
|
|
]
|
|
},
|
|
// catch ... so it won't trigger the property rule below
|
|
{
|
|
match: /\.\.\./,
|
|
relevance: 0
|
|
},
|
|
PROPERTY_ACCESS,
|
|
// hack: prevents detection of keywords in some circumstances
|
|
// .keyword()
|
|
// $keyword = x
|
|
{
|
|
match: '\\$' + IDENT_RE$1,
|
|
relevance: 0
|
|
},
|
|
{
|
|
match: [ /\bconstructor(?=\s*\()/ ],
|
|
className: { 1: "title.function" },
|
|
contains: [ PARAMS ]
|
|
},
|
|
FUNCTION_CALL,
|
|
UPPER_CASE_CONSTANT,
|
|
CLASS_OR_EXTENDS,
|
|
GETTER_OR_SETTER,
|
|
{
|
|
match: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
module.exports = javascript;
|