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