/** * Processes the tokens that this sniff is interested in. * * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void */ public function process(File $phpcsFile, $stackPtr) { $jslPath = Config::getExecutablePath('jsl'); if (is_null($jslPath) === true) { return; } $fileName = $phpcsFile->getFilename(); $cmd = '"' . $jslPath . '" -nologo -nofilelisting -nocontext -nosummary -output-format __LINE__:__ERROR__ -process "' . $fileName . '"'; $msg = exec($cmd, $output, $retval); // Variable $exitCode is the last line of $output if no error occurs, on // error it is numeric. Try to handle various error conditions and // provide useful error reporting. if ($retval === 2 || $retval === 4) { if (is_array($output) === true) { $msg = join('\\n', $output); } throw new RuntimeException("Failed invoking JavaScript Lint, retval was [{$retval}], output was [{$msg}]"); } if (is_array($output) === true) { foreach ($output as $finding) { $split = strpos($finding, ':'); $line = substr($finding, 0, $split); $message = substr($finding, $split + 1); $phpcsFile->addWarningOnLine(trim($message), $line, 'ExternalTool'); } } // Ignore the rest of the file. return $phpcsFile->numTokens + 1; }
/** * Processes the tokens that this sniff is interested in. * * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void * @throws PHP_CodeSniffer_Exception If jslint.js could not be run */ public function process(File $phpcsFile, $stackPtr) { $rhinoPath = Config::getExecutablePath('jslint'); $jslintPath = Config::getExecutablePath('jslint'); if ($rhinoPath === null || $jslintPath === null) { return; } $fileName = $phpcsFile->getFilename(); $cmd = "{$rhinoPath} \"{$jslintPath}\" \"{$fileName}\""; $msg = exec($cmd, $output, $retval); if (is_array($output) === true) { foreach ($output as $finding) { $matches = array(); $numMatches = preg_match('/Lint at line ([0-9]+).*:(.*)$/', $finding, $matches); if ($numMatches === 0) { continue; } $line = (int) $matches[1]; $message = 'jslint says: ' . trim($matches[2]); $phpcsFile->addWarningOnLine($message, $line, 'ExternalTool'); } } // Ignore the rest of the file. return $phpcsFile->numTokens + 1; }
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. * @param int $stackPtr The position of the current token in * the stack passed in $tokens. * * @return void */ public function process(File $phpcsFile, $stackPtr) { if ($this->phpPath === null) { $this->phpPath = Config::getExecutablePath('php'); if ($this->phpPath === null) { // PHP_BINARY is available in PHP 5.4+. if (defined('PHP_BINARY') === true) { $this->phpPath = PHP_BINARY; } else { return; } } } $fileName = $phpcsFile->getFilename(); $cmd = $this->phpPath . " -l \"{$fileName}\" 2>&1"; $output = shell_exec($cmd); $matches = array(); if (preg_match('/^.*error:(.*) in .* on line ([0-9]+)/', trim($output), $matches) === 1) { $error = trim($matches[1]); $line = (int) $matches[2]; $phpcsFile->addErrorOnLine("PHP syntax error: {$error}", $line, 'PHPSyntax'); } // Ignore the rest of the file. return $phpcsFile->numTokens + 1; }
/** * Processes the tokens that this sniff is interested in. * * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void */ public function process(File $phpcsFile, $stackPtr) { $csslintPath = Config::getExecutablePath('csslint'); if ($csslintPath === null) { return; } $fileName = $phpcsFile->getFilename(); $cmd = $csslintPath . ' ' . escapeshellarg($fileName); exec($cmd, $output, $retval); if (is_array($output) === false) { return; } $count = count($output); for ($i = 0; $i < $count; $i++) { $matches = array(); $numMatches = preg_match('/(error|warning) at line (\\d+)/', $output[$i], $matches); if ($numMatches === 0) { continue; } $line = (int) $matches[2]; $message = 'csslint says: ' . $output[$i + 1]; // First line is message with error line and error code. // Second is error message. // Third is wrong line in file. // Fourth is empty line. $i += 4; $phpcsFile->addWarningOnLine($message, $line, 'ExternalTool'); } //end for // Ignore the rest of the file. return $phpcsFile->numTokens + 1; }
/** * Should this test be skipped for some reason. * * @return void */ protected function shouldSkipTest() { if (defined('PHP_BINARY') === true) { return false; } $phpPath = Config::getExecutablePath('php'); if ($phpPath === null) { return true; } return false; }
/** * Returns an array of tokens this test wants to listen for. * * @return array */ public function register() { if ($this->phpVersion === null) { $this->phpVersion = Config::getConfigData('php_version'); if ($this->phpVersion === null) { $this->phpVersion = PHP_VERSION_ID; } } if ($this->phpVersion < 70000) { $this->aspTags = (bool) ini_get('asp_tags'); } return array(T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_INLINE_HTML); }
/** * Get a list paths where standards are installed. * * @return array */ public static function getInstalledStandardPaths() { $installedPaths = array(Common::realPath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'Standards')); $configPaths = Config::getConfigData('installed_paths'); if ($configPaths !== null) { $installedPaths = array_merge($installedPaths, explode(',', $configPaths)); } $resolvedInstalledPaths = array(); foreach ($installedPaths as $installedPath) { if (substr($installedPath, 0, 1) === '.') { $installedPath = Common::realPath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . $installedPath); } $resolvedInstalledPaths[] = $installedPath; } return $resolvedInstalledPaths; }
/** * Load configuration data. */ public function __construct() { $path = Config::getExecutablePath('notifysend'); if ($path !== null) { $this->path = $path; } $timeout = Config::getConfigData('notifysend_timeout'); if ($timeout !== null) { $this->timeout = (int) $timeout; } $showOk = Config::getConfigData('notifysend_showok'); if ($showOk !== null) { $this->showOk = (bool) $showOk; } $this->version = str_replace('notify-send ', '', exec($this->path . ' --version')); }
/** * Processes the tokens that this sniff is interested in. * * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return int */ public function process(File $phpcsFile, $stackPtr) { $analyzerPath = Config::getExecutablePath('zend_ca'); if (is_null($analyzerPath) === true) { return; } $fileName = $phpcsFile->getFilename(); // In the command, 2>&1 is important because the code analyzer sends its // findings to stderr. $output normally contains only stdout, so using 2>&1 // will pipe even stderr to stdout. $cmd = $analyzerPath . ' ' . $fileName . ' 2>&1'; // There is the possibility to pass "--ide" as an option to the analyzer. // This would result in an output format which would be easier to parse. // The problem here is that no cleartext error messages are returnwd; only // error-code-labels. So for a start we go for cleartext output. $exitCode = exec($cmd, $output, $retval); // Variable $exitCode is the last line of $output if no error occures, on // error it is numeric. Try to handle various error conditions and // provide useful error reporting. if (is_numeric($exitCode) === true && $exitCode > 0) { if (is_array($output) === true) { $msg = join('\\n', $output); } throw new RuntimeException("Failed invoking ZendCodeAnalyzer, exitcode was [{$exitCode}], retval was [{$retval}], output was [{$msg}]"); } if (is_array($output) === true) { foreach ($output as $finding) { // The first two lines of analyzer output contain // something like this: // > Zend Code Analyzer 1.2.2 // > Analyzing <filename>... // So skip these... $res = preg_match("/^.+\\(line ([0-9]+)\\):(.+)\$/", $finding, $regs); if (empty($regs) === true || $res === false) { continue; } $phpcsFile->addWarningOnLine(trim($regs[2]), $regs[1], 'ExternalTool'); } } // Ignore the rest of the file. return $phpcsFile->numTokens + 1; }
/** * Processes the tokens that this sniff is interested in. * * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void * @throws PHP_CodeSniffer_Exception If jslint.js could not be run */ public function process(File $phpcsFile, $stackPtr) { $lintPath = Config::getExecutablePath('gjslint'); if ($lintPath === null) { return; } $fileName = $phpcsFile->getFilename(); $cmd = "{$lintPath} --nosummary --notime --unix_mode \"{$fileName}\""; $msg = exec($cmd, $output, $retval); if (is_array($output) === false) { return; } foreach ($output as $finding) { $matches = array(); $numMatches = preg_match('/^(.*):([0-9]+):\\(.*?([0-9]+)\\)(.*)$/', $finding, $matches); if ($numMatches === 0) { continue; } // Skip error codes we are ignoring. $code = $matches[3]; if (in_array($code, $this->ignoreCodes) === true) { continue; } $line = (int) $matches[2]; $error = trim($matches[4]); $message = 'gjslint says: (%s) %s'; $data = array($code, $error); if (in_array($code, $this->errorCodes) === true) { $phpcsFile->addErrorOnLine($message, $line, 'ExternalToolError', $data); } else { $phpcsFile->addWarningOnLine($message, $line, 'ExternalTool', $data); } } //end foreach // Ignore the rest of the file. return $phpcsFile->numTokens + 1; }
/** * Creates a Config object and populates it with command line values. * * @param array $cliArgs An array of values gathered from CLI args. * @param bool $dieOnUnknownArg Whether or not to kill the process when an * unknown command line arg is found. * * @return void */ public function __construct(array $cliArgs = array(), $dieOnUnknownArg = true) { if (defined('PHP_CODESNIFFER_IN_TESTS') === true) { // Let everything through during testing so that we can // make use of PHPUnit command line arguments as well. $this->dieOnUnknownArg = false; } else { $this->dieOnUnknownArg = $dieOnUnknownArg; } $checkStdin = false; if (empty($cliArgs) === true) { $cliArgs = $_SERVER['argv']; array_shift($cliArgs); $checkStdin = true; } $this->restoreDefaults(); $this->setCommandLineValues($cliArgs); if (isset($this->overriddenDefaults['standards']) === false && Config::getConfigData('default_standard') === null) { // They did not supply a standard to use. // Look for a default ruleset in the current directory or higher. $currentDir = getcwd(); do { $default = $currentDir . DIRECTORY_SEPARATOR . 'phpcs.xml'; if (is_file($default) === true) { $this->standards = array($default); } else { $default = $currentDir . DIRECTORY_SEPARATOR . 'phpcs.xml.dist'; if (is_file($default) === true) { $this->standards = array($default); } } $lastDir = $currentDir; $currentDir = dirname($currentDir); } while ($currentDir !== '.' && $currentDir !== $lastDir); } // Check for content on STDIN. if ($checkStdin === true) { $handle = fopen('php://stdin', 'r'); if (stream_set_blocking($handle, false) === true) { $fileContents = ''; while (($line = fgets($handle)) !== false) { $fileContents .= $line; usleep(10); } stream_set_blocking($handle, true); fclose($handle); if (trim($fileContents) !== '') { $this->stdin = true; $this->stdinContent = $fileContents; $this->overriddenDefaults['stdin'] = true; $this->overriddenDefaults['stdinContent'] = true; } } } }
/** * Process the function parameter comments. * * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. * @param int $stackPtr The position of the current token * in the stack passed in $tokens. * @param int $commentStart The position in the stack where the comment started. * * @return void */ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) { if ($this->phpVersion === null) { $this->phpVersion = Config::getConfigData('php_version'); if ($this->phpVersion === null) { $this->phpVersion = PHP_VERSION_ID; } } $tokens = $phpcsFile->getTokens(); $params = array(); $maxType = 0; $maxVar = 0; foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { if ($tokens[$tag]['content'] !== '@param') { continue; } $type = ''; $typeSpace = 0; $var = ''; $varSpace = 0; $comment = ''; $commentLines = array(); if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) { $matches = array(); preg_match('/([^$&.]+)(?:((?:\\.\\.\\.)?(?:\\$|&)[^\\s]+)(?:(\\s+)(.*))?)?/', $tokens[$tag + 2]['content'], $matches); if (empty($matches) === false) { $typeLen = strlen($matches[1]); $type = trim($matches[1]); $typeSpace = $typeLen - strlen($type); $typeLen = strlen($type); if ($typeLen > $maxType) { $maxType = $typeLen; } } if (isset($matches[2]) === true) { $var = $matches[2]; $varLen = strlen($var); if ($varLen > $maxVar) { $maxVar = $varLen; } if (isset($matches[4]) === true) { $varSpace = strlen($matches[3]); $comment = $matches[4]; $commentLines[] = array('comment' => $comment, 'token' => $tag + 2, 'indent' => $varSpace); // Any strings until the next tag belong to this comment. if (isset($tokens[$commentStart]['comment_tags'][$pos + 1]) === true) { $end = $tokens[$commentStart]['comment_tags'][$pos + 1]; } else { $end = $tokens[$commentStart]['comment_closer']; } for ($i = $tag + 3; $i < $end; $i++) { if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { $indent = 0; if ($tokens[$i - 1]['code'] === T_DOC_COMMENT_WHITESPACE) { $indent = strlen($tokens[$i - 1]['content']); } $comment .= ' ' . $tokens[$i]['content']; $commentLines[] = array('comment' => $tokens[$i]['content'], 'token' => $i, 'indent' => $indent); } } } else { $error = 'Missing parameter comment'; $phpcsFile->addError($error, $tag, 'MissingParamComment'); $commentLines[] = array('comment' => ''); } //end if } else { $error = 'Missing parameter name'; $phpcsFile->addError($error, $tag, 'MissingParamName'); } //end if } else { $error = 'Missing parameter type'; $phpcsFile->addError($error, $tag, 'MissingParamType'); } //end if $params[] = array('tag' => $tag, 'type' => $type, 'var' => $var, 'comment' => $comment, 'commentLines' => $commentLines, 'type_space' => $typeSpace, 'var_space' => $varSpace); } //end foreach $realParams = $phpcsFile->getMethodParameters($stackPtr); $foundParams = array(); // We want to use ... for all variable length arguments, so added // this prefix to the variable name so comparisons are easier. foreach ($realParams as $pos => $param) { if ($param['variable_length'] === true) { $realParams[$pos]['name'] = '...' . $realParams[$pos]['name']; } } foreach ($params as $pos => $param) { // If the type is empty, the whole line is empty. if ($param['type'] === '') { continue; } // Check the param type value. $typeNames = explode('|', $param['type']); foreach ($typeNames as $typeName) { $suggestedName = Common::suggestType($typeName); if ($typeName !== $suggestedName) { $error = 'Expected "%s" but found "%s" for parameter type'; $data = array($suggestedName, $typeName); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data); if ($fix === true) { $content = $suggestedName; $content .= str_repeat(' ', $param['type_space']); $content .= $param['var']; $content .= str_repeat(' ', $param['var_space']); if (isset($param['commentLines'][0]) === true) { $content .= $param['commentLines'][0]['comment']; } $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); } } else { if (count($typeNames) === 1) { // Check type hint for array and custom type. $suggestedTypeHint = ''; if (strpos($suggestedName, 'array') !== false || substr($suggestedName, -2) === '[]') { $suggestedTypeHint = 'array'; } else { if (strpos($suggestedName, 'callable') !== false) { $suggestedTypeHint = 'callable'; } else { if (strpos($suggestedName, 'callback') !== false) { $suggestedTypeHint = 'callable'; } else { if (in_array($typeName, Common::$allowedTypes) === false) { $suggestedTypeHint = $suggestedName; } else { if ($this->phpVersion >= 70000) { if ($typeName === 'string') { $suggestedTypeHint = 'string'; } else { if ($typeName === 'int' || $typeName === 'integer') { $suggestedTypeHint = 'int'; } else { if ($typeName === 'float') { $suggestedTypeHint = 'float'; } else { if ($typeName === 'bool' || $typeName === 'boolean') { $suggestedTypeHint = 'bool'; } } } } } } } } } if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true) { $typeHint = $realParams[$pos]['type_hint']; if ($typeHint === '') { $error = 'Type hint "%s" missing for %s'; $data = array($suggestedTypeHint, $param['var']); $errorCode = 'TypeHintMissing'; if ($suggestedTypeHint === 'string' || $suggestedTypeHint === 'int' || $suggestedTypeHint === 'float' || $suggestedTypeHint === 'bool') { $errorCode = 'Scalar' . $errorCode; } $phpcsFile->addError($error, $stackPtr, $errorCode, $data); } else { if ($typeHint !== substr($suggestedTypeHint, strlen($typeHint) * -1)) { $error = 'Expected type hint "%s"; found "%s" for %s'; $data = array($suggestedTypeHint, $typeHint, $param['var']); $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data); } } //end if } else { if ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) { $typeHint = $realParams[$pos]['type_hint']; if ($typeHint !== '') { $error = 'Unknown type hint "%s" found for %s'; $data = array($typeHint, $param['var']); $phpcsFile->addError($error, $stackPtr, 'InvalidTypeHint', $data); } } } //end if } } //end if } //end foreach if ($param['var'] === '') { continue; } $foundParams[] = $param['var']; // Check number of spaces after the type. $spaces = $maxType - strlen($param['type']) + 1; if ($param['type_space'] !== $spaces) { $error = 'Expected %s spaces after parameter type; %s found'; $data = array($spaces, $param['type_space']); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamType', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $content = $param['type']; $content .= str_repeat(' ', $spaces); $content .= $param['var']; $content .= str_repeat(' ', $param['var_space']); $content .= $param['commentLines'][0]['comment']; $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); // Fix up the indent of additional comment lines. foreach ($param['commentLines'] as $lineNum => $line) { if ($lineNum === 0 || $param['commentLines'][$lineNum]['indent'] === 0) { continue; } $newIndent = $param['commentLines'][$lineNum]['indent'] + $spaces - $param['type_space']; $phpcsFile->fixer->replaceToken($param['commentLines'][$lineNum]['token'] - 1, str_repeat(' ', $newIndent)); } $phpcsFile->fixer->endChangeset(); } //end if } //end if // Make sure the param name is correct. if (isset($realParams[$pos]) === true) { $realName = $realParams[$pos]['name']; if ($realName !== $param['var']) { $code = 'ParamNameNoMatch'; $data = array($param['var'], $realName); $error = 'Doc comment for parameter %s does not match '; if (strtolower($param['var']) === strtolower($realName)) { $error .= 'case of '; $code = 'ParamNameNoCaseMatch'; } $error .= 'actual variable name %s'; $phpcsFile->addError($error, $param['tag'], $code, $data); } } else { if (substr($param['var'], -4) !== ',...') { // We must have an extra parameter comment. $error = 'Superfluous parameter comment'; $phpcsFile->addError($error, $param['tag'], 'ExtraParamComment'); } } //end if if ($param['comment'] === '') { continue; } // Check number of spaces after the var name. $spaces = $maxVar - strlen($param['var']) + 1; if ($param['var_space'] !== $spaces) { $error = 'Expected %s spaces after parameter name; %s found'; $data = array($spaces, $param['var_space']); $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamName', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $content = $param['type']; $content .= str_repeat(' ', $param['type_space']); $content .= $param['var']; $content .= str_repeat(' ', $spaces); $content .= $param['commentLines'][0]['comment']; $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); // Fix up the indent of additional comment lines. foreach ($param['commentLines'] as $lineNum => $line) { if ($lineNum === 0 || $param['commentLines'][$lineNum]['indent'] === 0) { continue; } $newIndent = $param['commentLines'][$lineNum]['indent'] + $spaces - $param['var_space']; $phpcsFile->fixer->replaceToken($param['commentLines'][$lineNum]['token'] - 1, str_repeat(' ', $newIndent)); } $phpcsFile->fixer->endChangeset(); } //end if } //end if // Param comments must start with a capital letter and end with the full stop. if (preg_match('/^(\\p{Ll}|\\P{L})/u', $param['comment']) === 1) { $error = 'Parameter comment must start with a capital letter'; $phpcsFile->addError($error, $param['tag'], 'ParamCommentNotCapital'); } $lastChar = substr($param['comment'], -1); if ($lastChar !== '.') { $error = 'Parameter comment must end with a full stop'; $phpcsFile->addError($error, $param['tag'], 'ParamCommentFullStop'); } } //end foreach $realNames = array(); foreach ($realParams as $realParam) { $realNames[] = $realParam['name']; } // Report missing comments. $diff = array_diff($realNames, $foundParams); foreach ($diff as $neededParam) { $error = 'Doc comment for parameter "%s" missing'; $data = array($neededParam); $phpcsFile->addError($error, $commentStart, 'MissingParamTag', $data); } }
/** * Processes a single ruleset and returns a list of the sniffs it represents. * * Rules founds within the ruleset are processed immediately, but sniff classes * are not registered by this method. * * @param string $rulesetPath The path to a ruleset XML file. * @param int $depth How many nested processing steps we are in. This * is only used for debug output. * * @return string[] * @throws RuntimeException If the ruleset path is invalid. */ public function processRuleset($rulesetPath, $depth = 0) { $rulesetPath = Util\Common::realpath($rulesetPath); if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo 'Processing ruleset ' . Util\Common::stripBasepath($rulesetPath, $this->config->basepath) . PHP_EOL; } $ruleset = simplexml_load_string(file_get_contents($rulesetPath)); if ($ruleset === false) { throw new RuntimeException("Ruleset {$rulesetPath} is not valid"); } $ownSniffs = array(); $includedSniffs = array(); $excludedSniffs = array(); $rulesetDir = dirname($rulesetPath); $this->rulesetDirs[] = $rulesetDir; $sniffDir = $rulesetDir . DIRECTORY_SEPARATOR . 'Sniffs'; if (is_dir($sniffDir) === true) { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\tAdding sniff files from " . Util\Common::stripBasepath($sniffDir, $this->config->basepath) . ' directory' . PHP_EOL; } $ownSniffs = $this->expandSniffDirectory($sniffDir, $depth); } // Process custom sniff config settings. foreach ($ruleset->{'config'} as $config) { if ($this->shouldProcessElement($config) === false) { continue; } $this->setConfigData((string) $config['name'], (string) $config['value'], true); if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t=> set config value " . (string) $config['name'] . ': ' . (string) $config['value'] . PHP_EOL; } } foreach ($ruleset->rule as $rule) { if (isset($rule['ref']) === false || $this->shouldProcessElement($rule) === false) { continue; } if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\tProcessing rule \"" . $rule['ref'] . '"' . PHP_EOL; } $expandedSniffs = $this->expandRulesetReference($rule['ref'], $rulesetDir, $depth); $newSniffs = array_diff($expandedSniffs, $includedSniffs); $includedSniffs = array_merge($includedSniffs, $expandedSniffs); $parts = explode('.', $rule['ref']); if (count($parts) === 4) { $sniffCode = $parts[0] . '.' . $parts[1] . '.' . $parts[2]; if (isset($this->ruleset[$sniffCode]['severity']) === true && $this->ruleset[$sniffCode]['severity'] === 0) { // This sniff code has already been turned off, but now // it is being explicitly included again, so turn it back on. $this->ruleset[(string) $rule['ref']]['severity'] = 5; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t* disabling sniff exclusion for specific message code *" . PHP_EOL; echo str_repeat("\t", $depth); echo "\t\t=> severity set to 5" . PHP_EOL; } } else { if (empty($newSniffs) === false) { // Including a sniff that hasn't been included higher up, but // only including a single message from it. So turn off all messages in // the sniff, except this one. $this->ruleset[$sniffCode]['severity'] = 0; $this->ruleset[(string) $rule['ref']]['severity'] = 5; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\tExcluding sniff \"" . $sniffCode . '" except for "' . $parts[3] . '"' . PHP_EOL; } } } //end if } //end if if (isset($rule->exclude) === true) { foreach ($rule->exclude as $exclude) { if ($this->shouldProcessElement($exclude) === false) { continue; } if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\tExcluding rule \"" . $exclude['name'] . '"' . PHP_EOL; } // Check if a single code is being excluded, which is a shortcut // for setting the severity of the message to 0. $parts = explode('.', $exclude['name']); if (count($parts) === 4) { $this->ruleset[(string) $exclude['name']]['severity'] = 0; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t\t=> severity set to 0" . PHP_EOL; } } else { $excludedSniffs = array_merge($excludedSniffs, $this->expandRulesetReference($exclude['name'], $rulesetDir, $depth + 1)); } } //end foreach } //end if $this->processRule($rule, $newSniffs, $depth); } //end foreach // Process custom command line arguments. $cliArgs = array(); foreach ($ruleset->{'arg'} as $arg) { if ($this->shouldProcessElement($arg) === false) { continue; } if (isset($arg['name']) === true) { $argString = '--' . (string) $arg['name']; if (isset($arg['value']) === true) { $argString .= '=' . (string) $arg['value']; } } else { $argString = '-' . (string) $arg['value']; } $cliArgs[] = $argString; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t=> set command line value {$argString}" . PHP_EOL; } } //end foreach // Set custom php ini values as CLI args. foreach ($ruleset->{'ini'} as $arg) { if ($this->shouldProcessElement($arg) === false) { continue; } if (isset($arg['name']) === false) { continue; } $name = (string) $arg['name']; $argString = $name; if (isset($arg['value']) === true) { $value = (string) $arg['value']; $argString .= "={$value}"; } else { $value = 'true'; } $cliArgs[] = '-d'; $cliArgs[] = $argString; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t=> set PHP ini value {$name} to {$value}" . PHP_EOL; } } //end foreach if (empty($this->config->files) === true) { // Process hard-coded file paths. foreach ($ruleset->{'file'} as $file) { $file = (string) $file; $cliArgs[] = $file; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t=> added \"{$file}\" to the file list" . PHP_EOL; } } } if (empty($cliArgs) === false) { // Change the directory so all relative paths are worked // out based on the location of the ruleset instead of // the location of the user. $inPhar = Util\Common::isPharFile($rulesetDir); if ($inPhar === false) { $currentDir = getcwd(); chdir($rulesetDir); } $this->config->setCommandLineValues($cliArgs); if ($inPhar === false) { chdir($currentDir); } } // Process custom ignore pattern rules. foreach ($ruleset->{'exclude-pattern'} as $pattern) { if ($this->shouldProcessElement($pattern) === false) { continue; } if (isset($pattern['type']) === false) { $pattern['type'] = 'absolute'; } $this->ignorePatterns[(string) $pattern] = (string) $pattern['type']; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo "\t=> added global " . (string) $pattern['type'] . ' ignore pattern: ' . (string) $pattern . PHP_EOL; } } $includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs)); $excludedSniffs = array_unique($excludedSniffs); if (PHP_CODESNIFFER_VERBOSITY > 1) { $included = count($includedSniffs); $excluded = count($excludedSniffs); echo str_repeat("\t", $depth); echo "=> Ruleset processing complete; included {$included} sniffs and excluded {$excluded}" . PHP_EOL; } // Merge our own sniff list with our externally included // sniff list, but filter out any excluded sniffs. $files = array(); foreach ($includedSniffs as $sniff) { if (in_array($sniff, $excludedSniffs) === true) { continue; } else { $files[] = Util\Common::realpath($sniff); } } return $files; }
/** * Processes this test, when one of its tokens is encountered. * * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document. * @param int $stackPtr The position of the current token * in the stack passed in $tokens. * * @return void */ public function process(File $phpcsFile, $stackPtr) { $debug = Config::getConfigData('scope_indent_debug'); if ($debug !== null) { $this->debug = (bool) $debug; } if ($this->tabWidth === null) { if (isset($phpcsFile->config->tabWidth) === false || $phpcsFile->config->tabWidth === 0) { // We have no idea how wide tabs are, so assume 4 spaces for fixing. // It shouldn't really matter because indent checks elsewhere in the // standard should fix things up. $this->tabWidth = 4; } else { $this->tabWidth = $phpcsFile->config->tabWidth; } } $currentIndent = 0; $lastOpenTag = $stackPtr; $lastCloseTag = null; $openScopes = array(); $adjustments = array(); $setIndents = array(); $tokens = $phpcsFile->getTokens(); $first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr); $trimmed = ltrim($tokens[$first]['content']); if ($trimmed === '') { $currentIndent = $tokens[$stackPtr]['column'] - 1; } else { $currentIndent = strlen($tokens[$first]['content']) - strlen($trimmed); } if ($this->debug === true) { $line = $tokens[$stackPtr]['line']; echo "Start with token {$stackPtr} on line {$line} with indent {$currentIndent}" . PHP_EOL; } if (empty($this->ignoreIndentation) === true) { $this->ignoreIndentation = array(T_INLINE_HTML => true); foreach ($this->ignoreIndentationTokens as $token) { if (is_int($token) === false) { if (defined($token) === false) { continue; } $token = constant($token); } $this->ignoreIndentation[$token] = true; } } //end if $this->exact = (bool) $this->exact; $this->tabIndent = (bool) $this->tabIndent; for ($i = $stackPtr + 1; $i < $phpcsFile->numTokens; $i++) { if ($i === false) { // Something has gone very wrong; maybe a parse error. break; } $checkToken = null; $checkIndent = null; $exact = (bool) $this->exact; if ($exact === true && isset($tokens[$i]['nested_parenthesis']) === true) { // Don't check indents exactly between parenthesis as they // tend to have custom rules, such as with multi-line function calls // and control structure conditions. $exact = false; } // Detect line changes and figure out where the indent is. if ($tokens[$i]['column'] === 1) { $trimmed = ltrim($tokens[$i]['content']); if ($trimmed === '') { if (isset($tokens[$i + 1]) === true && $tokens[$i]['line'] === $tokens[$i + 1]['line']) { $checkToken = $i + 1; $tokenIndent = $tokens[$i + 1]['column'] - 1; } } else { $checkToken = $i; $tokenIndent = strlen($tokens[$i]['content']) - strlen($trimmed); } } // Closing parenthesis should just be indented to at least // the same level as where they were opened (but can be more). if ($checkToken !== null && $tokens[$checkToken]['code'] === T_CLOSE_PARENTHESIS && isset($tokens[$checkToken]['parenthesis_opener']) === true || $tokens[$i]['code'] === T_CLOSE_PARENTHESIS && isset($tokens[$i]['parenthesis_opener']) === true && isset($tokens[$i]['parenthesis_owner']) === true && $tokens[$tokens[$i]['parenthesis_owner']]['code'] === T_ARRAY) { if ($checkToken !== null) { $parenCloser = $checkToken; } else { $parenCloser = $i; } if ($this->debug === true) { $line = $tokens[$i]['line']; echo "Closing parenthesis found on line {$line}" . PHP_EOL; } $parenOpener = $tokens[$parenCloser]['parenthesis_opener']; if ($tokens[$parenCloser]['line'] !== $tokens[$parenOpener]['line']) { $parens = 0; if (isset($tokens[$parenCloser]['nested_parenthesis']) === true && empty($tokens[$parenCloser]['nested_parenthesis']) === false) { end($tokens[$parenCloser]['nested_parenthesis']); $parens = key($tokens[$parenCloser]['nested_parenthesis']); if ($this->debug === true) { $line = $tokens[$parens]['line']; echo "\t* token has nested parenthesis {$parens} on line {$line} *" . PHP_EOL; } } $condition = 0; if (isset($tokens[$parenCloser]['conditions']) === true && empty($tokens[$parenCloser]['conditions']) === false) { end($tokens[$parenCloser]['conditions']); $condition = key($tokens[$parenCloser]['conditions']); if ($this->debug === true) { $line = $tokens[$condition]['line']; $type = $tokens[$condition]['type']; echo "\t* token is inside condition {$condition} ({$type}) on line {$line} *" . PHP_EOL; } } if ($parens > $condition) { if ($this->debug === true) { echo "\t* using parenthesis *" . PHP_EOL; } $parenOpener = $parens; $condition = 0; } else { if ($condition > 0) { if ($this->debug === true) { echo "\t* using condition *" . PHP_EOL; } $parenOpener = $condition; $parens = 0; } } $exact = false; if ($condition > 0 && $lastOpenTag > $condition) { if ($this->debug === true) { echo "\t* open tag is inside condition; using open tag *" . PHP_EOL; } $checkIndent = $tokens[$lastOpenTag]['column'] - 1; if (isset($adjustments[$condition]) === true) { $checkIndent += $adjustments[$condition]; } $currentIndent = $checkIndent; if ($this->debug === true) { $type = $tokens[$lastOpenTag]['type']; echo "\t=> checking indent of {$checkIndent}; main indent set to {$currentIndent} by token {$lastOpenTag} ({$type})" . PHP_EOL; } } else { if ($condition > 0 && isset($tokens[$condition]['scope_opener']) === true && isset($setIndents[$tokens[$condition]['scope_opener']]) === true) { $checkIndent = $setIndents[$tokens[$condition]['scope_opener']]; if (isset($adjustments[$condition]) === true) { $checkIndent += $adjustments[$condition]; } $currentIndent = $checkIndent; if ($this->debug === true) { $type = $tokens[$condition]['type']; echo "\t=> checking indent of {$checkIndent}; main indent set to {$currentIndent} by token {$condition} ({$type})" . PHP_EOL; } } else { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $parenOpener, true); $checkIndent = $tokens[$first]['column'] - 1; if (isset($adjustments[$first]) === true) { $checkIndent += $adjustments[$first]; } if ($this->debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* first token on line {$line} is {$first} ({$type}) *" . PHP_EOL; } if ($first === $tokens[$parenCloser]['parenthesis_opener']) { // This is unlikely to be the start of the statement, so look // back further to find it. $first--; } $prev = $phpcsFile->findStartOfStatement($first, T_COMMA); if ($prev !== $first) { // This is not the start of the statement. if ($this->debug === true) { $line = $tokens[$prev]['line']; $type = $tokens[$prev]['type']; echo "\t* previous is {$type} on line {$line} *" . PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); $prev = $phpcsFile->findStartOfStatement($first, T_COMMA); $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); if ($this->debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* amended first token is {$first} ({$type}) on line {$line} *" . PHP_EOL; } } if (isset($tokens[$first]['scope_closer']) === true && $tokens[$first]['scope_closer'] === $first) { if ($this->debug === true) { echo "\t* first token is a scope closer *" . PHP_EOL; } if (isset($tokens[$first]['scope_condition']) === true) { $scopeCloser = $first; $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['scope_condition'], true); $currentIndent = $tokens[$first]['column'] - 1; if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } // Make sure it is divisible by our expected indent. if ($tokens[$tokens[$scopeCloser]['scope_condition']]['code'] !== T_CLOSURE) { $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); } $setIndents[$first] = $currentIndent; if ($this->debug === true) { $type = $tokens[$first]['type']; echo "\t=> indent set to {$currentIndent} by token {$first} ({$type})" . PHP_EOL; } } //end if } else { // Don't force current indent to divisible because there could be custom // rules in place between parenthesis, such as with arrays. $currentIndent = $tokens[$first]['column'] - 1; if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } $setIndents[$first] = $currentIndent; if ($this->debug === true) { $type = $tokens[$first]['type']; echo "\t=> checking indent of {$checkIndent}; main indent set to {$currentIndent} by token {$first} ({$type})" . PHP_EOL; } } //end if } } //end if } else { if ($this->debug === true) { echo "\t * ignoring single-line definition *" . PHP_EOL; } } //end if } //end if // Closing short array bracket should just be indented to at least // the same level as where it was opened (but can be more). if ($tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY || $checkToken !== null && $tokens[$checkToken]['code'] === T_CLOSE_SHORT_ARRAY) { if ($checkToken !== null) { $arrayCloser = $checkToken; } else { $arrayCloser = $i; } if ($this->debug === true) { $line = $tokens[$arrayCloser]['line']; echo "Closing short array bracket found on line {$line}" . PHP_EOL; } $arrayOpener = $tokens[$arrayCloser]['bracket_opener']; if ($tokens[$arrayCloser]['line'] !== $tokens[$arrayOpener]['line']) { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $arrayOpener, true); $checkIndent = $tokens[$first]['column'] - 1; if (isset($adjustments[$first]) === true) { $checkIndent += $adjustments[$first]; } $exact = false; if ($this->debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* first token on line {$line} is {$first} ({$type}) *" . PHP_EOL; } if ($first === $tokens[$arrayCloser]['bracket_opener']) { // This is unlikely to be the start of the statement, so look // back further to find it. $first--; } $prev = $phpcsFile->findStartOfStatement($first, T_COMMA); if ($prev !== $first) { // This is not the start of the statement. if ($this->debug === true) { $line = $tokens[$prev]['line']; $type = $tokens[$prev]['type']; echo "\t* previous is {$type} on line {$line} *" . PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); $prev = $phpcsFile->findStartOfStatement($first, T_COMMA); $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); if ($this->debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* amended first token is {$first} ({$type}) on line {$line} *" . PHP_EOL; } } if (isset($tokens[$first]['scope_closer']) === true && $tokens[$first]['scope_closer'] === $first) { // The first token is a scope closer and would have already // been processed and set the indent level correctly, so // don't adjust it again. if ($this->debug === true) { echo "\t* first token is a scope closer; ignoring closing short array bracket *" . PHP_EOL; } if (isset($setIndents[$first]) === true) { $currentIndent = $setIndents[$first]; if ($this->debug === true) { echo "\t=> indent reset to {$currentIndent}" . PHP_EOL; } } } else { // Don't force current indent to be divisible because there could be custom // rules in place for arrays. $currentIndent = $tokens[$first]['column'] - 1; if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } $setIndents[$first] = $currentIndent; if ($this->debug === true) { $type = $tokens[$first]['type']; echo "\t=> checking indent of {$checkIndent}; main indent set to {$currentIndent} by token {$first} ({$type})" . PHP_EOL; } } //end if } else { if ($this->debug === true) { echo "\t * ignoring single-line definition *" . PHP_EOL; } } //end if } //end if // Adjust lines within scopes while auto-fixing. if ($checkToken !== null && $exact === false && (empty($tokens[$checkToken]['conditions']) === false || isset($tokens[$checkToken]['scope_opener']) === true && $tokens[$checkToken]['scope_opener'] === $checkToken)) { if (empty($tokens[$checkToken]['conditions']) === false) { end($tokens[$checkToken]['conditions']); $condition = key($tokens[$checkToken]['conditions']); } else { $condition = $tokens[$checkToken]['scope_condition']; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $condition, true); if (isset($adjustments[$first]) === true && ($adjustments[$first] < 0 && $tokenIndent > $currentIndent || $adjustments[$first] > 0 && $tokenIndent < $currentIndent)) { $padding = $tokenIndent + $adjustments[$first]; if ($padding > 0) { if ($this->tabIndent === true) { $numTabs = floor($padding / $this->tabWidth); $numSpaces = $padding - $numTabs * $this->tabWidth; $padding = str_repeat("\t", $numTabs) . str_repeat(' ', $numSpaces); } else { $padding = str_repeat(' ', $padding); } } else { $padding = ''; } if ($checkToken === $i) { $phpcsFile->fixer->replaceToken($checkToken, $padding . $trimmed); } else { // Easier to just replace the entire indent. $phpcsFile->fixer->replaceToken($checkToken - 1, $padding); } if ($this->debug === true) { $length = strlen($padding); $line = $tokens[$checkToken]['line']; $type = $tokens[$checkToken]['type']; echo "Indent adjusted to {$length} for {$type} on line {$line}" . PHP_EOL; } $adjustments[$checkToken] = $adjustments[$first]; if ($this->debug === true) { $line = $tokens[$checkToken]['line']; $type = $tokens[$checkToken]['type']; echo "\t=> Add adjustment of " . $adjustments[$checkToken] . " for token {$checkToken} ({$type}) on line {$line}" . PHP_EOL; } } //end if } //end if // Scope closers reset the required indent to the same level as the opening condition. if ($checkToken !== null && isset($openScopes[$checkToken]) === true || isset($tokens[$checkToken]['scope_condition']) === true && isset($tokens[$checkToken]['scope_closer']) === true && $tokens[$checkToken]['scope_closer'] === $checkToken && $tokens[$checkToken]['line'] !== $tokens[$tokens[$checkToken]['scope_opener']]['line'] || ($checkToken === null && isset($openScopes[$i]) === true || isset($tokens[$i]['scope_condition']) === true && isset($tokens[$i]['scope_closer']) === true && $tokens[$i]['scope_closer'] === $i && $tokens[$i]['line'] !== $tokens[$tokens[$i]['scope_opener']]['line'])) { if ($this->debug === true) { if ($checkToken === null) { $type = $tokens[$tokens[$i]['scope_condition']]['type']; $line = $tokens[$i]['line']; } else { $type = $tokens[$tokens[$checkToken]['scope_condition']]['type']; $line = $tokens[$checkToken]['line']; } echo "Close scope ({$type}) on line {$line}" . PHP_EOL; } $scopeCloser = $checkToken; if ($scopeCloser === null) { $scopeCloser = $i; } else { array_pop($openScopes); } if (isset($tokens[$scopeCloser]['scope_condition']) === true) { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['scope_condition'], true); $currentIndent = $tokens[$first]['column'] - 1; if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } // Make sure it is divisible by our expected indent. if ($tokens[$tokens[$scopeCloser]['scope_condition']]['code'] !== T_CLOSURE) { $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); } $setIndents[$scopeCloser] = $currentIndent; if ($this->debug === true) { $type = $tokens[$scopeCloser]['type']; echo "\t=> indent set to {$currentIndent} by token {$scopeCloser} ({$type})" . PHP_EOL; } // We only check the indent of scope closers if they are // curly braces because other constructs tend to have different rules. if ($tokens[$scopeCloser]['code'] === T_CLOSE_CURLY_BRACKET) { $exact = true; } else { $checkToken = null; } } //end if } //end if // Handle scope for JS object notation. if ($phpcsFile->tokenizerType === 'JS' && ($checkToken !== null && $tokens[$checkToken]['code'] === T_CLOSE_OBJECT && $tokens[$checkToken]['line'] !== $tokens[$tokens[$checkToken]['bracket_opener']]['line'] || $checkToken === null && $tokens[$i]['code'] === T_CLOSE_OBJECT && $tokens[$i]['line'] !== $tokens[$tokens[$i]['bracket_opener']]['line'])) { if ($this->debug === true) { $line = $tokens[$i]['line']; echo "Close JS object on line {$line}" . PHP_EOL; } $scopeCloser = $checkToken; if ($scopeCloser === null) { $scopeCloser = $i; } else { array_pop($openScopes); } $parens = 0; if (isset($tokens[$scopeCloser]['nested_parenthesis']) === true && empty($tokens[$scopeCloser]['nested_parenthesis']) === false) { end($tokens[$scopeCloser]['nested_parenthesis']); $parens = key($tokens[$scopeCloser]['nested_parenthesis']); if ($this->debug === true) { $line = $tokens[$parens]['line']; echo "\t* token has nested parenthesis {$parens} on line {$line} *" . PHP_EOL; } } $condition = 0; if (isset($tokens[$scopeCloser]['conditions']) === true && empty($tokens[$scopeCloser]['conditions']) === false) { end($tokens[$scopeCloser]['conditions']); $condition = key($tokens[$scopeCloser]['conditions']); if ($this->debug === true) { $line = $tokens[$condition]['line']; $type = $tokens[$condition]['type']; echo "\t* token is inside condition {$condition} ({$type}) on line {$line} *" . PHP_EOL; } } if ($parens > $condition) { if ($this->debug === true) { echo "\t* using parenthesis *" . PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $parens, true); $condition = 0; } else { if ($condition > 0) { if ($this->debug === true) { echo "\t* using condition *" . PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $condition, true); $parens = 0; } else { if ($this->debug === true) { $line = $tokens[$tokens[$scopeCloser]['bracket_opener']]['line']; echo "\t* token is not in parenthesis or condition; using opener on line {$line} *" . PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['bracket_opener'], true); } } //end if $currentIndent = $tokens[$first]['column'] - 1; if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } if ($parens > 0 || $condition > 0) { $checkIndent = $tokens[$first]['column'] - 1; if (isset($adjustments[$first]) === true) { $checkIndent += $adjustments[$first]; } if ($condition > 0) { $checkIndent += $this->indent; $currentIndent += $this->indent; $exact = true; } } else { $checkIndent = $currentIndent; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $checkIndent = (int) (ceil($checkIndent / $this->indent) * $this->indent); $setIndents[$first] = $currentIndent; if ($this->debug === true) { $type = $tokens[$first]['type']; echo "\t=> checking indent of {$checkIndent}; main indent set to {$currentIndent} by token {$first} ({$type})" . PHP_EOL; } } //end if if ($checkToken !== null && isset(Tokens::$scopeOpeners[$tokens[$checkToken]['code']]) === true && in_array($tokens[$checkToken]['code'], $this->nonIndentingScopes) === false && isset($tokens[$checkToken]['scope_opener']) === true) { $exact = true; $lastOpener = null; if (empty($openScopes) === false) { end($openScopes); $lastOpener = current($openScopes); } // A scope opener that shares a closer with another token (like multiple // CASEs using the same BREAK) needs to reduce the indent level so its // indent is checked correctly. It will then increase the indent again // (as all openers do) after being checked. if ($lastOpener !== null && isset($tokens[$lastOpener]['scope_closer']) === true && $tokens[$lastOpener]['level'] === $tokens[$checkToken]['level'] && $tokens[$lastOpener]['scope_closer'] === $tokens[$checkToken]['scope_closer']) { $currentIndent -= $this->indent; $setIndents[$lastOpener] = $currentIndent; if ($this->debug === true) { $line = $tokens[$i]['line']; $type = $tokens[$lastOpener]['type']; echo "Shared closer found on line {$line}" . PHP_EOL; echo "\t=> indent set to {$currentIndent} by token {$lastOpener} ({$type})" . PHP_EOL; } } if ($tokens[$checkToken]['code'] === T_CLOSURE && $tokenIndent > $currentIndent) { // The opener is indented more than needed, which is fine. // But just check that it is divisible by our expected indent. $checkIndent = (int) (ceil($tokenIndent / $this->indent) * $this->indent); $exact = false; if ($this->debug === true) { $line = $tokens[$i]['line']; echo "Closure found on line {$line}" . PHP_EOL; echo "\t=> checking indent of {$checkIndent}; main indent remains at {$currentIndent}" . PHP_EOL; } } } //end if // Method prefix indentation has to be exact or else if will break // the rest of the function declaration, and potentially future ones. if ($checkToken !== null && isset(Tokens::$methodPrefixes[$tokens[$checkToken]['code']]) === true && $tokens[$checkToken + 1]['code'] !== T_DOUBLE_COLON) { $exact = true; } // JS property indentation has to be exact or else if will break // things like function and object indentation. if ($checkToken !== null && $tokens[$checkToken]['code'] === T_PROPERTY) { $exact = true; } // PHP tags needs to be indented to exact column positions // so they don't cause problems with indent checks for the code // within them, but they don't need to line up with the current indent. if ($checkToken !== null && ($tokens[$checkToken]['code'] === T_OPEN_TAG || $tokens[$checkToken]['code'] === T_OPEN_TAG_WITH_ECHO || $tokens[$checkToken]['code'] === T_CLOSE_TAG)) { $exact = true; $checkIndent = $tokens[$checkToken]['column'] - 1; $checkIndent = (int) (ceil($checkIndent / $this->indent) * $this->indent); } // Check the line indent. if ($checkIndent === null) { $checkIndent = $currentIndent; } $adjusted = false; if ($checkToken !== null && isset($this->ignoreIndentation[$tokens[$checkToken]['code']]) === false && ($tokenIndent !== $checkIndent && $exact === true || $tokenIndent < $checkIndent && $exact === false)) { $type = 'IncorrectExact'; $error = 'Line indented incorrectly; expected '; if ($exact === false) { $error .= 'at least '; $type = 'Incorrect'; } if ($this->tabIndent === true) { $error .= '%s tabs, found %s'; $data = array(floor($checkIndent / $this->tabWidth), floor($tokenIndent / $this->tabWidth)); } else { $error .= '%s spaces, found %s'; $data = array($checkIndent, $tokenIndent); } if ($this->debug === true) { $line = $tokens[$checkToken]['line']; $message = vsprintf($error, $data); echo "[Line {$line}] {$message}" . PHP_EOL; } $fix = $phpcsFile->addFixableError($error, $checkToken, $type, $data); if ($fix === true || $this->debug === true) { $padding = ''; if ($this->tabIndent === true) { $numTabs = floor($checkIndent / $this->tabWidth); if ($numTabs > 0) { $numSpaces = $checkIndent - $numTabs * $this->tabWidth; $padding = str_repeat("\t", $numTabs) . str_repeat(' ', $numSpaces); } } else { if ($checkIndent > 0) { $padding = str_repeat(' ', $checkIndent); } } if ($checkToken === $i) { $accepted = $phpcsFile->fixer->replaceToken($checkToken, $padding . $trimmed); } else { // Easier to just replace the entire indent. $accepted = $phpcsFile->fixer->replaceToken($checkToken - 1, $padding); } if ($accepted === true) { $adjustments[$checkToken] = $checkIndent - $tokenIndent; if ($this->debug === true) { $line = $tokens[$checkToken]['line']; $type = $tokens[$checkToken]['type']; echo "\t=> Add adjustment of " . $adjustments[$checkToken] . " for token {$checkToken} ({$type}) on line {$line}" . PHP_EOL; } } } else { // Assume the change would be applied and continue // checking indents under this assumption. This gives more // technically accurate error messages. $adjustments[$checkToken] = $checkIndent - $tokenIndent; } //end if } //end if if ($checkToken !== null) { $i = $checkToken; } // Completely skip here/now docs as the indent is a part of the // content itself. if ($tokens[$i]['code'] === T_START_HEREDOC || $tokens[$i]['code'] === T_START_NOWDOC) { $i = $phpcsFile->findNext(array(T_END_HEREDOC, T_END_NOWDOC), $i + 1); continue; } // Completely skip multi-line strings as the indent is a part of the // content itself. if ($tokens[$i]['code'] === T_CONSTANT_ENCAPSED_STRING || $tokens[$i]['code'] === T_DOUBLE_QUOTED_STRING) { $i = $phpcsFile->findNext($tokens[$i]['code'], $i + 1, null, true); $i--; continue; } // Completely skip doc comments as they tend to have complex // indentation rules. if ($tokens[$i]['code'] === T_DOC_COMMENT_OPEN_TAG) { $i = $tokens[$i]['comment_closer']; continue; } // Open tags reset the indent level. if ($tokens[$i]['code'] === T_OPEN_TAG || $tokens[$i]['code'] === T_OPEN_TAG_WITH_ECHO) { if ($this->debug === true) { $line = $tokens[$i]['line']; echo "Open PHP tag found on line {$line}" . PHP_EOL; } if ($checkToken === null) { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true); $currentIndent = strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content'])); } else { $currentIndent = $tokens[$i]['column'] - 1; } $lastOpenTag = $i; if (isset($adjustments[$i]) === true) { $currentIndent += $adjustments[$i]; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $setIndents[$i] = $currentIndent; if ($this->debug === true) { $type = $tokens[$i]['type']; echo "\t=> indent set to {$currentIndent} by token {$i} ({$type})" . PHP_EOL; } continue; } //end if // Close tags reset the indent level, unless they are closing a tag // opened on the same line. if ($tokens[$i]['code'] === T_CLOSE_TAG) { if ($this->debug === true) { $line = $tokens[$i]['line']; echo "Close PHP tag found on line {$line}" . PHP_EOL; } if ($tokens[$lastOpenTag]['line'] !== $tokens[$i]['line']) { $currentIndent = $tokens[$i]['column'] - 1; $lastCloseTag = $i; } else { if ($lastCloseTag === null) { $currentIndent = 0; } else { $currentIndent = $tokens[$lastCloseTag]['column'] - 1; } } if (isset($adjustments[$i]) === true) { $currentIndent += $adjustments[$i]; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $setIndents[$i] = $currentIndent; if ($this->debug === true) { $type = $tokens[$i]['type']; echo "\t=> indent set to {$currentIndent} by token {$i} ({$type})" . PHP_EOL; } continue; } //end if // Anon classes and functions set the indent based on their own indent level. if ($tokens[$i]['code'] === T_CLOSURE || $tokens[$i]['code'] === T_ANON_CLASS) { $closer = $tokens[$i]['scope_closer']; if ($tokens[$i]['line'] === $tokens[$closer]['line']) { if ($this->debug === true) { $type = str_replace('_', ' ', strtolower(substr($tokens[$i]['type'], 2))); $line = $tokens[$i]['line']; echo "* ignoring single-line {$type} on line {$line}" . PHP_EOL; } $i = $closer; continue; } if ($this->debug === true) { $type = str_replace('_', ' ', strtolower(substr($tokens[$i]['type'], 2))); $line = $tokens[$i]['line']; echo "Open {$type} on line {$line}" . PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true); $currentIndent = $tokens[$first]['column'] - 1 + $this->indent; if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (floor($currentIndent / $this->indent) * $this->indent); $i = $tokens[$i]['scope_opener']; $setIndents[$i] = $currentIndent; if ($this->debug === true) { $type = $tokens[$i]['type']; echo "\t=> indent set to {$currentIndent} by token {$i} ({$type})" . PHP_EOL; } continue; } //end if // Scope openers increase the indent level. if (isset($tokens[$i]['scope_condition']) === true && isset($tokens[$i]['scope_opener']) === true && $tokens[$i]['scope_opener'] === $i) { $closer = $tokens[$i]['scope_closer']; if ($tokens[$i]['line'] === $tokens[$closer]['line']) { if ($this->debug === true) { $line = $tokens[$i]['line']; $type = $tokens[$i]['type']; echo "* ignoring single-line {$type} on line {$line}" . PHP_EOL; } $i = $closer; continue; } $condition = $tokens[$tokens[$i]['scope_condition']]['code']; if (isset(Tokens::$scopeOpeners[$condition]) === true && in_array($condition, $this->nonIndentingScopes) === false) { if ($this->debug === true) { $line = $tokens[$i]['line']; $type = $tokens[$tokens[$i]['scope_condition']]['type']; echo "Open scope ({$type}) on line {$line}" . PHP_EOL; } $currentIndent += $this->indent; $setIndents[$i] = $currentIndent; $openScopes[$tokens[$i]['scope_closer']] = $tokens[$i]['scope_condition']; if ($this->debug === true) { $type = $tokens[$i]['type']; echo "\t=> indent set to {$currentIndent} by token {$i} ({$type})" . PHP_EOL; } continue; } } //end if // JS objects set the indent level. if ($phpcsFile->tokenizerType === 'JS' && $tokens[$i]['code'] === T_OBJECT) { $closer = $tokens[$i]['bracket_closer']; if ($tokens[$i]['line'] === $tokens[$closer]['line']) { if ($this->debug === true) { $line = $tokens[$i]['line']; echo "* ignoring single-line JS object on line {$line}" . PHP_EOL; } $i = $closer; continue; } if ($this->debug === true) { $line = $tokens[$i]['line']; echo "Open JS object on line {$line}" . PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true); $currentIndent = $tokens[$first]['column'] - 1 + $this->indent; if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $setIndents[$first] = $currentIndent; if ($this->debug === true) { $type = $tokens[$first]['type']; echo "\t=> indent set to {$currentIndent} by token {$first} ({$type})" . PHP_EOL; } continue; } //end if // Closing an anon class or function. if (isset($tokens[$i]['scope_condition']) === true && $tokens[$i]['scope_closer'] === $i && ($tokens[$tokens[$i]['scope_condition']]['code'] === T_CLOSURE || $tokens[$tokens[$i]['scope_condition']]['code'] === T_ANON_CLASS)) { if ($this->debug === true) { $type = str_replace('_', ' ', strtolower(substr($tokens[$tokens[$i]['scope_condition']]['type'], 2))); $line = $tokens[$i]['line']; echo "Close {$type} on line {$line}" . PHP_EOL; } $prev = false; $object = 0; if ($phpcsFile->tokenizerType === 'JS') { $conditions = $tokens[$i]['conditions']; krsort($conditions, SORT_NUMERIC); foreach ($conditions as $token => $condition) { if ($condition === T_OBJECT) { $object = $token; break; } } if ($this->debug === true && $object !== 0) { $line = $tokens[$object]['line']; echo "\t* token is inside JS object {$object} on line {$line} *" . PHP_EOL; } } $parens = 0; if (isset($tokens[$i]['nested_parenthesis']) === true && empty($tokens[$i]['nested_parenthesis']) === false) { end($tokens[$i]['nested_parenthesis']); $parens = key($tokens[$i]['nested_parenthesis']); if ($this->debug === true) { $line = $tokens[$parens]['line']; echo "\t* token has nested parenthesis {$parens} on line {$line} *" . PHP_EOL; } } $condition = 0; if (isset($tokens[$i]['conditions']) === true && empty($tokens[$i]['conditions']) === false) { end($tokens[$i]['conditions']); $condition = key($tokens[$i]['conditions']); if ($this->debug === true) { $line = $tokens[$condition]['line']; $type = $tokens[$condition]['type']; echo "\t* token is inside condition {$condition} ({$type}) on line {$line} *" . PHP_EOL; } } if ($parens > $object && $parens > $condition) { if ($this->debug === true) { echo "\t* using parenthesis *" . PHP_EOL; } $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, $parens - 1, null, true); $object = 0; $condition = 0; } else { if ($object > 0 && $object >= $condition) { if ($this->debug === true) { echo "\t* using object *" . PHP_EOL; } $prev = $object; $parens = 0; $condition = 0; } else { if ($condition > 0) { if ($this->debug === true) { echo "\t* using condition *" . PHP_EOL; } $prev = $condition; $object = 0; $parens = 0; } } } //end if if ($prev === false) { $prev = $phpcsFile->findPrevious(array(T_EQUAL, T_RETURN), $tokens[$i]['scope_condition'] - 1, null, false, null, true); if ($prev === false) { $prev = $i; if ($this->debug === true) { echo "\t* could not find a previous T_EQUAL or T_RETURN token; will use current token *" . PHP_EOL; } } } if ($this->debug === true) { $line = $tokens[$prev]['line']; $type = $tokens[$prev]['type']; echo "\t* previous token is {$type} on line {$line} *" . PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); if ($this->debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* first token on line {$line} is {$first} ({$type}) *" . PHP_EOL; } $prev = $phpcsFile->findStartOfStatement($first); if ($prev !== $first) { // This is not the start of the statement. if ($this->debug === true) { $line = $tokens[$prev]['line']; $type = $tokens[$prev]['type']; echo "\t* amended previous is {$type} on line {$line} *" . PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); if ($this->debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* amended first token is {$first} ({$type}) on line {$line} *" . PHP_EOL; } } $currentIndent = $tokens[$first]['column'] - 1; if ($object > 0 || $condition > 0) { $currentIndent += $this->indent; } if (isset($tokens[$first]['scope_closer']) === true && $tokens[$first]['scope_closer'] === $first) { if ($this->debug === true) { echo "\t* first token is a scope closer *" . PHP_EOL; } if ($condition === 0 || $tokens[$condition]['scope_opener'] < $first) { $currentIndent = $setIndents[$first]; } else { if ($this->debug === true) { echo "\t* ignoring scope closer *" . PHP_EOL; } } } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $setIndents[$first] = $currentIndent; if ($this->debug === true) { $type = $tokens[$first]['type']; echo "\t=> indent set to {$currentIndent} by token {$first} ({$type})" . PHP_EOL; } } //end if } //end for // Don't process the rest of the file. return $phpcsFile->numTokens; }
/** * Should this test be skipped for some reason. * * @return void */ protected function shouldSkipTest() { $jslPath = Config::getExecutablePath('jslint'); return is_null($jslPath); }
/** * Performs the run. * * @return int The number of errors and warnings found. */ private function run() { // The class that manages all reporters for the run. $this->reporter = new Reporter($this->config); // Include bootstrap files. foreach ($this->config->bootstrap as $bootstrap) { include $bootstrap; } if ($this->config->stdin === true) { $fileContents = $this->config->stdinContent; if ($fileContents === null) { $handle = fopen('php://stdin', 'r'); stream_set_blocking($handle, true); $fileContents = stream_get_contents($handle); fclose($handle); } $todo = new FileList($this->config, $this->ruleset); $dummy = new DummyFile($fileContents, $this->ruleset, $this->config); $todo->addFile($dummy->path, $dummy); $numFiles = 1; } else { if (empty($this->config->files) === true) { echo 'ERROR: You must supply at least one file or directory to process.' . PHP_EOL . PHP_EOL; $this->config->printUsage(); exit(0); } if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Creating file list... '; } $todo = new FileList($this->config, $this->ruleset); $numFiles = count($todo); if (PHP_CODESNIFFER_VERBOSITY > 0) { echo "DONE ({$numFiles} files in queue)" . PHP_EOL; } if ($this->config->cache === true) { if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Loading cache... '; } Cache::load($this->ruleset, $this->config); if (PHP_CODESNIFFER_VERBOSITY > 0) { $size = Cache::getSize(); echo "DONE ({$size} files in cache)" . PHP_EOL; } } } //end if // Turn all sniff errors into exceptions. set_error_handler(array($this, 'handleErrors')); // If verbosity is too high, turn off parallelism so the // debug output is clean. if (PHP_CODESNIFFER_VERBOSITY > 1) { $this->config->parallel = 1; } // If the PCNTL extension isn't installed, we can't fork. if (function_exists('pcntl_fork') === false) { $this->config->parallel = 1; } $lastDir = ''; if ($this->config->parallel === 1) { // Running normally. $numProcessed = 0; foreach ($todo as $path => $file) { $currDir = dirname($path); if ($lastDir !== $currDir) { if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Changing into directory ' . Common::stripBasepath($currDir, $this->config->basepath) . PHP_EOL; } $lastDir = $currDir; } $this->processFile($file); $numProcessed++; $this->printProgress($file, $numFiles, $numProcessed); } } else { // Batching and forking. $childProcs = array(); $numFiles = count($todo); $numPerBatch = ceil($numFiles / $this->config->parallel); for ($batch = 0; $batch < $this->config->parallel; $batch++) { $startAt = $batch * $numPerBatch; if ($startAt >= $numFiles) { break; } $endAt = $startAt + $numPerBatch; if ($endAt > $numFiles) { $endAt = $numFiles; } $childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child'); $pid = pcntl_fork(); if ($pid === -1) { throw new RuntimeException('Failed to create child process'); } else { if ($pid !== 0) { $childProcs[] = array('pid' => $pid, 'out' => $childOutFilename); } else { // Move forward to the start of the batch. $todo->rewind(); for ($i = 0; $i < $startAt; $i++) { $todo->next(); } // Reset the reporter to make sure only figures from this // file batch are recorded. $this->reporter->totalFiles = 0; $this->reporter->totalErrors = 0; $this->reporter->totalWarnings = 0; $this->reporter->totalFixable = 0; // Process the files. $pathsProcessed = array(); ob_start(); for ($i = $startAt; $i < $endAt; $i++) { $path = $todo->key(); $file = $todo->current(); $currDir = dirname($path); if ($lastDir !== $currDir) { if (PHP_CODESNIFFER_VERBOSITY > 0) { echo 'Changing into directory ' . Common::stripBasepath($currDir, $this->config->basepath) . PHP_EOL; } $lastDir = $currDir; } $this->processFile($file); $pathsProcessed[] = $path; $todo->next(); } $debugOutput = ob_get_contents(); ob_end_clean(); // Write information about the run to the filesystem // so it can be picked up by the main process. $childOutput = array('totalFiles' => $this->reporter->totalFiles, 'totalErrors' => $this->reporter->totalErrors, 'totalWarnings' => $this->reporter->totalWarnings, 'totalFixable' => $this->reporter->totalFixable, 'totalFixed' => $this->reporter->totalFixed); $output = '<' . '?php' . "\n" . ' $childOutput = '; $output .= var_export($childOutput, true); $output .= ";\n\$debugOutput = "; $output .= var_export($debugOutput, true); if ($this->config->cache === true) { $childCache = array(); foreach ($pathsProcessed as $path) { $childCache[$path] = Cache::get($path); } $output .= ";\n\$childCache = "; $output .= var_export($childCache, true); } $output .= ";\n?" . '>'; file_put_contents($childOutFilename, $output); exit($pid); } } //end if } //end for $this->processChildProcs($childProcs); } //end if restore_error_handler(); if (PHP_CODESNIFFER_VERBOSITY === 0 && $this->config->interactive === false && $this->config->showProgress === true) { echo PHP_EOL . PHP_EOL; } if ($this->config->cache === true) { Cache::save(); } $ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit'); $ignoreErrors = Config::getConfigData('ignore_errors_on_exit'); $return = $this->reporter->totalErrors + $this->reporter->totalWarnings; if ($ignoreErrors !== null) { $ignoreErrors = (bool) $ignoreErrors; if ($ignoreErrors === true) { $return -= $this->reporter->totalErrors; } } if ($ignoreWarnings !== null) { $ignoreWarnings = (bool) $ignoreWarnings; if ($ignoreWarnings === true) { $return -= $this->reporter->totalWarnings; } } return $return; }
/** * Should this test be skipped for some reason. * * @return void */ protected function shouldSkipTest() { $analyzerPath = Config::getExecutablePath('zend_ca'); return is_null($analyzerPath); }