CodeCheck.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\TestFramework\Utility;
  7. /**
  8. * Searches for usage of classes, namespaces, functions, etc in PHP files
  9. */
  10. class CodeCheck
  11. {
  12. /**
  13. * Check if the class is used in the content
  14. *
  15. * @param string $className
  16. * @param string $content
  17. * @return bool
  18. */
  19. public static function isClassUsed($className, $content)
  20. {
  21. /* avoid matching namespace instead of class */
  22. $content = preg_replace('/namespace[^;]+;/', '', $content);
  23. return self::_isRegexpMatched('/[^a-z\d_\$]' . preg_quote($className, '/') . '[^a-z\d_\\\\]/iS', $content);
  24. }
  25. /**
  26. * Check if the namespace is used in the content
  27. *
  28. * @param string $namespace
  29. * @param string $content
  30. * @return bool
  31. */
  32. public static function isNamespaceUsed($namespace, $content)
  33. {
  34. return self::_isRegexpMatched('/namespace\s+' . preg_quote($namespace, '/') . ';/S', $content)
  35. || self::_isRegexpMatched('/[^a-zA-Z\d_]' . preg_quote($namespace . '\\', '/') . '/S', $content);
  36. }
  37. /**
  38. * Check if the specified function is called in the content.
  39. * Note that declarations are not considered.
  40. *
  41. * If class context is not specified, invocation of all functions or methods (of any class)
  42. * will be matched across the board
  43. *
  44. * If some class is specified, only its methods will be matched as follows:
  45. * - usage of class::method
  46. * - usage of $this, self and static within the class and its descendants
  47. *
  48. * @param string $method
  49. * @param string $content
  50. * @param string $class
  51. * @return bool
  52. */
  53. public static function isFunctionCalled($method, $content, $class = null)
  54. {
  55. $quotedMethod = preg_quote($method, '/');
  56. if (!$class) {
  57. return self::_isRegexpMatched(
  58. '/(?<![a-z\d_:]|->|function\s)' . $quotedMethod . '\s*\(/iS',
  59. $content
  60. );
  61. }
  62. // without opening parentheses to match static callbacks notation
  63. if (self::_isRegexpMatched(
  64. '/' . preg_quote($class, '/') . '::\s*' . $quotedMethod . '[^a-z\d_]/iS',
  65. $content
  66. )
  67. ) {
  68. return true;
  69. }
  70. if (self::isClassOrInterface($content, $class) || self::isDirectDescendant($content, $class)) {
  71. return self::_isRegexpMatched('/this->' . $quotedMethod . '\s*\(/iS', $content)
  72. || self::_isRegexpMatched(
  73. '/(self|static|parent)::\s*' . $quotedMethod . '\s*\(/iS',
  74. $content
  75. );
  76. }
  77. }
  78. /**
  79. * Check if methods or functions are used in the content
  80. *
  81. * If class context is specified, only the class and its descendants will be checked.
  82. *
  83. * @param string $property
  84. * @param string $content
  85. * @param string $class
  86. * @return bool
  87. */
  88. public static function isPropertyUsed($property, $content, $class = null)
  89. {
  90. if ($class) {
  91. if (!self::isClassOrInterface($content, $class) && !self::isDirectDescendant($content, $class)) {
  92. return false;
  93. }
  94. }
  95. return self::_isRegexpMatched(
  96. '/[^a-z\d_]' . preg_quote($property, '/') . '[^a-z\d_]/iS',
  97. $content
  98. );
  99. }
  100. /**
  101. * Analyze content to determine whether it is a declaration of the specified class/interface
  102. *
  103. * @param string $content
  104. * @param string $name
  105. * @return bool
  106. */
  107. public static function isClassOrInterface($content, $name)
  108. {
  109. return self::_isRegexpMatched('/\b(?:class|interface)\s+' . preg_quote($name, '/') . '\b[^{]*\{/iS', $content);
  110. }
  111. /**
  112. * Analyze content to determine whether this is a direct descendant of the specified class/interface
  113. *
  114. * @param string $content
  115. * @param string $name
  116. * @return bool
  117. */
  118. public static function isDirectDescendant($content, $name)
  119. {
  120. $name = preg_quote($name, '/');
  121. return self::_isRegexpMatched(
  122. '/\s+extends\s+\\\\?' . $name . '\b|\s+implements\s+[^{]*\b' . $name . '\b[^{^\\\\]*\{/iS',
  123. $content
  124. );
  125. }
  126. /**
  127. * Check if content matches the regexp
  128. *
  129. * @param string $regexp
  130. * @param string $content
  131. * @throws \Exception
  132. * @return bool True if the content matches the regexp
  133. */
  134. protected static function _isRegexpMatched($regexp, $content)
  135. {
  136. $result = preg_match($regexp, $content);
  137. if ($result === false) {
  138. throw new \Exception('An error occurred when matching regexp "' . $regexp . '""');
  139. }
  140. return 1 === $result;
  141. }
  142. }