22 private $componentRegistrar;
29 private $existingClasses = [];
34 private static $keywordsBlacklist = [
"String",
"Array",
"Boolean",
"Element"];
39 private $referenceBlackList =
null;
51 $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
61 # ::getResourceModel ::getBlockSingleton ::getModel ::getSingleton 62 \:\:get(?:ResourceModel | BlockSingleton | Model | Singleton)?\(\s*[\'"]([a-z\d\\\\]+)[\'"]\s*[\),] 64 # various methods, first argument 65 | \->(?:initReport | addBlock | createBlock 66 | setAttributeModel | setBackendModel | setFrontendModel | setSourceModel | setModel 67 )\(\s*\'([a-z\d\\\\]+)\'\s*[\),] 69 # various methods, second argument 70 | \->add(?:ProductConfigurationHelper | OptionsRenderCfg)\(.+?,\s*\'([a-z\d\\\\]+)\'\s*[\),] 72 # \Mage::helper ->helper 73 | (?:Mage\:\:|\->)helper\(\s*\'([a-z\d\\\\]+)\'\s*\) 76 | function\s_getCollectionClass\(\)\s+{\s+return\s+[\'"]([a-z\d\\\\]+)[\'"] 77 | \'resource_model\'\s*=>\s*[\'"]([a-z\d\\\\]+)[\'"] 78 | (?:_parentResourceModelName | _checkoutType | _apiType)\s*=\s*\'([a-z\d\\\\]+)\' 79 | \'renderer\'\s*=>\s*\'([a-z\d\\\\]+)\' 86 '/(?:\-> | parent\:\:)(?:_init | setType)\(\s* 87 \'([A-Z][a-z\d][A-Za-z\d\\\\]+)\'(?:,\s*\'([A-Z][a-z\d][A-Za-z\d\\\\]+)\') 92 $this->collectResourceHelpersPhp(
$contents, $classes);
94 $this->assertClassesExist($classes, $file);
114 private function collectResourceHelpersPhp(
string $contents, array &$classes): void
116 $regex =
'/(?:\:\:|\->)getResourceHelper\(\s*\'([a-z\d\\\\]+)\'\s*\)/ix';
118 foreach ($matches as $moduleName) {
119 $classes[] =
"{$moduleName}\\Model\\ResourceModel\\Helper\\Mysql4";
125 $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
132 $this->assertClassesExist($classes,
$path);
140 $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
146 $xml = simplexml_load_file(
$path);
150 '/layout//*[contains(text(), "\\\\Block\\\\") or contains(text(), 151 "\\\\Model\\\\") or contains(text(), "\\\\Helper\\\\")]' 165 $classes[] = str_replace(
'_',
'\\',
"{$module}_Helper_Data");
169 $this->assertClassesExist(array_unique($classes),
$path);
187 private function assertClassesExist(array $classes,
string $path): void
194 foreach ($classes as
$class) {
203 $this->existingClasses[
$class]
213 $this->existingClasses[
$class] = 1;
214 }
catch (\PHPUnit\Framework\AssertionFailedError $e) {
215 $badClasses[] =
'\\' .
$class;
219 $this->fail(
"Files not found for following usages in {$path}:\n" . implode(
"\n", $badClasses));
222 $this->fail(
"Bad usages of classes in {$path}: \n" . implode(
"\n", $badUsages));
228 $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
244 $classPattern =
'/^(abstract\s)?class\s[A-Z][^\s\/]+/m';
246 $classNameMatch = [];
250 if (preg_match($classPattern,
$contents, $classNameMatch) == 0) {
254 $classParts = explode(
' ', $classNameMatch[0]);
274 $namespacePattern =
'/(Magento|Zend)\/[a-zA-Z]+[^\.]+/';
275 $formalPattern =
'/^namespace\s[a-zA-Z]+(\\\\[a-zA-Z0-9]+)*/m';
277 $namespaceMatch = [];
278 $formalNamespaceArray = [];
279 $namespaceFolders =
null;
282 if (preg_match($namespacePattern,
$relativePath, $namespaceMatch) == 0) {
286 $namespaceFolders = $namespaceMatch[0];
287 $classParts = explode(
'/', $namespaceFolders);
288 array_pop($classParts);
289 $expectedNamespace = implode(
'\\', $classParts);
291 if (preg_match($formalPattern,
$contents, $formalNamespaceArray) != 0) {
292 $foundNamespace = substr($formalNamespaceArray[0], 10);
293 $foundNamespace = str_replace(
'\\',
'/', $foundNamespace);
295 if ($namespaceFolders !=
null && $foundNamespace !=
null) {
299 "Location of {$file} does not match formal namespace: {$expectedNamespace}\n" 303 $this->fail(
"Missing expected namespace \"{$expectedNamespace}\" for file: {$file}");
309 $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
318 '/\/dev\/tests\/static\/testsuite\/Magento\/Test\/Integrity\/ClassesTest.php$/',
324 $formalPattern =
'/^namespace\s[a-zA-Z]+(\\\\[a-zA-Z0-9]+)*/m';
325 $formalNamespaceArray = [];
328 if (preg_match($formalPattern,
$contents, $formalNamespaceArray) == 0) {
331 $namespacePath = str_replace(
'\\',
'/', substr($formalNamespaceArray[0], 10));
334 $newObjectPattern =
'/^' .
335 '.*new\s(?<venderClass>\\\\Magento(?:\\\\[a-zA-Z0-9_]+)+)\(.*\)' .
336 '|.*new\s(?<badClass>[A-Z][a-zA-Z0-9]+[a-zA-Z0-9_\\\\]*)\(.*\)\;' .
337 '|use [A-Z][a-zA-Z0-9_\\\\]+ as (?<aliasClass>[A-Z][a-zA-Z0-9]+)' .
340 preg_match_all($newObjectPattern,
$contents, $result1);
343 $staticCallPattern =
'/^' .
344 '((?!Magento).)*(?<venderClass>\\\\Magento(?:\\\\[a-zA-Z0-9_]+)+)\:\:.*\;' .
345 '|[^\\\\^a-z^A-Z^0-9^_^:](?<badClass>[A-Z][a-zA-Z0-9_]+)\:\:.*\;' .
346 '|use [A-Z][a-zA-Z0-9_\\\\]+ as (?<aliasClass>[A-Z][a-zA-Z0-9]+)' .
349 preg_match_all($staticCallPattern,
$contents, $result2);
352 $annotationPattern =
'/^' .
353 '[\s]*\*\s\@(?:return|throws)\s(?<venderClass>\\\\Magento(?:\\\\[a-zA-Z0-9_]+)+)' .
354 '|[\s]*\*\s\@return\s(?<badClass>[A-Z][a-zA-Z0-9_\\\\]+)' .
355 '|[\s]*\*\s\@throws\s(?<exception>[A-Z][a-zA-Z0-9_\\\\]+)' .
356 '|use [A-Z][a-zA-Z0-9_\\\\]+ as (?<aliasClass>[A-Z][a-zA-Z0-9]+)' .
359 preg_match_all($annotationPattern,
$contents, $result3);
361 $vendorClasses = array_unique(
362 array_merge_recursive($result1[
'venderClass'], $result2[
'venderClass'], $result3[
'venderClass'])
365 $badClasses = array_unique(
366 array_merge_recursive($result1[
'badClass'], $result2[
'badClass'], $result3[
'badClass'])
369 $aliasClasses = array_unique(
370 array_merge_recursive($result1[
'aliasClass'], $result2[
'aliasClass'], $result3[
'aliasClass'])
373 $vendorClasses = array_filter($vendorClasses,
'strlen');
374 $vendorClasses = $this->referenceBlacklistFilter($vendorClasses);
375 if (!empty($vendorClasses)) {
376 $this->assertClassesExist($vendorClasses, $file);
379 if (!empty($result3[
'exception']) && $result3[
'exception'][0] !=
"") {
380 $badClasses = array_merge($badClasses, array_filter($result3[
'exception'],
'strlen'));
383 $badClasses = array_filter($badClasses,
'strlen');
384 if (empty($badClasses)) {
388 $aliasClasses = array_filter($aliasClasses,
'strlen');
389 if (!empty($aliasClasses)) {
390 $badClasses = $this->handleAliasClasses($aliasClasses, $badClasses);
393 $badClasses = $this->referenceBlacklistFilter($badClasses);
394 $badClasses = $this->removeSpecialCases($badClasses, $file,
$contents, $namespacePath);
395 $this->assertClassReferences($badClasses, $file);
408 private function handleAliasClasses(array $aliasClasses, array $badClasses): array
410 foreach ($aliasClasses as $aliasClass) {
411 foreach ($badClasses as $badClass) {
412 if (strpos($badClass, $aliasClass) === 0) {
413 unset($badClasses[array_search($badClass, $badClasses)]);
427 private function referenceBlacklistFilter(array $classes): array
430 $classes = $this->getReferenceBlacklist();
431 foreach ($classes as
$class) {
432 if (in_array(
$class, $this->referenceBlackList)) {
433 unset($classes[array_search(
$class, $classes)]);
445 private function getReferenceBlacklist(): array
447 if (!isset($this->referenceBlackList)) {
448 $this->referenceBlackList = file(
449 __DIR__ .
'/_files/blacklist/reference.txt',
450 FILE_IGNORE_NEW_LINES
454 return $this->referenceBlackList;
466 private function removeSpecialCases(array $badClasses,
string $file,
string $contents,
string $namespacePath): array
468 foreach ($badClasses as $badClass) {
471 if (preg_match(
'/^[A-Z][a-z]+_[A-Z0-9][a-z0-9]+$/', $badClass)) {
473 if ($moduleDir !==
null) {
474 unset($badClasses[array_search($badClass, $badClasses)]);
480 if (in_array($badClass, self::$keywordsBlacklist)) {
481 unset($badClasses[array_search($badClass, $badClasses)]);
485 $classParts = explode(
'/', $file);
489 unset($badClasses[array_search($badClass, $badClasses)]);
493 if ($this->removeSpecialCasesNonFullyQualifiedClassNames($namespacePath, $badClasses, $badClass)) {
497 $referenceFile = implode(
'/', $classParts) .
'/' . str_replace(
'\\',
'/', $badClass) .
'.php';
498 if (file_exists($referenceFile)) {
499 unset($badClasses[array_search($badClass, $badClasses)]);
506 if (preg_match(
'/use\s.*[\\n]?.*' . str_replace(
'\\',
'\\\\', $badClass) .
'[\,\;]/',
$contents)) {
507 unset($badClasses[array_search($badClass, $badClasses)]);
524 private function removeSpecialCasesNonFullyQualifiedClassNames($namespacePath, &$badClasses, $badClass)
526 $namespaceParts = explode(
'/', $namespacePath);
528 if (isset($namespaceParts[1])) {
529 $moduleName = array_shift($namespaceParts) .
'_' . array_shift($namespaceParts);
533 $fullPath = $moduleDir .
'/' . implode(
'/', $namespaceParts) .
'/' .
534 str_replace(
'\\',
'/', $badClass) .
'.php';
536 if (file_exists($fullPath)) {
537 unset($badClasses[array_search($badClass, $badClasses)]);
542 $fullPath = $this->getLibraryDirByPath($namespacePath, $badClass);
544 if ($fullPath && file_exists($fullPath)) {
545 unset($badClasses[array_search($badClass, $badClasses)]);
548 return $this->removeSpecialCasesForAllOthers($namespacePath, $badClass, $badClasses);
559 private function getLibraryDirByPath(
string $namespacePath,
string $badClass)
563 $namespaceParts = explode(
'/', $namespacePath);
564 if (isset($namespaceParts[1]) && $namespaceParts[1]) {
565 $vendor = array_shift($namespaceParts);
566 $lib = array_shift($namespaceParts);
567 if ($lib ==
'framework') {
568 $subLib = $namespaceParts[0];
569 $subLib = strtolower(preg_replace(
'/(.)([A-Z])/',
"$1-$2", $subLib));
570 $libraryName = $vendor .
'/' . $lib .
'-' . $subLib;
571 $libraryDir = $this->componentRegistrar->getPath(
573 strtolower($libraryName)
576 array_shift($namespaceParts);
578 $libraryName = $vendor .
'/' . $lib;
579 $libraryDir = $this->componentRegistrar->getPath(
581 strtolower($libraryName)
585 $lib = strtolower(preg_replace(
'/(.)([A-Z])/',
"$1-$2", $lib));
586 $libraryName = $vendor .
'/' . $lib;
587 $libraryDir = $this->componentRegistrar->getPath(
589 strtolower($libraryName)
594 $fullPath = $libraryDir .
'/' . implode(
'/', $namespaceParts) .
'/' .
595 str_replace(
'\\',
'/', $badClass) .
'.php';
607 private function removeSpecialCasesForAllOthers(
string $namespacePath,
string $badClass, array &$badClasses): bool
612 BP .
'/dev/tests/api-functional/framework/',
613 BP .
'/dev/tests/functional/',
614 BP .
'/dev/tests/integration/framework/',
615 BP .
'/dev/tests/integration/framework/tests/unit/testsuite/',
616 BP .
'/dev/tests/integration/testsuite/',
617 BP .
'/dev/tests/integration/testsuite/Magento/Test/Integrity/',
618 BP .
'/dev/tests/static/framework/',
619 BP .
'/dev/tests/static/testsuite/',
623 $directories = array_merge($directories, $libraryPaths);
625 foreach ($directories as $directory) {
626 $fullPath = $directory . $namespacePath .
'/' . str_replace(
'\\',
'/', $badClass) .
'.php';
627 if (file_exists($fullPath)) {
628 unset($badClasses[array_search($badClass, $badClasses)]);
644 private function assertClassReferences(array $badClasses,
string $file): void
646 if (empty($badClasses)) {
649 $this->fail(
"Incorrect namespace usage(s) found in file {$file}:\n" . implode(
"\n", $badClasses));
658 if (($key = array_search(str_replace(
'\\',
'/',
__FILE__), $filesToTest)) !==
false) {
659 unset($filesToTest[$key]);
662 foreach ($filesToTest as $file) {
664 if (preg_match(
'/@covers(DefaultClass)?\s+([\w\\\\]+)(::([\w\\\\]+))?/',
$code, $matches)) {
665 if ($this->isNonexistentEntityCovered($matches)) {
666 $errors[] = $file .
': ' . $matches[0];
672 'Nonexistent classes/methods were found in @covers annotations: ' . PHP_EOL . implode(PHP_EOL,
$errors)
681 private function isNonexistentEntityCovered($matches)
683 return !empty($matches[2]) && !
class_exists($matches[2])
684 || !empty($matches[4]) && !method_exists($matches[2], $matches[4]);
defined('TESTS_BP')||define('TESTS_BP' __DIR__
defined('MTF_BOOT_FILE')||define('MTF_BOOT_FILE' __FILE__
static getCallbackClass($callbackName)
const INCLUDE_NON_CLASSES
static collectClassesInConfig(\SimpleXMLElement $xml)
static getXmlAttributeValues(\SimpleXMLElement $xml, $xPath, $attributeName)
static collectLayoutClasses(\SimpleXMLElement $xml)
$_option $_optionId $class
static isVirtual($className)
static getAllMatches($contents, $regex, &$result=[])
static isAutogenerated($className)
static getXmlNodeValues(\SimpleXMLElement $xml, $xPath)
foreach($appDirs as $dir) $files