123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613 |
- /*
- Language: PHP
- Author: Victor Karamzin <Victor.Karamzin@enterra-inc.com>
- Contributors: Evgeny Stepanischev <imbolk@gmail.com>, Ivan Sagalaev <maniac@softwaremaniacs.org>
- Website: https://www.php.net
- Category: common
- */
- /**
- * @param {HLJSApi} hljs
- * @returns {LanguageDetail}
- * */
- function php(hljs) {
- const regex = hljs.regex;
- // negative look-ahead tries to avoid matching patterns that are not
- // Perl at all like $ident$, @ident@, etc.
- const NOT_PERL_ETC = /(?![A-Za-z0-9])(?![$])/;
- const IDENT_RE = regex.concat(
- /[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,
- NOT_PERL_ETC);
- // Will not detect camelCase classes
- const PASCAL_CASE_CLASS_NAME_RE = regex.concat(
- /(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,
- NOT_PERL_ETC);
- const VARIABLE = {
- scope: 'variable',
- match: '\\$+' + IDENT_RE,
- };
- const PREPROCESSOR = {
- scope: 'meta',
- variants: [
- { begin: /<\?php/, relevance: 10 }, // boost for obvious PHP
- { begin: /<\?=/ },
- // less relevant per PSR-1 which says not to use short-tags
- { begin: /<\?/, relevance: 0.1 },
- { begin: /\?>/ } // end php tag
- ]
- };
- const SUBST = {
- scope: 'subst',
- variants: [
- { begin: /\$\w+/ },
- {
- begin: /\{\$/,
- end: /\}/
- }
- ]
- };
- const SINGLE_QUOTED = hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null, });
- const DOUBLE_QUOTED = hljs.inherit(hljs.QUOTE_STRING_MODE, {
- illegal: null,
- contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
- });
- const HEREDOC = {
- begin: /<<<[ \t]*(?:(\w+)|"(\w+)")\n/,
- end: /[ \t]*(\w+)\b/,
- contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
- 'on:begin': (m, resp) => { resp.data._beginMatch = m[1] || m[2]; },
- 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); },
- };
- const NOWDOC = hljs.END_SAME_AS_BEGIN({
- begin: /<<<[ \t]*'(\w+)'\n/,
- end: /[ \t]*(\w+)\b/,
- });
- // list of valid whitespaces because non-breaking space might be part of a IDENT_RE
- const WHITESPACE = '[ \t\n]';
- const STRING = {
- scope: 'string',
- variants: [
- DOUBLE_QUOTED,
- SINGLE_QUOTED,
- HEREDOC,
- NOWDOC
- ]
- };
- const NUMBER = {
- scope: 'number',
- variants: [
- { begin: `\\b0[bB][01]+(?:_[01]+)*\\b` }, // Binary w/ underscore support
- { begin: `\\b0[oO][0-7]+(?:_[0-7]+)*\\b` }, // Octals w/ underscore support
- { begin: `\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b` }, // Hex w/ underscore support
- // Decimals w/ underscore support, with optional fragments and scientific exponent (e) suffix.
- { begin: `(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?` }
- ],
- relevance: 0
- };
- const LITERALS = [
- "false",
- "null",
- "true"
- ];
- const KWS = [
- // Magic constants:
- // <https://www.php.net/manual/en/language.constants.predefined.php>
- "__CLASS__",
- "__DIR__",
- "__FILE__",
- "__FUNCTION__",
- "__COMPILER_HALT_OFFSET__",
- "__LINE__",
- "__METHOD__",
- "__NAMESPACE__",
- "__TRAIT__",
- // Function that look like language construct or language construct that look like function:
- // List of keywords that may not require parenthesis
- "die",
- "echo",
- "exit",
- "include",
- "include_once",
- "print",
- "require",
- "require_once",
- // These are not language construct (function) but operate on the currently-executing function and can access the current symbol table
- // 'compact extract func_get_arg func_get_args func_num_args get_called_class get_parent_class ' +
- // Other keywords:
- // <https://www.php.net/manual/en/reserved.php>
- // <https://www.php.net/manual/en/language.types.type-juggling.php>
- "array",
- "abstract",
- "and",
- "as",
- "binary",
- "bool",
- "boolean",
- "break",
- "callable",
- "case",
- "catch",
- "class",
- "clone",
- "const",
- "continue",
- "declare",
- "default",
- "do",
- "double",
- "else",
- "elseif",
- "empty",
- "enddeclare",
- "endfor",
- "endforeach",
- "endif",
- "endswitch",
- "endwhile",
- "enum",
- "eval",
- "extends",
- "final",
- "finally",
- "float",
- "for",
- "foreach",
- "from",
- "global",
- "goto",
- "if",
- "implements",
- "instanceof",
- "insteadof",
- "int",
- "integer",
- "interface",
- "isset",
- "iterable",
- "list",
- "match|0",
- "mixed",
- "new",
- "never",
- "object",
- "or",
- "private",
- "protected",
- "public",
- "readonly",
- "real",
- "return",
- "string",
- "switch",
- "throw",
- "trait",
- "try",
- "unset",
- "use",
- "var",
- "void",
- "while",
- "xor",
- "yield"
- ];
- const BUILT_INS = [
- // Standard PHP library:
- // <https://www.php.net/manual/en/book.spl.php>
- "Error|0",
- "AppendIterator",
- "ArgumentCountError",
- "ArithmeticError",
- "ArrayIterator",
- "ArrayObject",
- "AssertionError",
- "BadFunctionCallException",
- "BadMethodCallException",
- "CachingIterator",
- "CallbackFilterIterator",
- "CompileError",
- "Countable",
- "DirectoryIterator",
- "DivisionByZeroError",
- "DomainException",
- "EmptyIterator",
- "ErrorException",
- "Exception",
- "FilesystemIterator",
- "FilterIterator",
- "GlobIterator",
- "InfiniteIterator",
- "InvalidArgumentException",
- "IteratorIterator",
- "LengthException",
- "LimitIterator",
- "LogicException",
- "MultipleIterator",
- "NoRewindIterator",
- "OutOfBoundsException",
- "OutOfRangeException",
- "OuterIterator",
- "OverflowException",
- "ParentIterator",
- "ParseError",
- "RangeException",
- "RecursiveArrayIterator",
- "RecursiveCachingIterator",
- "RecursiveCallbackFilterIterator",
- "RecursiveDirectoryIterator",
- "RecursiveFilterIterator",
- "RecursiveIterator",
- "RecursiveIteratorIterator",
- "RecursiveRegexIterator",
- "RecursiveTreeIterator",
- "RegexIterator",
- "RuntimeException",
- "SeekableIterator",
- "SplDoublyLinkedList",
- "SplFileInfo",
- "SplFileObject",
- "SplFixedArray",
- "SplHeap",
- "SplMaxHeap",
- "SplMinHeap",
- "SplObjectStorage",
- "SplObserver",
- "SplPriorityQueue",
- "SplQueue",
- "SplStack",
- "SplSubject",
- "SplTempFileObject",
- "TypeError",
- "UnderflowException",
- "UnexpectedValueException",
- "UnhandledMatchError",
- // Reserved interfaces:
- // <https://www.php.net/manual/en/reserved.interfaces.php>
- "ArrayAccess",
- "BackedEnum",
- "Closure",
- "Fiber",
- "Generator",
- "Iterator",
- "IteratorAggregate",
- "Serializable",
- "Stringable",
- "Throwable",
- "Traversable",
- "UnitEnum",
- "WeakReference",
- "WeakMap",
- // Reserved classes:
- // <https://www.php.net/manual/en/reserved.classes.php>
- "Directory",
- "__PHP_Incomplete_Class",
- "parent",
- "php_user_filter",
- "self",
- "static",
- "stdClass"
- ];
- /** Dual-case keywords
- *
- * ["then","FILE"] =>
- * ["then", "THEN", "FILE", "file"]
- *
- * @param {string[]} items */
- const dualCase = (items) => {
- /** @type string[] */
- const result = [];
- items.forEach(item => {
- result.push(item);
- if (item.toLowerCase() === item) {
- result.push(item.toUpperCase());
- } else {
- result.push(item.toLowerCase());
- }
- });
- return result;
- };
- const KEYWORDS = {
- keyword: KWS,
- literal: dualCase(LITERALS),
- built_in: BUILT_INS,
- };
- /**
- * @param {string[]} items */
- const normalizeKeywords = (items) => {
- return items.map(item => {
- return item.replace(/\|\d+$/, "");
- });
- };
- const CONSTRUCTOR_CALL = { variants: [
- {
- match: [
- /new/,
- regex.concat(WHITESPACE, "+"),
- // to prevent built ins from being confused as the class constructor call
- regex.concat("(?!", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
- PASCAL_CASE_CLASS_NAME_RE,
- ],
- scope: {
- 1: "keyword",
- 4: "title.class",
- },
- }
- ] };
- const CONSTANT_REFERENCE = regex.concat(IDENT_RE, "\\b(?!\\()");
- const LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON = { variants: [
- {
- match: [
- regex.concat(
- /::/,
- regex.lookahead(/(?!class\b)/)
- ),
- CONSTANT_REFERENCE,
- ],
- scope: { 2: "variable.constant", },
- },
- {
- match: [
- /::/,
- /class/,
- ],
- scope: { 2: "variable.language", },
- },
- {
- match: [
- PASCAL_CASE_CLASS_NAME_RE,
- regex.concat(
- /::/,
- regex.lookahead(/(?!class\b)/)
- ),
- CONSTANT_REFERENCE,
- ],
- scope: {
- 1: "title.class",
- 3: "variable.constant",
- },
- },
- {
- match: [
- PASCAL_CASE_CLASS_NAME_RE,
- regex.concat(
- "::",
- regex.lookahead(/(?!class\b)/)
- ),
- ],
- scope: { 1: "title.class", },
- },
- {
- match: [
- PASCAL_CASE_CLASS_NAME_RE,
- /::/,
- /class/,
- ],
- scope: {
- 1: "title.class",
- 3: "variable.language",
- },
- }
- ] };
- const NAMED_ARGUMENT = {
- scope: 'attr',
- match: regex.concat(IDENT_RE, regex.lookahead(':'), regex.lookahead(/(?!::)/)),
- };
- const PARAMS_MODE = {
- relevance: 0,
- begin: /\(/,
- end: /\)/,
- keywords: KEYWORDS,
- contains: [
- NAMED_ARGUMENT,
- VARIABLE,
- LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
- hljs.C_BLOCK_COMMENT_MODE,
- STRING,
- NUMBER,
- CONSTRUCTOR_CALL,
- ],
- };
- const FUNCTION_INVOKE = {
- relevance: 0,
- match: [
- /\b/,
- // to prevent keywords from being confused as the function title
- regex.concat("(?!fn\\b|function\\b|", normalizeKeywords(KWS).join("\\b|"), "|", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
- IDENT_RE,
- regex.concat(WHITESPACE, "*"),
- regex.lookahead(/(?=\()/)
- ],
- scope: { 3: "title.function.invoke", },
- contains: [ PARAMS_MODE ]
- };
- PARAMS_MODE.contains.push(FUNCTION_INVOKE);
- const ATTRIBUTE_CONTAINS = [
- NAMED_ARGUMENT,
- LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
- hljs.C_BLOCK_COMMENT_MODE,
- STRING,
- NUMBER,
- CONSTRUCTOR_CALL,
- ];
- const ATTRIBUTES = {
- begin: regex.concat(/#\[\s*/, PASCAL_CASE_CLASS_NAME_RE),
- beginScope: "meta",
- end: /]/,
- endScope: "meta",
- keywords: {
- literal: LITERALS,
- keyword: [
- 'new',
- 'array',
- ]
- },
- contains: [
- {
- begin: /\[/,
- end: /]/,
- keywords: {
- literal: LITERALS,
- keyword: [
- 'new',
- 'array',
- ]
- },
- contains: [
- 'self',
- ...ATTRIBUTE_CONTAINS,
- ]
- },
- ...ATTRIBUTE_CONTAINS,
- {
- scope: 'meta',
- match: PASCAL_CASE_CLASS_NAME_RE
- }
- ]
- };
- return {
- case_insensitive: false,
- keywords: KEYWORDS,
- contains: [
- ATTRIBUTES,
- hljs.HASH_COMMENT_MODE,
- hljs.COMMENT('//', '$'),
- hljs.COMMENT(
- '/\\*',
- '\\*/',
- { contains: [
- {
- scope: 'doctag',
- match: '@[A-Za-z]+'
- }
- ] }
- ),
- {
- match: /__halt_compiler\(\);/,
- keywords: '__halt_compiler',
- starts: {
- scope: "comment",
- end: hljs.MATCH_NOTHING_RE,
- contains: [
- {
- match: /\?>/,
- scope: "meta",
- endsParent: true
- }
- ]
- }
- },
- PREPROCESSOR,
- {
- scope: 'variable.language',
- match: /\$this\b/
- },
- VARIABLE,
- FUNCTION_INVOKE,
- LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
- {
- match: [
- /const/,
- /\s/,
- IDENT_RE,
- ],
- scope: {
- 1: "keyword",
- 3: "variable.constant",
- },
- },
- CONSTRUCTOR_CALL,
- {
- scope: 'function',
- relevance: 0,
- beginKeywords: 'fn function',
- end: /[;{]/,
- excludeEnd: true,
- illegal: '[$%\\[]',
- contains: [
- { beginKeywords: 'use', },
- hljs.UNDERSCORE_TITLE_MODE,
- {
- begin: '=>', // No markup, just a relevance booster
- endsParent: true
- },
- {
- scope: 'params',
- begin: '\\(',
- end: '\\)',
- excludeBegin: true,
- excludeEnd: true,
- keywords: KEYWORDS,
- contains: [
- 'self',
- VARIABLE,
- LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
- hljs.C_BLOCK_COMMENT_MODE,
- STRING,
- NUMBER
- ]
- },
- ]
- },
- {
- scope: 'class',
- variants: [
- {
- beginKeywords: "enum",
- illegal: /[($"]/
- },
- {
- beginKeywords: "class interface trait",
- illegal: /[:($"]/
- }
- ],
- relevance: 0,
- end: /\{/,
- excludeEnd: true,
- contains: [
- { beginKeywords: 'extends implements' },
- hljs.UNDERSCORE_TITLE_MODE
- ]
- },
- // both use and namespace still use "old style" rules (vs multi-match)
- // because the namespace name can include `\` and we still want each
- // element to be treated as its own *individual* title
- {
- beginKeywords: 'namespace',
- relevance: 0,
- end: ';',
- illegal: /[.']/,
- contains: [ hljs.inherit(hljs.UNDERSCORE_TITLE_MODE, { scope: "title.class" }) ]
- },
- {
- beginKeywords: 'use',
- relevance: 0,
- end: ';',
- contains: [
- // TODO: title.function vs title.class
- {
- match: /\b(as|const|function)\b/,
- scope: "keyword"
- },
- // TODO: could be title.class or title.function
- hljs.UNDERSCORE_TITLE_MODE
- ]
- },
- STRING,
- NUMBER,
- ]
- };
- }
- export { php as default };
|