/** * With lots of various rules we created 4 various rulesets for operators. If one * of the tokens or content is found we use the given regex on the joined array. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $tokens = $testable->tokens(); foreach ($this->inspector as $inspector) { if (isset($inspector['tokens'])) { $byToken = $testable->findAll($inspector['tokens']); } else { $byToken = array(); } if (isset($inspector['content'])) { $byContent = $testable->findAllContent($inspector['content']); } else { $byContent = array(); } foreach (array_merge($byToken, $byContent) as $id) { $token = $tokens[$id]; $isPHP = $testable->isPHP($token['line']); if ($isPHP && empty($token['isString'])) { $pattern = String::insert($inspector['regex'], array('content' => preg_quote($token['content'], "/"))); $firstId = $id - $inspector['relativeTokens']['before']; $firstId = $firstId < 0 ? 0 : $firstId; $length = $inspector['relativeTokens']['length']; $inspectTokens = array_slice($tokens, $firstId, $length); $html = null; foreach ($inspectTokens as $htmlToken) { $html .= $htmlToken['content']; } if (preg_match($pattern, $html) === 0) { $this->addViolation(array('message' => String::insert($inspector['message'], $token), 'line' => $token['line'])); } } } } }
/** * Will iterate the given tokens finding them based on the keys of self::$_tokenMap. * Upon finding the matching tokens it will attempt to match the line against a regular * expression proivded in tokenMap and if none are found add a violation from the message * provided in tokenMap. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $lines = $testable->lines(); $tokens = $testable->tokens(); $filtered = $testable->findAll(array_keys($this->_tokenMap)); foreach ($filtered as $tokenId) { $token = $tokens[$tokenId]; $tokenMap = $this->_tokenMap[$token['id']]; $patterns = $tokenMap['patterns']; $body = $this->_extractContent($tokenId, $tokens); $singleLine = $this->_matchPattern($patterns, $body); $multiLine = false; if (!$singleLine) { foreach ($patterns as $key => $value) { $patterns[$key] .= 's'; } $multiLine = $this->_matchPattern($patterns, $body); } if (!$singleLine && !$multiLine) { $this->addViolation(array('message' => $this->_tokenMap[$token['id']]['message'], 'line' => $token['line'])); } elseif (!$singleLine) { $this->addWarning(array('message' => $this->_tokenMap[$token['id']]['message'] . ' on a single line.', 'line' => $token['line'])); } } }
/** * Will iterate over each line checking if tabs are only first * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $message = 'Tabs can only appear at the beginning of the line'; $lines = $testable->lines(); $tokens = $testable->tokens(); $currLine = 1; $allowTabs = true; foreach ($tokens as $token) { $content = str_replace("\r\n", "\n", $token['content']); $isNewLine = $token['line'] > $currLine || $token['id'] === T_WHITESPACE && preg_match('/\\n/', $content); if ($isNewLine) { $currLine = $token['line']; $allowTabs = true; } if ($token['id'] !== T_WHITESPACE) { $allowTabs = false; continue; } if ($allowTabs) { $isInvalidTab = !preg_match('/^(\\n?\\t?)+ *$/', $content); } else { $isInvalidTab = preg_match('/\\t/', $content); } if ($isInvalidTab) { $this->addViolation(array('message' => $message, 'line' => $token['line'])); } } }
/** * Iterates over T_USE tokens, gets the aliased name into an array and * validates it was used within the script. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $tokens = $testable->tokens(); $lines = $testable->lines(); $typeCache = $testable->typeCache(); $matches = array(); if (!isset($typeCache[T_USE])) { return; } foreach ($typeCache[T_USE] as $tokenId) { $token = $tokens[$tokenId]; $line = $lines[$token['line'] - 1]; if (preg_match('/^use (?:([^ ]+ as )|(.*\\\\))?(.*);$/i', $line, $matches) === 1) { $count = 0; if (in_array($matches[3], $this->_ignored)) { continue; } foreach ($typeCache[T_STRING] as $stringId) { if (strcasecmp($tokens[$stringId]['content'], $matches[3]) === 0) { $count++; } if ($count === 2) { break; } } if ($count < 2) { $this->addViolation(array('message' => 'Class ' . $matches[3] . ' was never called', 'line' => $token['line'])); } } } }
/** * Will iterate over each line checking if any weak comparison operators * are used within the code. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $tokens = $testable->tokens(); $message = 'Weak comparison operator {:key} used, try {:value} instead'; $filtered = $testable->findAll(array_keys($this->inspectableTokens)); foreach ($filtered as $id) { $token = $tokens[$id]; $this->addWarning(array('message' => String::insert($message, array('key' => token_name($token['id']), 'value' => $this->inspectableTokens[$token['id']])), 'line' => $token['line'])); } }
/** * Will iterate tokens looking for comments and if found will determine the regex * to test the comment against. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $tokens = $testable->tokens(); $lines = $testable->lines(); $lineCache = $testable->lineCache(); $inspectable = array(T_CLASS, T_VARIABLE, T_FUNCTION, T_CONST, T_DOUBLE_COLON); foreach ($testable->findAll(array(T_DOC_COMMENT)) as $tokenId) { $token = $tokens[$tokenId]; $nextLine = $token['line'] + count(preg_split('/\\r\\n|\\r|\\n/', $token['content'])); $parentId = false; if (isset($lineCache[$nextLine])) { $parentId = $testable->findNext($inspectable, $lineCache[$nextLine]); } if ($parentId === false && $token['line'] !== 2) { $this->addViolation(array('message' => 'Docblocks should only be at the beginning of the page or ' . 'before a class/function or static call.', 'line' => $token['line'])); continue; } $parent = $tokens[$parentId]; $content = null; if ($token['line'] === 2) { $match = 'PAGE'; } else { switch ($parent['id']) { case T_CLASS: $match = 'CLASS'; break; case T_DOUBLE_COLON: case T_FUNCTION: $match = 'METHOD'; break; case T_VARIABLE: case T_CONST: $match = 'VARIABLE'; break; } } if (in_array($parent['id'], array(T_FUNCTION, T_VARIABLE), true)) { $content .= $tokens[$tokenId - 1]['content']; } $content .= $token['content']; $pattern = $this->compilePattern($match); $correctFormat = preg_match($this->compilePattern($match), $content) === 1; $hasTags = preg_match($this->compilePattern('HAS_TAGS'), $content) === 1; $correctTagFormat = preg_match($this->compilePattern('TAG_FORMAT'), $content) === 1; if (!$correctFormat) { $this->addViolation(array('message' => 'Docblocks are in the incorrect format.', 'line' => $token['line'])); } elseif ($hasTags && !$correctTagFormat) { $this->addViolation(array('hasTags' => (int) $hasTags, 'correctTagFormat' => (int) $correctTagFormat, 'tagFormat' => $this->compilePattern('TAG_FORMAT'), 'message' => 'Tags should be last and have a blank docblock line.', 'line' => $token['line'])); } } }
/** * Will iterate tokens looking for comments and if found will determine the regex * to test the comment against. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $tokens = $testable->tokens(); $comments = $testable->findAll(array(T_COMMENT)); foreach ($comments as $tokenId) { $token = $tokens[$tokenId]; $parentId = $tokens[$tokenId]['parent']; if ($parentId === -1 || $tokens[$parentId]['id'] !== T_FUNCTION) { $this->addViolation(array('message' => 'Inline comments should never appear.', 'line' => $token['line'])); } elseif (preg_match('/^test/', Parser::label($parentId, $tokens)) === 0) { $this->addViolation(array('message' => 'Inline comments should only appear in testing methods.', 'line' => $token['line'])); } } }
/** * Iterates tokens looking for cast tokens then testing against a regex * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $message = 'Casting in the incorrect format, try: "(array) $object;"'; $tokens = $testable->tokens(); $filtered = $testable->findAll($this->tokens); foreach ($filtered as $id) { $token = $tokens[$id]; $html = ''; foreach (array_slice($tokens, $id, 3) as $t) { $html .= $t['content']; } if (preg_match($this->pattern, $html) === 0) { $this->addViolation(array('message' => $message, 'line' => $token['line'])); } } }
/** * Will iterate tokens looking for comments and if found will determine the regex * to test the comment against. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $tokens = $testable->tokens(); $filtered = $testable->findAll(array(T_VARIABLE)); $message = 'Superglobal usage detected in class'; $currLine = 1; foreach ($filtered as $key) { $token = $tokens[$key]; if (preg_match(static::PATTERN, $token['content'])) { $isNewLine = $token['line'] > $currLine; $currLine = $token['line']; if (!$isNewLine) { continue; } $this->addViolation(array('message' => $message, 'line' => $token['line'])); } } }
/** * Will iterate the tokens looking for a T_CLASS which should be * in CamelCase and match the file name * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $tokens = $testable->tokens(); $pathinfo = pathinfo($testable->config('path')); if ($pathinfo['extension'] !== 'php') { return; } $filtered = $testable->findAll(array(T_CLASS)); foreach ($filtered as $key) { $token = $tokens[$key]; $className = $tokens[$key + 2]['content']; if ($className !== Inflector::camelize($className)) { $this->addViolation(array('message' => 'Class name is not in CamelCase style', 'line' => $token['line'])); } elseif ($className !== $pathinfo['filename']) { $this->addViolation(array('message' => 'Class name and file name should match', 'line' => $token['line'])); } } }
/** * Will iterate the lines until it finds one with $requiredTokens * Once found it will find all short tags using $findPattern and * match against them using $matchedPattern * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $message = 'Inline HTML should be in the following format: "<?=$var; ?>"'; $lines = $testable->lines(); $tokens = $testable->tokens(); $lineCache = $testable->lineCache(); $matches = array(); foreach ($lines as $lineNumber => $line) { $lineTokens = isset($lineCache[$lineNumber]) ? $lineCache[$lineNumber] : array(); if ($this->hasRequiredTokens($tokens, $lineTokens)) { preg_match_all($this->findPattern, $line, $matches); foreach ($matches as $match) { if (isset($match[0]) && preg_match($this->matchPattern, $match[0]) === 0) { $this->addViolation(array('message' => $message, 'line' => $lineNumber)); } } } } }
/** * Will iterate the tokens looking for functions validating they have the * correct camelBack naming style. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $tokens = $testable->tokens(); $filtered = $testable->findAll(array(T_FUNCTION)); foreach ($filtered as $key) { $token = $tokens[$key]; $label = Parser::label($key, $tokens); $modifiers = Parser::modifiers($key, $tokens); $isClosure = Parser::closure($key, $tokens); if (in_array($label, $this->_magicMethods)) { continue; } if ($testable->findNext(array(T_PROTECTED), $modifiers) !== false) { $label = preg_replace('/^_/', '', $label); } if (!$isClosure && $label !== Inflector::camelize($label, false)) { $this->addViolation(array('message' => 'Function "' . $label . '" is not in camelBack style', 'line' => $tokens[$tokens[$key]['parent']]['line'])); } } }
/** * Will iterate all the tokens looking for tokens in inspectableTokens * The token needs an access modifier if it is a T_FUNCTION or T_VARIABLE * and is in the first level of T_CLASS. This prevents functions and variables * inside methods and outside classes to register violations. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $message = '{:name} has no declared visibility.'; $tokens = $testable->tokens(); $classes = $testable->findAll(array(T_CLASS)); $filtered = $testable->findAll($this->inspectableTokens); foreach ($classes as $classId) { $children = $tokens[$classId]['children']; foreach ($children as $member) { if (!in_array($member, $filtered)) { continue; } $modifiers = Parser::modifiers($member, $tokens); $visibility = $testable->findNext($this->findTokens, $modifiers); if ($visibility === false) { $token = $tokens[$member]; $this->addViolation(array('modifiers' => $modifiers, 'message' => String::insert($message, $token), 'line' => $token['line'])); } } } }
/** * Will iterate over each line checking for blank lines * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $lines = $testable->lines(); $tokens = $testable->tokens(); $lastBlankLineId = -2; foreach ($lines as $lineId => $line) { $lineNumber = $lineId + 1; $ignore = false; $key = $testable->findTokenByLine($lineNumber); if (isset($tokens[$key])) { $token = $tokens[$key]; $ignore = in_array($token['id'], $this->ignoreableTokens, true); } if (!$ignore && preg_match('/^$/', $line) === 1) { if ($lastBlankLineId + 1 === $lineId) { $this->addViolation(array('message' => 'Multiple blank lines.', 'line' => $lineNumber)); } $lastBlankLineId = $lineId; } } }
/** * Will iterate the tokens looking for protected methods and variables, once * found it will validate the name of it's parent starts with an underscore. * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $message = 'Protected method {:name} must start with an underscore'; $tokens = $testable->tokens(); $filtered = $testable->findAll(array(T_PROTECTED)); foreach ($filtered as $tokenId) { $token = $tokens[$tokenId]; $parent = $testable->findNext(array(T_FUNCTION, T_VARIABLE), $tokenId); $parentLabel = Parser::label($parent, $tokens); if (substr($parentLabel, 0, 1) !== '_') { $classTokenId = $testable->findNext(array(T_STRING), $token['parent']); $classname = $tokens[$classTokenId]['content']; $params = array('message' => String::insert($message, array('name' => $parentLabel)), 'line' => $token['line']); if ($this->_strictMode($classname)) { $this->addViolation($params); } else { $this->addWarning($params); } } } }
/** * Will iterate the tokens for $inspectableTokens, once found it'll find * the next content taht doesn't have the token $skipTokens and validate * it has no parentheses * * @param Testable $testable The testable object * @return void */ public function apply($testable, array $config = array()) { $message = 'Construct {:name} should not contain parentheses and be on its own line.'; $tokens = $testable->tokens(); $inspectable = $testable->findAll($this->inspectableTokens); $lines = $testable->lines(); foreach ($inspectable as $key) { $token = $tokens[$key]; $lineIndex = $token['line'] - 1; if (isset($lines[$lineIndex])) { $line = $lines[$lineIndex]; $next = $key + 1; if (in_array($token['id'], $this->ownLineTokens)) { $pattern = '/^' . $this->pattern . '$/'; } else { $pattern = '/' . $this->pattern . '$/'; } if (preg_match($pattern, $line) === 0) { $this->addViolation(array('message' => String::insert($message, array('name' => $token['name'])), 'line' => $token['line'])); } } } }