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

https://github.com/jasonweng/forkcms · PHP · 193 lines · 128 code · 27 blank · 38 comment · 48 complexity · 06d0bdcadeaeedcb3cdf75eaed7ea1a4 MD5 · raw file

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