* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence */ namespace PHP_CodeSniffer\Util; class Common { /** * An array of variable types for param/var we will check. * * @var string[] */ public static $allowedTypes = [ 'array', 'boolean', 'float', 'integer', 'mixed', 'object', 'string', 'resource', 'callable', ]; /** * Return TRUE if the path is a PHAR file. * * @param string $path The path to use. * * @return mixed */ public static function isPharFile($path) { if (strpos($path, 'phar://') === 0) { return true; } return false; }//end isPharFile() /** * CodeSniffer alternative for realpath. * * Allows for PHAR support. * * @param string $path The path to use. * * @return mixed */ public static function realpath($path) { // Support the path replacement of ~ with the user's home directory. if (substr($path, 0, 2) === '~/') { $homeDir = getenv('HOME'); if ($homeDir !== false) { $path = $homeDir.substr($path, 1); } } // Check for process substitution. if (strpos($path, '/dev/fd') === 0) { return str_replace('/dev/fd', 'php://fd', $path); } // No extra work needed if this is not a phar file. if (self::isPharFile($path) === false) { return realpath($path); } // Before trying to break down the file path, // check if it exists first because it will mostly not // change after running the below code. if (file_exists($path) === true) { return $path; } $phar = \Phar::running(false); $extra = str_replace('phar://'.$phar, '', $path); $path = realpath($phar); if ($path === false) { return false; } $path = 'phar://'.$path.$extra; if (file_exists($path) === true) { return $path; } return false; }//end realpath() /** * Removes a base path from the front of a file path. * * @param string $path The path of the file. * @param string $basepath The base path to remove. This should not end * with a directory separator. * * @return string */ public static function stripBasepath($path, $basepath) { if (empty($basepath) === true) { return $path; } $basepathLen = strlen($basepath); if (substr($path, 0, $basepathLen) === $basepath) { $path = substr($path, $basepathLen); } $path = ltrim($path, DIRECTORY_SEPARATOR); if ($path === '') { $path = '.'; } return $path; }//end stripBasepath() /** * Detects the EOL character being used in a string. * * @param string $contents The contents to check. * * @return string */ public static function detectLineEndings($contents) { if (preg_match("/\r\n?|\n/", $contents, $matches) !== 1) { // Assume there are no newlines. $eolChar = "\n"; } else { $eolChar = $matches[0]; } return $eolChar; }//end detectLineEndings() /** * Check if STDIN is a TTY. * * @return boolean */ public static function isStdinATTY() { // The check is slow (especially calling `tty`) so we static // cache the result. static $isTTY = null; if ($isTTY !== null) { return $isTTY; } if (defined('STDIN') === false) { return false; } // If PHP has the POSIX extensions we will use them. if (function_exists('posix_isatty') === true) { $isTTY = (posix_isatty(STDIN) === true); return $isTTY; } // Next try is detecting whether we have `tty` installed and use that. if (defined('PHP_WINDOWS_VERSION_PLATFORM') === true) { $devnull = 'NUL'; $which = 'where'; } else { $devnull = '/dev/null'; $which = 'which'; } $tty = trim(shell_exec("$which tty 2> $devnull")); if (empty($tty) === false) { exec("tty -s 2> $devnull", $output, $returnValue); $isTTY = ($returnValue === 0); return $isTTY; } // Finally we will use fstat. The solution borrowed from // https://stackoverflow.com/questions/11327367/detect-if-a-php-script-is-being-run-interactively-or-not // This doesn't work on Mingw/Cygwin/... using Mintty but they // have `tty` installed. $type = [ 'S_IFMT' => 0170000, 'S_IFIFO' => 0010000, ]; $stat = fstat(STDIN); $mode = ($stat['mode'] & $type['S_IFMT']); $isTTY = ($mode !== $type['S_IFIFO']); return $isTTY; }//end isStdinATTY() /** * Prepares token content for output to screen. * * Replaces invisible characters so they are visible. On non-Windows * OSes it will also colour the invisible characters. * * @param string $content The content to prepare. * @param string[] $exclude A list of characters to leave invisible. * Can contain \r, \n, \t and a space. * * @return string */ public static function prepareForOutput($content, $exclude=[]) { if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { if (in_array("\r", $exclude, true) === false) { $content = str_replace("\r", '\r', $content); } if (in_array("\n", $exclude, true) === false) { $content = str_replace("\n", '\n', $content); } if (in_array("\t", $exclude, true) === false) { $content = str_replace("\t", '\t', $content); } } else { if (in_array("\r", $exclude, true) === false) { $content = str_replace("\r", "\033[30;1m\\r\033[0m", $content); } if (in_array("\n", $exclude, true) === false) { $content = str_replace("\n", "\033[30;1m\\n\033[0m", $content); } if (in_array("\t", $exclude, true) === false) { $content = str_replace("\t", "\033[30;1m\\t\033[0m", $content); } if (in_array(' ', $exclude, true) === false) { $content = str_replace(' ', "\033[30;1m·\033[0m", $content); } }//end if return $content; }//end prepareForOutput() /** * Returns true if the specified string is in the camel caps format. * * @param string $string The string the verify. * @param boolean $classFormat If true, check to see if the string is in the * class format. Class format strings must start * with a capital letter and contain no * underscores. * @param boolean $public If true, the first character in the string * must be an a-z character. If false, the * character must be an underscore. This * argument is only applicable if $classFormat * is false. * @param boolean $strict If true, the string must not have two capital * letters next to each other. If false, a * relaxed camel caps policy is used to allow * for acronyms. * * @return boolean */ public static function isCamelCaps( $string, $classFormat=false, $public=true, $strict=true ) { // Check the first character first. if ($classFormat === false) { $legalFirstChar = ''; if ($public === false) { $legalFirstChar = '[_]'; } if ($strict === false) { // Can either start with a lowercase letter, or multiple uppercase // in a row, representing an acronym. $legalFirstChar .= '([A-Z]{2,}|[a-z])'; } else { $legalFirstChar .= '[a-z]'; } } else { $legalFirstChar = '[A-Z]'; } if (preg_match("/^$legalFirstChar/", $string) === 0) { return false; } // Check that the name only contains legal characters. $legalChars = 'a-zA-Z0-9'; if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) { return false; } if ($strict === true) { // Check that there are not two capital letters next to each other. $length = strlen($string); $lastCharWasCaps = $classFormat; for ($i = 1; $i < $length; $i++) { $ascii = ord($string[$i]); if ($ascii >= 48 && $ascii <= 57) { // The character is a number, so it cant be a capital. $isCaps = false; } else { if (strtoupper($string[$i]) === $string[$i]) { $isCaps = true; } else { $isCaps = false; } } if ($isCaps === true && $lastCharWasCaps === true) { return false; } $lastCharWasCaps = $isCaps; } }//end if return true; }//end isCamelCaps() /** * Returns true if the specified string is in the underscore caps format. * * @param string $string The string to verify. * * @return boolean */ public static function isUnderscoreName($string) { // If there are space in the name, it can't be valid. if (strpos($string, ' ') !== false) { return false; } $validName = true; $nameBits = explode('_', $string); if (preg_match('|^[A-Z]|', $string) === 0) { // Name does not begin with a capital letter. $validName = false; } else { foreach ($nameBits as $bit) { if ($bit === '') { continue; } if ($bit[0] !== strtoupper($bit[0])) { $validName = false; break; } } } return $validName; }//end isUnderscoreName() /** * Returns a valid variable type for param/var tags. * * If type is not one of the standard types, it must be a custom type. * Returns the correct type name suggestion if type name is invalid. * * @param string $varType The variable type to process. * * @return string */ public static function suggestType($varType) { if ($varType === '') { return ''; } if (in_array($varType, self::$allowedTypes, true) === true) { return $varType; } else { $lowerVarType = strtolower($varType); switch ($lowerVarType) { case 'bool': case 'boolean': return 'boolean'; case 'double': case 'real': case 'float': return 'float'; case 'int': case 'integer': return 'integer'; case 'array()': case 'array': return 'array'; }//end switch if (strpos($lowerVarType, 'array(') !== false) { // Valid array declaration: // array, array(type), array(type1 => type2). $matches = []; $pattern = '/^array\(\s*([^\s^=^>]*)(\s*=>\s*(.*))?\s*\)/i'; if (preg_match($pattern, $varType, $matches) !== 0) { $type1 = ''; if (isset($matches[1]) === true) { $type1 = $matches[1]; } $type2 = ''; if (isset($matches[3]) === true) { $type2 = $matches[3]; } $type1 = self::suggestType($type1); $type2 = self::suggestType($type2); if ($type2 !== '') { $type2 = ' => '.$type2; } return "array($type1$type2)"; } else { return 'array'; }//end if } else if (in_array($lowerVarType, self::$allowedTypes, true) === true) { // A valid type, but not lower cased. return $lowerVarType; } else { // Must be a custom type name. return $varType; }//end if }//end if }//end suggestType() /** * Given a sniff class name, returns the code for the sniff. * * @param string $sniffClass The fully qualified sniff class name. * * @return string */ public static function getSniffCode($sniffClass) { $parts = explode('\\', $sniffClass); $sniff = array_pop($parts); if (substr($sniff, -5) === 'Sniff') { // Sniff class name. $sniff = substr($sniff, 0, -5); } else { // Unit test class name. $sniff = substr($sniff, 0, -8); } $category = array_pop($parts); $sniffDir = array_pop($parts); $standard = array_pop($parts); $code = $standard.'.'.$category.'.'.$sniff; return $code; }//end getSniffCode() /** * Removes project-specific information from a sniff class name. * * @param string $sniffClass The fully qualified sniff class name. * * @return string */ public static function cleanSniffClass($sniffClass) { $newName = strtolower($sniffClass); $sniffPos = strrpos($newName, '\sniffs\\'); if ($sniffPos === false) { // Nothing we can do as it isn't in a known format. return $newName; } $end = (strlen($newName) - $sniffPos + 1); $start = strrpos($newName, '\\', ($end * -1)); if ($start === false) { // Nothing needs to be cleaned. return $newName; } $newName = substr($newName, ($start + 1)); return $newName; }//end cleanSniffClass() }//end class