PageRenderTime 41ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Util/Common.php

http://github.com/squizlabs/PHP_CodeSniffer
PHP | 570 lines | 333 code | 85 blank | 152 comment | 68 complexity | 96f73d9993a5510a6a00b253fdb5a552 MD5 | raw file
  1. <?php
  2. /**
  3. * Basic util functions.
  4. *
  5. * @author Greg Sherwood <gsherwood@squiz.net>
  6. * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  7. * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  8. */
  9. namespace PHP_CodeSniffer\Util;
  10. class Common
  11. {
  12. /**
  13. * An array of variable types for param/var we will check.
  14. *
  15. * @var string[]
  16. */
  17. public static $allowedTypes = [
  18. 'array',
  19. 'boolean',
  20. 'float',
  21. 'integer',
  22. 'mixed',
  23. 'object',
  24. 'string',
  25. 'resource',
  26. 'callable',
  27. ];
  28. /**
  29. * Return TRUE if the path is a PHAR file.
  30. *
  31. * @param string $path The path to use.
  32. *
  33. * @return mixed
  34. */
  35. public static function isPharFile($path)
  36. {
  37. if (strpos($path, 'phar://') === 0) {
  38. return true;
  39. }
  40. return false;
  41. }//end isPharFile()
  42. /**
  43. * Checks if a file is readable.
  44. *
  45. * Addresses PHP bug related to reading files from network drives on Windows.
  46. * e.g. when using WSL2.
  47. *
  48. * @param string $path The path to the file.
  49. *
  50. * @return boolean
  51. */
  52. public static function isReadable($path)
  53. {
  54. if (@is_readable($path) === true) {
  55. return true;
  56. }
  57. if (@file_exists($path) === true && @is_file($path) === true) {
  58. $f = @fopen($path, 'rb');
  59. if (fclose($f) === true) {
  60. return true;
  61. }
  62. }
  63. return false;
  64. }//end isReadable()
  65. /**
  66. * CodeSniffer alternative for realpath.
  67. *
  68. * Allows for PHAR support.
  69. *
  70. * @param string $path The path to use.
  71. *
  72. * @return mixed
  73. */
  74. public static function realpath($path)
  75. {
  76. // Support the path replacement of ~ with the user's home directory.
  77. if (substr($path, 0, 2) === '~/') {
  78. $homeDir = getenv('HOME');
  79. if ($homeDir !== false) {
  80. $path = $homeDir.substr($path, 1);
  81. }
  82. }
  83. // Check for process substitution.
  84. if (strpos($path, '/dev/fd') === 0) {
  85. return str_replace('/dev/fd', 'php://fd', $path);
  86. }
  87. // No extra work needed if this is not a phar file.
  88. if (self::isPharFile($path) === false) {
  89. return realpath($path);
  90. }
  91. // Before trying to break down the file path,
  92. // check if it exists first because it will mostly not
  93. // change after running the below code.
  94. if (file_exists($path) === true) {
  95. return $path;
  96. }
  97. $phar = \Phar::running(false);
  98. $extra = str_replace('phar://'.$phar, '', $path);
  99. $path = realpath($phar);
  100. if ($path === false) {
  101. return false;
  102. }
  103. $path = 'phar://'.$path.$extra;
  104. if (file_exists($path) === true) {
  105. return $path;
  106. }
  107. return false;
  108. }//end realpath()
  109. /**
  110. * Removes a base path from the front of a file path.
  111. *
  112. * @param string $path The path of the file.
  113. * @param string $basepath The base path to remove. This should not end
  114. * with a directory separator.
  115. *
  116. * @return string
  117. */
  118. public static function stripBasepath($path, $basepath)
  119. {
  120. if (empty($basepath) === true) {
  121. return $path;
  122. }
  123. $basepathLen = strlen($basepath);
  124. if (substr($path, 0, $basepathLen) === $basepath) {
  125. $path = substr($path, $basepathLen);
  126. }
  127. $path = ltrim($path, DIRECTORY_SEPARATOR);
  128. if ($path === '') {
  129. $path = '.';
  130. }
  131. return $path;
  132. }//end stripBasepath()
  133. /**
  134. * Detects the EOL character being used in a string.
  135. *
  136. * @param string $contents The contents to check.
  137. *
  138. * @return string
  139. */
  140. public static function detectLineEndings($contents)
  141. {
  142. if (preg_match("/\r\n?|\n/", $contents, $matches) !== 1) {
  143. // Assume there are no newlines.
  144. $eolChar = "\n";
  145. } else {
  146. $eolChar = $matches[0];
  147. }
  148. return $eolChar;
  149. }//end detectLineEndings()
  150. /**
  151. * Check if STDIN is a TTY.
  152. *
  153. * @return boolean
  154. */
  155. public static function isStdinATTY()
  156. {
  157. // The check is slow (especially calling `tty`) so we static
  158. // cache the result.
  159. static $isTTY = null;
  160. if ($isTTY !== null) {
  161. return $isTTY;
  162. }
  163. if (defined('STDIN') === false) {
  164. return false;
  165. }
  166. // If PHP has the POSIX extensions we will use them.
  167. if (function_exists('posix_isatty') === true) {
  168. $isTTY = (posix_isatty(STDIN) === true);
  169. return $isTTY;
  170. }
  171. // Next try is detecting whether we have `tty` installed and use that.
  172. if (defined('PHP_WINDOWS_VERSION_PLATFORM') === true) {
  173. $devnull = 'NUL';
  174. $which = 'where';
  175. } else {
  176. $devnull = '/dev/null';
  177. $which = 'which';
  178. }
  179. $tty = trim(shell_exec("$which tty 2> $devnull"));
  180. if (empty($tty) === false) {
  181. exec("tty -s 2> $devnull", $output, $returnValue);
  182. $isTTY = ($returnValue === 0);
  183. return $isTTY;
  184. }
  185. // Finally we will use fstat. The solution borrowed from
  186. // https://stackoverflow.com/questions/11327367/detect-if-a-php-script-is-being-run-interactively-or-not
  187. // This doesn't work on Mingw/Cygwin/... using Mintty but they
  188. // have `tty` installed.
  189. $type = [
  190. 'S_IFMT' => 0170000,
  191. 'S_IFIFO' => 0010000,
  192. ];
  193. $stat = fstat(STDIN);
  194. $mode = ($stat['mode'] & $type['S_IFMT']);
  195. $isTTY = ($mode !== $type['S_IFIFO']);
  196. return $isTTY;
  197. }//end isStdinATTY()
  198. /**
  199. * Escape a path to a system command.
  200. *
  201. * @param string $cmd The path to the system command.
  202. *
  203. * @return string
  204. */
  205. public static function escapeshellcmd($cmd)
  206. {
  207. $cmd = escapeshellcmd($cmd);
  208. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  209. // Spaces are not escaped by escapeshellcmd on Windows, but need to be
  210. // for the command to be able to execute.
  211. $cmd = preg_replace('`(?<!^) `', '^ ', $cmd);
  212. }
  213. return $cmd;
  214. }//end escapeshellcmd()
  215. /**
  216. * Prepares token content for output to screen.
  217. *
  218. * Replaces invisible characters so they are visible. On non-Windows
  219. * operating systems it will also colour the invisible characters.
  220. *
  221. * @param string $content The content to prepare.
  222. * @param string[] $exclude A list of characters to leave invisible.
  223. * Can contain \r, \n, \t and a space.
  224. *
  225. * @return string
  226. */
  227. public static function prepareForOutput($content, $exclude=[])
  228. {
  229. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  230. if (in_array("\r", $exclude, true) === false) {
  231. $content = str_replace("\r", '\r', $content);
  232. }
  233. if (in_array("\n", $exclude, true) === false) {
  234. $content = str_replace("\n", '\n', $content);
  235. }
  236. if (in_array("\t", $exclude, true) === false) {
  237. $content = str_replace("\t", '\t', $content);
  238. }
  239. } else {
  240. if (in_array("\r", $exclude, true) === false) {
  241. $content = str_replace("\r", "\033[30;1m\\r\033[0m", $content);
  242. }
  243. if (in_array("\n", $exclude, true) === false) {
  244. $content = str_replace("\n", "\033[30;1m\\n\033[0m", $content);
  245. }
  246. if (in_array("\t", $exclude, true) === false) {
  247. $content = str_replace("\t", "\033[30;1m\\t\033[0m", $content);
  248. }
  249. if (in_array(' ', $exclude, true) === false) {
  250. $content = str_replace(' ', "\033[30;1m·\033[0m", $content);
  251. }
  252. }//end if
  253. return $content;
  254. }//end prepareForOutput()
  255. /**
  256. * Returns true if the specified string is in the camel caps format.
  257. *
  258. * @param string $string The string the verify.
  259. * @param boolean $classFormat If true, check to see if the string is in the
  260. * class format. Class format strings must start
  261. * with a capital letter and contain no
  262. * underscores.
  263. * @param boolean $public If true, the first character in the string
  264. * must be an a-z character. If false, the
  265. * character must be an underscore. This
  266. * argument is only applicable if $classFormat
  267. * is false.
  268. * @param boolean $strict If true, the string must not have two capital
  269. * letters next to each other. If false, a
  270. * relaxed camel caps policy is used to allow
  271. * for acronyms.
  272. *
  273. * @return boolean
  274. */
  275. public static function isCamelCaps(
  276. $string,
  277. $classFormat=false,
  278. $public=true,
  279. $strict=true
  280. ) {
  281. // Check the first character first.
  282. if ($classFormat === false) {
  283. $legalFirstChar = '';
  284. if ($public === false) {
  285. $legalFirstChar = '[_]';
  286. }
  287. if ($strict === false) {
  288. // Can either start with a lowercase letter, or multiple uppercase
  289. // in a row, representing an acronym.
  290. $legalFirstChar .= '([A-Z]{2,}|[a-z])';
  291. } else {
  292. $legalFirstChar .= '[a-z]';
  293. }
  294. } else {
  295. $legalFirstChar = '[A-Z]';
  296. }
  297. if (preg_match("/^$legalFirstChar/", $string) === 0) {
  298. return false;
  299. }
  300. // Check that the name only contains legal characters.
  301. $legalChars = 'a-zA-Z0-9';
  302. if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) {
  303. return false;
  304. }
  305. if ($strict === true) {
  306. // Check that there are not two capital letters next to each other.
  307. $length = strlen($string);
  308. $lastCharWasCaps = $classFormat;
  309. for ($i = 1; $i < $length; $i++) {
  310. $ascii = ord($string[$i]);
  311. if ($ascii >= 48 && $ascii <= 57) {
  312. // The character is a number, so it cant be a capital.
  313. $isCaps = false;
  314. } else {
  315. if (strtoupper($string[$i]) === $string[$i]) {
  316. $isCaps = true;
  317. } else {
  318. $isCaps = false;
  319. }
  320. }
  321. if ($isCaps === true && $lastCharWasCaps === true) {
  322. return false;
  323. }
  324. $lastCharWasCaps = $isCaps;
  325. }
  326. }//end if
  327. return true;
  328. }//end isCamelCaps()
  329. /**
  330. * Returns true if the specified string is in the underscore caps format.
  331. *
  332. * @param string $string The string to verify.
  333. *
  334. * @return boolean
  335. */
  336. public static function isUnderscoreName($string)
  337. {
  338. // If there are space in the name, it can't be valid.
  339. if (strpos($string, ' ') !== false) {
  340. return false;
  341. }
  342. $validName = true;
  343. $nameBits = explode('_', $string);
  344. if (preg_match('|^[A-Z]|', $string) === 0) {
  345. // Name does not begin with a capital letter.
  346. $validName = false;
  347. } else {
  348. foreach ($nameBits as $bit) {
  349. if ($bit === '') {
  350. continue;
  351. }
  352. if ($bit[0] !== strtoupper($bit[0])) {
  353. $validName = false;
  354. break;
  355. }
  356. }
  357. }
  358. return $validName;
  359. }//end isUnderscoreName()
  360. /**
  361. * Returns a valid variable type for param/var tags.
  362. *
  363. * If type is not one of the standard types, it must be a custom type.
  364. * Returns the correct type name suggestion if type name is invalid.
  365. *
  366. * @param string $varType The variable type to process.
  367. *
  368. * @return string
  369. */
  370. public static function suggestType($varType)
  371. {
  372. if ($varType === '') {
  373. return '';
  374. }
  375. if (in_array($varType, self::$allowedTypes, true) === true) {
  376. return $varType;
  377. } else {
  378. $lowerVarType = strtolower($varType);
  379. switch ($lowerVarType) {
  380. case 'bool':
  381. case 'boolean':
  382. return 'boolean';
  383. case 'double':
  384. case 'real':
  385. case 'float':
  386. return 'float';
  387. case 'int':
  388. case 'integer':
  389. return 'integer';
  390. case 'array()':
  391. case 'array':
  392. return 'array';
  393. }//end switch
  394. if (strpos($lowerVarType, 'array(') !== false) {
  395. // Valid array declaration:
  396. // array, array(type), array(type1 => type2).
  397. $matches = [];
  398. $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i';
  399. if (preg_match($pattern, $varType, $matches) !== 0) {
  400. $type1 = '';
  401. if (isset($matches[1]) === true) {
  402. $type1 = $matches[1];
  403. }
  404. $type2 = '';
  405. if (isset($matches[3]) === true) {
  406. $type2 = $matches[3];
  407. }
  408. $type1 = self::suggestType($type1);
  409. $type2 = self::suggestType($type2);
  410. if ($type2 !== '') {
  411. $type2 = ' => '.$type2;
  412. }
  413. return "array($type1$type2)";
  414. } else {
  415. return 'array';
  416. }//end if
  417. } else if (in_array($lowerVarType, self::$allowedTypes, true) === true) {
  418. // A valid type, but not lower cased.
  419. return $lowerVarType;
  420. } else {
  421. // Must be a custom type name.
  422. return $varType;
  423. }//end if
  424. }//end if
  425. }//end suggestType()
  426. /**
  427. * Given a sniff class name, returns the code for the sniff.
  428. *
  429. * @param string $sniffClass The fully qualified sniff class name.
  430. *
  431. * @return string
  432. */
  433. public static function getSniffCode($sniffClass)
  434. {
  435. $parts = explode('\\', $sniffClass);
  436. $sniff = array_pop($parts);
  437. if (substr($sniff, -5) === 'Sniff') {
  438. // Sniff class name.
  439. $sniff = substr($sniff, 0, -5);
  440. } else {
  441. // Unit test class name.
  442. $sniff = substr($sniff, 0, -8);
  443. }
  444. $category = array_pop($parts);
  445. $sniffDir = array_pop($parts);
  446. $standard = array_pop($parts);
  447. $code = $standard.'.'.$category.'.'.$sniff;
  448. return $code;
  449. }//end getSniffCode()
  450. /**
  451. * Removes project-specific information from a sniff class name.
  452. *
  453. * @param string $sniffClass The fully qualified sniff class name.
  454. *
  455. * @return string
  456. */
  457. public static function cleanSniffClass($sniffClass)
  458. {
  459. $newName = strtolower($sniffClass);
  460. $sniffPos = strrpos($newName, '\sniffs\\');
  461. if ($sniffPos === false) {
  462. // Nothing we can do as it isn't in a known format.
  463. return $newName;
  464. }
  465. $end = (strlen($newName) - $sniffPos + 1);
  466. $start = strrpos($newName, '\\', ($end * -1));
  467. if ($start === false) {
  468. // Nothing needs to be cleaned.
  469. return $newName;
  470. }
  471. $newName = substr($newName, ($start + 1));
  472. return $newName;
  473. }//end cleanSniffClass()
  474. }//end class