php.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. /*
  2. Language: PHP
  3. Author: Victor Karamzin <Victor.Karamzin@enterra-inc.com>
  4. Contributors: Evgeny Stepanischev <imbolk@gmail.com>, Ivan Sagalaev <maniac@softwaremaniacs.org>
  5. Website: https://www.php.net
  6. Category: common
  7. */
  8. /**
  9. * @param {HLJSApi} hljs
  10. * @returns {LanguageDetail}
  11. * */
  12. function php(hljs) {
  13. const regex = hljs.regex;
  14. // negative look-ahead tries to avoid matching patterns that are not
  15. // Perl at all like $ident$, @ident@, etc.
  16. const NOT_PERL_ETC = /(?![A-Za-z0-9])(?![$])/;
  17. const IDENT_RE = regex.concat(
  18. /[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,
  19. NOT_PERL_ETC);
  20. // Will not detect camelCase classes
  21. const PASCAL_CASE_CLASS_NAME_RE = regex.concat(
  22. /(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,
  23. NOT_PERL_ETC);
  24. const VARIABLE = {
  25. scope: 'variable',
  26. match: '\\$+' + IDENT_RE,
  27. };
  28. const PREPROCESSOR = {
  29. scope: 'meta',
  30. variants: [
  31. { begin: /<\?php/, relevance: 10 }, // boost for obvious PHP
  32. { begin: /<\?=/ },
  33. // less relevant per PSR-1 which says not to use short-tags
  34. { begin: /<\?/, relevance: 0.1 },
  35. { begin: /\?>/ } // end php tag
  36. ]
  37. };
  38. const SUBST = {
  39. scope: 'subst',
  40. variants: [
  41. { begin: /\$\w+/ },
  42. {
  43. begin: /\{\$/,
  44. end: /\}/
  45. }
  46. ]
  47. };
  48. const SINGLE_QUOTED = hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null, });
  49. const DOUBLE_QUOTED = hljs.inherit(hljs.QUOTE_STRING_MODE, {
  50. illegal: null,
  51. contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
  52. });
  53. const HEREDOC = hljs.END_SAME_AS_BEGIN({
  54. begin: /<<<[ \t]*(\w+)\n/,
  55. end: /[ \t]*(\w+)\b/,
  56. contains: hljs.QUOTE_STRING_MODE.contains.concat(SUBST),
  57. });
  58. // list of valid whitespaces because non-breaking space might be part of a IDENT_RE
  59. const WHITESPACE = '[ \t\n]';
  60. const STRING = {
  61. scope: 'string',
  62. variants: [
  63. DOUBLE_QUOTED,
  64. SINGLE_QUOTED,
  65. HEREDOC
  66. ]
  67. };
  68. const NUMBER = {
  69. scope: 'number',
  70. variants: [
  71. { begin: `\\b0[bB][01]+(?:_[01]+)*\\b` }, // Binary w/ underscore support
  72. { begin: `\\b0[oO][0-7]+(?:_[0-7]+)*\\b` }, // Octals w/ underscore support
  73. { begin: `\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b` }, // Hex w/ underscore support
  74. // Decimals w/ underscore support, with optional fragments and scientific exponent (e) suffix.
  75. { begin: `(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?` }
  76. ],
  77. relevance: 0
  78. };
  79. const LITERALS = [
  80. "false",
  81. "null",
  82. "true"
  83. ];
  84. const KWS = [
  85. // Magic constants:
  86. // <https://www.php.net/manual/en/language.constants.predefined.php>
  87. "__CLASS__",
  88. "__DIR__",
  89. "__FILE__",
  90. "__FUNCTION__",
  91. "__COMPILER_HALT_OFFSET__",
  92. "__LINE__",
  93. "__METHOD__",
  94. "__NAMESPACE__",
  95. "__TRAIT__",
  96. // Function that look like language construct or language construct that look like function:
  97. // List of keywords that may not require parenthesis
  98. "die",
  99. "echo",
  100. "exit",
  101. "include",
  102. "include_once",
  103. "print",
  104. "require",
  105. "require_once",
  106. // These are not language construct (function) but operate on the currently-executing function and can access the current symbol table
  107. // 'compact extract func_get_arg func_get_args func_num_args get_called_class get_parent_class ' +
  108. // Other keywords:
  109. // <https://www.php.net/manual/en/reserved.php>
  110. // <https://www.php.net/manual/en/language.types.type-juggling.php>
  111. "array",
  112. "abstract",
  113. "and",
  114. "as",
  115. "binary",
  116. "bool",
  117. "boolean",
  118. "break",
  119. "callable",
  120. "case",
  121. "catch",
  122. "class",
  123. "clone",
  124. "const",
  125. "continue",
  126. "declare",
  127. "default",
  128. "do",
  129. "double",
  130. "else",
  131. "elseif",
  132. "empty",
  133. "enddeclare",
  134. "endfor",
  135. "endforeach",
  136. "endif",
  137. "endswitch",
  138. "endwhile",
  139. "enum",
  140. "eval",
  141. "extends",
  142. "final",
  143. "finally",
  144. "float",
  145. "for",
  146. "foreach",
  147. "from",
  148. "global",
  149. "goto",
  150. "if",
  151. "implements",
  152. "instanceof",
  153. "insteadof",
  154. "int",
  155. "integer",
  156. "interface",
  157. "isset",
  158. "iterable",
  159. "list",
  160. "match|0",
  161. "mixed",
  162. "new",
  163. "never",
  164. "object",
  165. "or",
  166. "private",
  167. "protected",
  168. "public",
  169. "readonly",
  170. "real",
  171. "return",
  172. "string",
  173. "switch",
  174. "throw",
  175. "trait",
  176. "try",
  177. "unset",
  178. "use",
  179. "var",
  180. "void",
  181. "while",
  182. "xor",
  183. "yield"
  184. ];
  185. const BUILT_INS = [
  186. // Standard PHP library:
  187. // <https://www.php.net/manual/en/book.spl.php>
  188. "Error|0",
  189. "AppendIterator",
  190. "ArgumentCountError",
  191. "ArithmeticError",
  192. "ArrayIterator",
  193. "ArrayObject",
  194. "AssertionError",
  195. "BadFunctionCallException",
  196. "BadMethodCallException",
  197. "CachingIterator",
  198. "CallbackFilterIterator",
  199. "CompileError",
  200. "Countable",
  201. "DirectoryIterator",
  202. "DivisionByZeroError",
  203. "DomainException",
  204. "EmptyIterator",
  205. "ErrorException",
  206. "Exception",
  207. "FilesystemIterator",
  208. "FilterIterator",
  209. "GlobIterator",
  210. "InfiniteIterator",
  211. "InvalidArgumentException",
  212. "IteratorIterator",
  213. "LengthException",
  214. "LimitIterator",
  215. "LogicException",
  216. "MultipleIterator",
  217. "NoRewindIterator",
  218. "OutOfBoundsException",
  219. "OutOfRangeException",
  220. "OuterIterator",
  221. "OverflowException",
  222. "ParentIterator",
  223. "ParseError",
  224. "RangeException",
  225. "RecursiveArrayIterator",
  226. "RecursiveCachingIterator",
  227. "RecursiveCallbackFilterIterator",
  228. "RecursiveDirectoryIterator",
  229. "RecursiveFilterIterator",
  230. "RecursiveIterator",
  231. "RecursiveIteratorIterator",
  232. "RecursiveRegexIterator",
  233. "RecursiveTreeIterator",
  234. "RegexIterator",
  235. "RuntimeException",
  236. "SeekableIterator",
  237. "SplDoublyLinkedList",
  238. "SplFileInfo",
  239. "SplFileObject",
  240. "SplFixedArray",
  241. "SplHeap",
  242. "SplMaxHeap",
  243. "SplMinHeap",
  244. "SplObjectStorage",
  245. "SplObserver",
  246. "SplPriorityQueue",
  247. "SplQueue",
  248. "SplStack",
  249. "SplSubject",
  250. "SplTempFileObject",
  251. "TypeError",
  252. "UnderflowException",
  253. "UnexpectedValueException",
  254. "UnhandledMatchError",
  255. // Reserved interfaces:
  256. // <https://www.php.net/manual/en/reserved.interfaces.php>
  257. "ArrayAccess",
  258. "BackedEnum",
  259. "Closure",
  260. "Fiber",
  261. "Generator",
  262. "Iterator",
  263. "IteratorAggregate",
  264. "Serializable",
  265. "Stringable",
  266. "Throwable",
  267. "Traversable",
  268. "UnitEnum",
  269. "WeakReference",
  270. "WeakMap",
  271. // Reserved classes:
  272. // <https://www.php.net/manual/en/reserved.classes.php>
  273. "Directory",
  274. "__PHP_Incomplete_Class",
  275. "parent",
  276. "php_user_filter",
  277. "self",
  278. "static",
  279. "stdClass"
  280. ];
  281. /** Dual-case keywords
  282. *
  283. * ["then","FILE"] =>
  284. * ["then", "THEN", "FILE", "file"]
  285. *
  286. * @param {string[]} items */
  287. const dualCase = (items) => {
  288. /** @type string[] */
  289. const result = [];
  290. items.forEach(item => {
  291. result.push(item);
  292. if (item.toLowerCase() === item) {
  293. result.push(item.toUpperCase());
  294. } else {
  295. result.push(item.toLowerCase());
  296. }
  297. });
  298. return result;
  299. };
  300. const KEYWORDS = {
  301. keyword: KWS,
  302. literal: dualCase(LITERALS),
  303. built_in: BUILT_INS,
  304. };
  305. /**
  306. * @param {string[]} items */
  307. const normalizeKeywords = (items) => {
  308. return items.map(item => {
  309. return item.replace(/\|\d+$/, "");
  310. });
  311. };
  312. const CONSTRUCTOR_CALL = { variants: [
  313. {
  314. match: [
  315. /new/,
  316. regex.concat(WHITESPACE, "+"),
  317. // to prevent built ins from being confused as the class constructor call
  318. regex.concat("(?!", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
  319. PASCAL_CASE_CLASS_NAME_RE,
  320. ],
  321. scope: {
  322. 1: "keyword",
  323. 4: "title.class",
  324. },
  325. }
  326. ] };
  327. const CONSTANT_REFERENCE = regex.concat(IDENT_RE, "\\b(?!\\()");
  328. const LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON = { variants: [
  329. {
  330. match: [
  331. regex.concat(
  332. /::/,
  333. regex.lookahead(/(?!class\b)/)
  334. ),
  335. CONSTANT_REFERENCE,
  336. ],
  337. scope: { 2: "variable.constant", },
  338. },
  339. {
  340. match: [
  341. /::/,
  342. /class/,
  343. ],
  344. scope: { 2: "variable.language", },
  345. },
  346. {
  347. match: [
  348. PASCAL_CASE_CLASS_NAME_RE,
  349. regex.concat(
  350. /::/,
  351. regex.lookahead(/(?!class\b)/)
  352. ),
  353. CONSTANT_REFERENCE,
  354. ],
  355. scope: {
  356. 1: "title.class",
  357. 3: "variable.constant",
  358. },
  359. },
  360. {
  361. match: [
  362. PASCAL_CASE_CLASS_NAME_RE,
  363. regex.concat(
  364. "::",
  365. regex.lookahead(/(?!class\b)/)
  366. ),
  367. ],
  368. scope: { 1: "title.class", },
  369. },
  370. {
  371. match: [
  372. PASCAL_CASE_CLASS_NAME_RE,
  373. /::/,
  374. /class/,
  375. ],
  376. scope: {
  377. 1: "title.class",
  378. 3: "variable.language",
  379. },
  380. }
  381. ] };
  382. const NAMED_ARGUMENT = {
  383. scope: 'attr',
  384. match: regex.concat(IDENT_RE, regex.lookahead(':'), regex.lookahead(/(?!::)/)),
  385. };
  386. const PARAMS_MODE = {
  387. relevance: 0,
  388. begin: /\(/,
  389. end: /\)/,
  390. keywords: KEYWORDS,
  391. contains: [
  392. NAMED_ARGUMENT,
  393. VARIABLE,
  394. LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
  395. hljs.C_BLOCK_COMMENT_MODE,
  396. STRING,
  397. NUMBER,
  398. CONSTRUCTOR_CALL,
  399. ],
  400. };
  401. const FUNCTION_INVOKE = {
  402. relevance: 0,
  403. match: [
  404. /\b/,
  405. // to prevent keywords from being confused as the function title
  406. regex.concat("(?!fn\\b|function\\b|", normalizeKeywords(KWS).join("\\b|"), "|", normalizeKeywords(BUILT_INS).join("\\b|"), "\\b)"),
  407. IDENT_RE,
  408. regex.concat(WHITESPACE, "*"),
  409. regex.lookahead(/(?=\()/)
  410. ],
  411. scope: { 3: "title.function.invoke", },
  412. contains: [ PARAMS_MODE ]
  413. };
  414. PARAMS_MODE.contains.push(FUNCTION_INVOKE);
  415. const ATTRIBUTE_CONTAINS = [
  416. NAMED_ARGUMENT,
  417. LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
  418. hljs.C_BLOCK_COMMENT_MODE,
  419. STRING,
  420. NUMBER,
  421. CONSTRUCTOR_CALL,
  422. ];
  423. const ATTRIBUTES = {
  424. begin: regex.concat(/#\[\s*/, PASCAL_CASE_CLASS_NAME_RE),
  425. beginScope: "meta",
  426. end: /]/,
  427. endScope: "meta",
  428. keywords: {
  429. literal: LITERALS,
  430. keyword: [
  431. 'new',
  432. 'array',
  433. ]
  434. },
  435. contains: [
  436. {
  437. begin: /\[/,
  438. end: /]/,
  439. keywords: {
  440. literal: LITERALS,
  441. keyword: [
  442. 'new',
  443. 'array',
  444. ]
  445. },
  446. contains: [
  447. 'self',
  448. ...ATTRIBUTE_CONTAINS,
  449. ]
  450. },
  451. ...ATTRIBUTE_CONTAINS,
  452. {
  453. scope: 'meta',
  454. match: PASCAL_CASE_CLASS_NAME_RE
  455. }
  456. ]
  457. };
  458. return {
  459. case_insensitive: false,
  460. keywords: KEYWORDS,
  461. contains: [
  462. ATTRIBUTES,
  463. hljs.HASH_COMMENT_MODE,
  464. hljs.COMMENT('//', '$'),
  465. hljs.COMMENT(
  466. '/\\*',
  467. '\\*/',
  468. { contains: [
  469. {
  470. scope: 'doctag',
  471. match: '@[A-Za-z]+'
  472. }
  473. ] }
  474. ),
  475. {
  476. match: /__halt_compiler\(\);/,
  477. keywords: '__halt_compiler',
  478. starts: {
  479. scope: "comment",
  480. end: hljs.MATCH_NOTHING_RE,
  481. contains: [
  482. {
  483. match: /\?>/,
  484. scope: "meta",
  485. endsParent: true
  486. }
  487. ]
  488. }
  489. },
  490. PREPROCESSOR,
  491. {
  492. scope: 'variable.language',
  493. match: /\$this\b/
  494. },
  495. VARIABLE,
  496. FUNCTION_INVOKE,
  497. LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
  498. {
  499. match: [
  500. /const/,
  501. /\s/,
  502. IDENT_RE,
  503. ],
  504. scope: {
  505. 1: "keyword",
  506. 3: "variable.constant",
  507. },
  508. },
  509. CONSTRUCTOR_CALL,
  510. {
  511. scope: 'function',
  512. relevance: 0,
  513. beginKeywords: 'fn function',
  514. end: /[;{]/,
  515. excludeEnd: true,
  516. illegal: '[$%\\[]',
  517. contains: [
  518. { beginKeywords: 'use', },
  519. hljs.UNDERSCORE_TITLE_MODE,
  520. {
  521. begin: '=>', // No markup, just a relevance booster
  522. endsParent: true
  523. },
  524. {
  525. scope: 'params',
  526. begin: '\\(',
  527. end: '\\)',
  528. excludeBegin: true,
  529. excludeEnd: true,
  530. keywords: KEYWORDS,
  531. contains: [
  532. 'self',
  533. VARIABLE,
  534. LEFT_AND_RIGHT_SIDE_OF_DOUBLE_COLON,
  535. hljs.C_BLOCK_COMMENT_MODE,
  536. STRING,
  537. NUMBER
  538. ]
  539. },
  540. ]
  541. },
  542. {
  543. scope: 'class',
  544. variants: [
  545. {
  546. beginKeywords: "enum",
  547. illegal: /[($"]/
  548. },
  549. {
  550. beginKeywords: "class interface trait",
  551. illegal: /[:($"]/
  552. }
  553. ],
  554. relevance: 0,
  555. end: /\{/,
  556. excludeEnd: true,
  557. contains: [
  558. { beginKeywords: 'extends implements' },
  559. hljs.UNDERSCORE_TITLE_MODE
  560. ]
  561. },
  562. // both use and namespace still use "old style" rules (vs multi-match)
  563. // because the namespace name can include `\` and we still want each
  564. // element to be treated as its own *individual* title
  565. {
  566. beginKeywords: 'namespace',
  567. relevance: 0,
  568. end: ';',
  569. illegal: /[.']/,
  570. contains: [ hljs.inherit(hljs.UNDERSCORE_TITLE_MODE, { scope: "title.class" }) ]
  571. },
  572. {
  573. beginKeywords: 'use',
  574. relevance: 0,
  575. end: ';',
  576. contains: [
  577. // TODO: title.function vs title.class
  578. {
  579. match: /\b(as|const|function)\b/,
  580. scope: "keyword"
  581. },
  582. // TODO: could be title.class or title.function
  583. hljs.UNDERSCORE_TITLE_MODE
  584. ]
  585. },
  586. STRING,
  587. NUMBER,
  588. ]
  589. };
  590. }
  591. module.exports = php;