/tools/codesniffer/Fork/Sniffs/Styleguide/ClassesSniff.php

https://github.com/zakgrant/forkcms · PHP · 233 lines · 135 code · 44 blank · 54 comment · 73 complexity · f2489d61e8975e12d6c1c9e227403602 MD5 · raw file

  1. <?php
  2. /**
  3. * Fork_Sniffs_Styleguide_ClassesSniff
  4. * Checks if classes are meeting the styleguide
  5. *
  6. * @author Tijs Verkoyen <tijs@sumocoders.be>
  7. */
  8. class Fork_Sniffs_Styleguide_ClassesSniff implements PHP_CodeSniffer_Sniff
  9. {
  10. private static $functions = array();
  11. private static $classesWithErrors = array();
  12. /**
  13. * Process the code
  14. *
  15. * @return void
  16. * @param PHP_CodeSniffer_File $phpcsFile The codesniffer file.
  17. * @param mixed $stackPtr The stackpointer.
  18. */
  19. public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
  20. {
  21. // get the tokens
  22. $tokens = $phpcsFile->getTokens();
  23. $current = $tokens[$stackPtr];
  24. $lines = file($phpcsFile->getFilename());
  25. $previous = $tokens[$stackPtr - 1];
  26. $next = $tokens[$stackPtr + 1];
  27. // handle all types
  28. switch($current['code'])
  29. {
  30. case T_CLASS:
  31. case T_INTERFACE:
  32. $nextClass = false;
  33. if(isset($current['scope_closer'])) $nextClass = $phpcsFile->findNext(T_CLASS, $current['scope_closer']);
  34. // multiple classes in one file
  35. if($nextClass !== false)
  36. {
  37. if(!($lines[$tokens[$current['scope_closer']]['line']] == "\n" && $lines[$tokens[$current['scope_closer']]['line'] + 1] == "\n" && trim($lines[$tokens[$current['scope_closer']]['line'] + 2]) != ''))
  38. {
  39. $phpcsFile->addError('Expected 2 empty lines after a class.', $stackPtr);
  40. }
  41. }
  42. if($next['code'] != T_WHITESPACE) $phpcsFile->addError('Space expected after class, interface', $stackPtr);
  43. // find comment
  44. if($phpcsFile->findPrevious(T_DOC_COMMENT, $stackPtr, $stackPtr - 4) === false) $phpcsFile->addError('PHPDoc expected before class', $stackPtr);
  45. // get classname
  46. $className = trim(str_replace('class', '', $lines[$current['line'] - 1]));
  47. // exceptions may have brackets on the same line
  48. if(substr_count($className, 'Exception') == 0)
  49. {
  50. if(($tokens[$current['scope_opener']]['line'] - $current['line']) != 1) $phpcsFile->addError('Opening bracket should be at the next line', $stackPtr);
  51. if($tokens[$current['scope_opener']]['column'] != $tokens[$current['scope_closer']]['column']) $phpcsFile->addError('Closing brace should be at same column as opening brace.', $stackPtr);
  52. if($tokens[$current['scope_opener'] + 1]['content'] != "\n") $phpcsFile->addError('Content should be on a new line.', $stackPtr);
  53. if($tokens[$current['scope_opener'] + 1]['column'] != $tokens[$current['scope_closer']]['column'] + 1) $phpcsFile->addError('Content should be indented correctly', $stackPtr);
  54. }
  55. // is it a fork class?
  56. if(substr_count($className, 'Frontend') > 0 || substr_count($className, 'Backend') > 0 || substr_count($className, 'Api') > 0 || substr_count($className, 'Installer') > 0)
  57. {
  58. $folder = substr($phpcsFile->getFilename(), strpos($phpcsFile->getFilename(), DIRECTORY_SEPARATOR . 'default_www') + 1);
  59. $chunks = explode(DIRECTORY_SEPARATOR, $folder);
  60. $correctPackage = $chunks[1];
  61. $correctSubPackage = $chunks[2];
  62. if($correctSubPackage == 'modules') $correctSubPackage = $chunks[3];
  63. if(isset($chunks[4]) && $chunks[4] == 'installer') $correctPackage = 'installer';
  64. if($chunks[1] == 'install')
  65. {
  66. $correctPackage = 'install';
  67. $correctSubPackage = 'installer';
  68. }
  69. // get comment
  70. $startComment = (int) $phpcsFile->findPrevious(T_DOC_COMMENT, $stackPtr, null, null, '/**'."\n");
  71. $endComment = (int) $phpcsFile->findPrevious(T_DOC_COMMENT, $stackPtr, null, null, ' */');
  72. $hasPackage = false;
  73. $hasSubPackage = false;
  74. $hasAuthor = false;
  75. $hasSince = false;
  76. for($i = $startComment; $i <= $endComment; $i++)
  77. {
  78. // package
  79. if(substr($tokens[$i]['content'], 0, 11) == ' * @package')
  80. {
  81. // reset
  82. $hasPackage = true;
  83. // find part
  84. $content = trim(substr($tokens[$i]['content'], strrpos($tokens[$i]['content'], "\t")));
  85. // validate content
  86. if($content != $correctPackage) $phpcsFile->addError('Invalid value for @package', $i);
  87. // validate syntax
  88. if(substr($tokens[$i]['content'], 11, 1) != "\t") $phpcsFile->addError('After @package there should be at least one tab', $i);
  89. }
  90. // subpackage
  91. if(substr($tokens[$i]['content'], 0, 14) == ' * @subpackage')
  92. {
  93. // reset
  94. $hasSubPackage = true;
  95. // find part
  96. $content = trim(substr($tokens[$i]['content'], strrpos($tokens[$i]['content'], "\t")));
  97. // validate content
  98. if(substr_count($correctSubPackage, '.') == 0 && $content != $correctSubPackage) $phpcsFile->addError('Invalid value for @subpackage', $i);
  99. // validate syntax
  100. if(substr($tokens[$i]['content'], 14, 1) != "\t") $phpcsFile->addError('After @subpackage there should be at least one tab', $i);
  101. }
  102. // author
  103. if(substr($tokens[$i]['content'], 0, 10) == ' * @author')
  104. {
  105. // reset
  106. $hasAuthor = true;
  107. // validate syntax
  108. if(substr($tokens[$i]['content'], 10, 1) != "\t") $phpcsFile->addError('After @author there should be at least one tab', $i);
  109. // find part
  110. $content = trim(substr($tokens[$i]['content'], strrpos($tokens[$i]['content'], "\t")));
  111. // validate
  112. if(preg_match('/.*<.*@.*>$/', $content) != 1) $phpcsFile->addError('Invalid syntax for the @author-value', $i);
  113. }
  114. // since
  115. if(substr($tokens[$i]['content'], 0, 9) == ' * @since')
  116. {
  117. // reset
  118. $hasSince = true;
  119. // validate syntax
  120. if(substr($tokens[$i]['content'], 9, 1) != "\t") $phpcsFile->addError('After @since there should be at least one tab', $i);
  121. // find part
  122. $content = trim(substr($tokens[$i]['content'], strrpos($tokens[$i]['content'], "\t")));
  123. // validate
  124. if(preg_match('/^[0-9\.]*$/', $content) != 1) $phpcsFile->addError('Invalid syntax for the @since-value', $i);
  125. }
  126. }
  127. if(!$hasPackage) $phpcsFile->addError('No package found in PHPDoc', $startComment);
  128. if(!$hasSubPackage) $phpcsFile->addError('No subpackage found in PHPDoc', $startComment);
  129. if(!$hasAuthor) $phpcsFile->addError('No author found in PHPDoc', $startComment);
  130. if(!$hasSince) $phpcsFile->addError('No since found in PHPDoc', $startComment);
  131. }
  132. break;
  133. case T_EXTENDS:
  134. case T_IMPLEMENTS:
  135. if($previous['content'] != ' ') $phpcsFile->addError('Space excpected before extends, implements', $stackPtr);
  136. if($next['content'] != ' ') $phpcsFile->addError('Space expected after extends, implements', $stackPtr);
  137. break;
  138. case T_FUNCTION:
  139. $prevClass = $phpcsFile->findPrevious(T_CLASS, $stackPtr);
  140. if(isset($tokens[$prevClass]['scope_closer']) && $current['line'] <= $tokens[$tokens[$prevClass]['scope_closer']]['line'])
  141. {
  142. if($prevClass != 0)
  143. {
  144. // get class
  145. $className = strtolower($tokens[$prevClass + 2]['content']);
  146. if(!in_array($className, self::$classesWithErrors))
  147. {
  148. // add
  149. self::$functions[$className][] = strtolower($tokens[$stackPtr + 2]['content']);
  150. // copy to local
  151. $local = self::$functions[$className];
  152. // sort
  153. sort($local);
  154. // gte dif
  155. $diff = (array) array_diff_assoc($local, self::$functions[$className]);
  156. // check
  157. if(count($diff) > 0)
  158. {
  159. $phpcsFile->addError('The methods should be placed in alphabetical order.', $stackPtr);
  160. self::$classesWithErrors[] = $className;
  161. }
  162. }
  163. }
  164. }
  165. break;
  166. case T_CLONE:
  167. case T_NAMESPACE:
  168. case T_NS_SEPARATOR:
  169. // @later we don't use these elements at the moment
  170. break;
  171. }
  172. // cleanup
  173. unset($tokens);
  174. unset($current);
  175. unset($lines);
  176. unset($next);
  177. unset($previous);
  178. }
  179. /**
  180. * Register
  181. *
  182. * @return void
  183. */
  184. public function register()
  185. {
  186. return array(T_CLASS, T_EXTENDS, T_IMPLEMENTS, T_INTERFACE, T_NAMESPACE, T_NS_SEPARATOR, T_CLONE, T_FUNCTION);
  187. }
  188. }
  189. ?>