init
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\CodeMessDetector\Rule\Design;
|
||||
|
||||
use PDepend\Source\AST\ASTClass;
|
||||
use PHPMD\AbstractNode;
|
||||
use PHPMD\AbstractRule;
|
||||
use PHPMD\Node\ClassNode;
|
||||
use PHPMD\Rule\ClassAware;
|
||||
use Magento\Framework\App\ActionInterface;
|
||||
|
||||
/**
|
||||
* Actions must process a defined list of HTTP methods.
|
||||
*/
|
||||
class AllPurposeAction extends AbstractRule implements ClassAware
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param ClassNode|ASTClass $node
|
||||
*/
|
||||
public function apply(AbstractNode $node)
|
||||
{
|
||||
// Skip validation for Abstract Controllers
|
||||
if ($node->isAbstract()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!class_exists($node->getFullQualifiedName(), true)) {
|
||||
return;
|
||||
}
|
||||
$impl = class_implements($node->getFullQualifiedName(), true);
|
||||
} catch (\Throwable $exception) {
|
||||
//Couldn't load a class.
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($impl) && in_array(ActionInterface::class, $impl, true)) {
|
||||
$methodsDefined = false;
|
||||
foreach ($impl as $i) {
|
||||
if (preg_match('/\\\Http[a-z]+ActionInterface$/i', $i)) {
|
||||
$methodsDefined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$methodsDefined) {
|
||||
$this->addViolation($node, [$node->getFullQualifiedName()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\CodeMessDetector\Rule\Design;
|
||||
|
||||
use Magento\Framework\GetParameterClassTrait;
|
||||
use Magento\Framework\Session\SessionManagerInterface;
|
||||
use Magento\Framework\Stdlib\Cookie\CookieReaderInterface;
|
||||
use PDepend\Source\AST\ASTClass;
|
||||
use PHPMD\AbstractNode;
|
||||
use PHPMD\AbstractRule;
|
||||
use PHPMD\Node\ClassNode;
|
||||
use PHPMD\Rule\ClassAware;
|
||||
|
||||
/**
|
||||
* Session and Cookies must be used only in HTML Presentation layer.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class CookieAndSessionMisuse extends AbstractRule implements ClassAware
|
||||
{
|
||||
use GetParameterClassTrait;
|
||||
|
||||
/**
|
||||
* Is given class a controller?
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @return bool
|
||||
*/
|
||||
private function isController(\ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\Magento\Framework\App\ActionInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is given class a block?
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @return bool
|
||||
*/
|
||||
private function isBlock(\ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\Magento\Framework\View\Element\BlockInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is given class an HTML UI data provider?
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @return bool
|
||||
*/
|
||||
private function isUiDataProvider(\ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(
|
||||
\Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is given class a Layout Processor?
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @return bool
|
||||
*/
|
||||
private function isLayoutProcessor(\ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(
|
||||
\Magento\Checkout\Block\Checkout\LayoutProcessorInterface::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is given class a View Model?
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @return bool
|
||||
*/
|
||||
private function isViewModel(\ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(
|
||||
\Magento\Framework\View\Element\Block\ArgumentInterface::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is given class an HTML UI Document?
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @return bool
|
||||
*/
|
||||
private function isUiDocument(\ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\Magento\Framework\View\Element\UiComponent\DataProvider\Document::class)
|
||||
|| $class->getName() === \Magento\Framework\View\Element\UiComponent\DataProvider\Document::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is given class a plugin for controllers?
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @return bool
|
||||
*/
|
||||
private function isControllerPlugin(\ReflectionClass $class): bool
|
||||
{
|
||||
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
|
||||
if (preg_match('/^(after|around|before).+/i', $method->getName())) {
|
||||
try {
|
||||
$parameters = $method->getParameters();
|
||||
if (count($parameters) === 0) {
|
||||
continue;
|
||||
}
|
||||
$argument = $this->getParameterClass($parameters[0]);
|
||||
} catch (\Throwable $exception) {
|
||||
//Non-existing class (autogenerated perhaps) or doesn't have an argument.
|
||||
continue;
|
||||
}
|
||||
if ($argument) {
|
||||
$isAction = $argument->isSubclassOf(\Magento\Framework\App\ActionInterface::class)
|
||||
|| $argument->getName() === \Magento\Framework\App\ActionInterface::class;
|
||||
if ($isAction) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is given class a plugin for blocks?
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @return bool
|
||||
*/
|
||||
private function isBlockPlugin(\ReflectionClass $class): bool
|
||||
{
|
||||
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
|
||||
if (preg_match('/^(after|around|before).+/i', $method->getName())) {
|
||||
try {
|
||||
$parameters = $method->getParameters();
|
||||
if (count($parameters) === 0) {
|
||||
continue;
|
||||
}
|
||||
$argument = $this->getParameterClass($parameters[0]);
|
||||
} catch (\Throwable $exception) {
|
||||
//Non-existing class (autogenerated perhaps) or doesn't have an argument.
|
||||
continue;
|
||||
}
|
||||
if ($argument) {
|
||||
$isBlock = $argument->isSubclassOf(\Magento\Framework\View\Element\BlockInterface::class)
|
||||
|| $argument->getName() === \Magento\Framework\View\Element\BlockInterface::class;
|
||||
if ($isBlock) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether given class depends on classes to pay attention to.
|
||||
*
|
||||
* @param \ReflectionClass $class
|
||||
* @return bool
|
||||
*/
|
||||
private function doesUseRestrictedClasses(\ReflectionClass $class): bool
|
||||
{
|
||||
$constructor = $class->getConstructor();
|
||||
if ($constructor) {
|
||||
foreach ($constructor->getParameters() as $argument) {
|
||||
try {
|
||||
$class = $this->getParameterClass($argument);
|
||||
if ($class === null) {
|
||||
continue;
|
||||
}
|
||||
if ($class->isSubclassOf(SessionManagerInterface::class)
|
||||
|| $class->getName() === SessionManagerInterface::class
|
||||
|| $class->isSubclassOf(CookieReaderInterface::class)
|
||||
|| $class->getName() === CookieReaderInterface::class
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} catch (\ReflectionException $exception) {
|
||||
//Failed to load the argument's class information
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param ClassNode|ASTClass $node
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
public function apply(AbstractNode $node)
|
||||
{
|
||||
try {
|
||||
$class = new \ReflectionClass($node->getFullQualifiedName());
|
||||
} catch (\Throwable $exception) {
|
||||
//Failed to load class, nothing we can do
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->doesUseRestrictedClasses($class)) {
|
||||
if (!$this->isController($class)
|
||||
&& !$this->isBlock($class)
|
||||
&& !$this->isUiDataProvider($class)
|
||||
&& !$this->isUiDocument($class)
|
||||
&& !$this->isControllerPlugin($class)
|
||||
&& !$this->isBlockPlugin($class)
|
||||
&& !$this->isLayoutProcessor($class)
|
||||
&& !$this->isViewModel($class)
|
||||
) {
|
||||
$this->addViolation($node, [$node->getFullQualifiedName()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\CodeMessDetector\Rule\UnusedCode;
|
||||
|
||||
use PHPMD\AbstractNode;
|
||||
use PHPMD\Node\ClassNode;
|
||||
use PHPMD\Node\MethodNode;
|
||||
use PHPMD\Rule\UnusedFormalParameter as PhpmdUnusedFormalParameter;
|
||||
|
||||
class UnusedFormalParameter extends PhpmdUnusedFormalParameter
|
||||
{
|
||||
/**
|
||||
* This method collects all local variables in the body of the currently
|
||||
* analyzed method or function and removes those parameters that are
|
||||
* referenced by one of the collected variables.
|
||||
*
|
||||
* @param AbstractNode $node
|
||||
* @return void
|
||||
*/
|
||||
protected function removeUsedParameters(AbstractNode $node)
|
||||
{
|
||||
parent::removeUsedParameters($node);
|
||||
$this->removeVariablesUsedInPlugins($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove required method variables used in plugins from given node
|
||||
*
|
||||
* @param AbstractNode $node
|
||||
*/
|
||||
private function removeVariablesUsedInPlugins(AbstractNode $node)
|
||||
{
|
||||
if (!$node instanceof MethodNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var ClassNode $classNode */
|
||||
$classNode = $node->getParentType();
|
||||
if (!$this->isPluginClass($classNode->getNamespaceName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Around and After plugins has 2 required params $subject and $proceed or $result
|
||||
* that should be ignored
|
||||
*/
|
||||
foreach (['around', 'after'] as $pluginMethodPrefix) {
|
||||
if ($this->isFunctionNameStartingWith($node, $pluginMethodPrefix)) {
|
||||
$this->removeVariablesByCount(2);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Before plugins has 1 required params $subject
|
||||
* that should be ignored
|
||||
*/
|
||||
if ($this->isFunctionNameStartingWith($node, 'before')) {
|
||||
$this->removeVariablesByCount(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the first part of function fully qualified name is equal to $name
|
||||
*
|
||||
* Methods getImage and getName are equal. getImage used prior to usage in phpmd source
|
||||
*
|
||||
* @param MethodNode $node
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
private function isFunctionNameStartingWith(MethodNode $node, string $name): bool
|
||||
{
|
||||
return (0 === strpos($node->getImage(), $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove first $countOfRemovingVariables from given node
|
||||
*
|
||||
* @param int $countOfRemovingVariables
|
||||
*/
|
||||
private function removeVariablesByCount(int $countOfRemovingVariables)
|
||||
{
|
||||
array_splice($this->nodes, 0, $countOfRemovingVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if namespace contain "Plugin". Case-sensitive ignored
|
||||
*
|
||||
* @param string $class
|
||||
* @return bool
|
||||
*/
|
||||
private function isPluginClass(string $class): bool
|
||||
{
|
||||
return (stripos($class, 'plugin') !== false);
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\CodeMessDetector\Test\Unit\Rule\Design;
|
||||
|
||||
use Magento\CodeMessDetector\Rule\Design\AllPurposeAction;
|
||||
use Magento\Framework\App\Action\HttpGetActionInterface;
|
||||
use Magento\Framework\App\ActionInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\MockObject\MockObject as MockObject;
|
||||
use PHPUnit\Framework\MockObject\Builder\InvocationMocker;
|
||||
use PHPMD\Report;
|
||||
use PHPMD\Node\ClassNode;
|
||||
|
||||
class AllPurposeActionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @param object $fakeAction
|
||||
* @param bool $violates
|
||||
*
|
||||
* @dataProvider getCases
|
||||
*/
|
||||
public function testApply($fakeAction, bool $violates)
|
||||
{
|
||||
$node = $this->createNodeMock($fakeAction);
|
||||
$rule = new AllPurposeAction();
|
||||
$this->expectsRuleViolation($rule, $violates);
|
||||
$rule->apply($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCases(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
new class implements ActionInterface, HttpGetActionInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
},
|
||||
false
|
||||
],
|
||||
[
|
||||
new class implements ActionInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
},
|
||||
true
|
||||
],
|
||||
[
|
||||
new class implements HttpGetActionInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
},
|
||||
false
|
||||
],
|
||||
[
|
||||
new class
|
||||
{
|
||||
|
||||
},
|
||||
false
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $dynamicObject
|
||||
*
|
||||
* @return ClassNode|MockObject
|
||||
*/
|
||||
private function createNodeMock($dynamicObject): MockObject
|
||||
{
|
||||
$node = $this->getMockBuilder(ClassNode::class)
|
||||
->disableOriginalConstructor()
|
||||
->disableProxyingToOriginalMethods()
|
||||
->setMethods(
|
||||
[
|
||||
// disable name lookup from AST artifact
|
||||
'getNamespaceName',
|
||||
'getParentName',
|
||||
'getName',
|
||||
'getFullQualifiedName',
|
||||
]
|
||||
)
|
||||
->getMock();
|
||||
$node->expects($this->any())
|
||||
->method('getFullQualifiedName')
|
||||
->willReturn(get_class($dynamicObject));
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AllPurposeAction $rule
|
||||
* @param bool $expects
|
||||
* @return InvocationMocker
|
||||
*/
|
||||
private function expectsRuleViolation(AllPurposeAction $rule, bool $expects): InvocationMocker
|
||||
{
|
||||
/** @var Report|MockObject $report */
|
||||
$report = $this->getMockBuilder(Report::class)->getMock();
|
||||
if ($expects) {
|
||||
$violationExpectation = $this->atLeastOnce();
|
||||
} else {
|
||||
$violationExpectation = $this->never();
|
||||
}
|
||||
$invokation = $report->expects($violationExpectation)
|
||||
->method('addRuleViolation');
|
||||
$rule->setReport($report);
|
||||
|
||||
return $invokation;
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\CodeMessDetector\Test\Unit\Rule\UnusedCode;
|
||||
|
||||
use Magento\CodeMessDetector\Rule\UnusedCode\UnusedFormalParameter;
|
||||
use PHPMD\Node\ASTNode;
|
||||
use PHPMD\Node\MethodNode;
|
||||
use PHPMD\Report;
|
||||
use PHPUnit\Framework\MockObject\MockObject as MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
class UnusedFormalParameterTest extends TestCase
|
||||
{
|
||||
private const FAKE_PLUGIN_NAMESPACE = 'Magento\CodeMessDetector\Test\UnusedCode\Plugin';
|
||||
private const FAKE_NAMESPACE = 'Magento\CodeMessDetector\Test\UnusedCode';
|
||||
|
||||
/**
|
||||
*
|
||||
* @dataProvider getCases
|
||||
*/
|
||||
public function testApply($methodName, $methodParams, $namespace, $expectViolation)
|
||||
{
|
||||
$node = $this->createMethodNodeMock($methodName, $methodParams, $namespace);
|
||||
$rule = new UnusedFormalParameter();
|
||||
$this->expectsRuleViolation($rule, $expectViolation);
|
||||
$rule->apply($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare method node mock
|
||||
*
|
||||
* @param $methodName
|
||||
* @param $methodParams
|
||||
* @param $namespace
|
||||
* @return MethodNode|MockObject
|
||||
*/
|
||||
private function createMethodNodeMock($methodName, $methodParams, $namespace)
|
||||
{
|
||||
$methodNode = $this->createConfiguredMock(
|
||||
MethodNode::class,
|
||||
[
|
||||
'getName' => $methodName,
|
||||
'getImage' => $methodName,
|
||||
'isAbstract' => false,
|
||||
'isDeclaration' => true
|
||||
]
|
||||
);
|
||||
|
||||
$variableDeclarators = [];
|
||||
foreach ($methodParams as $methodParam) {
|
||||
$variableDeclarator = $this->createASTNodeMock();
|
||||
$variableDeclarator->method('getImage')
|
||||
->willReturn($methodParam);
|
||||
|
||||
$variableDeclarators[] = $variableDeclarator;
|
||||
}
|
||||
$parametersMock = $this->createASTNodeMock();
|
||||
$parametersMock->expects($this->once())
|
||||
->method('findChildrenOfType')
|
||||
->with('VariableDeclarator')
|
||||
->willReturn($variableDeclarators);
|
||||
|
||||
/**
|
||||
* Declare mock result for findChildrenOfType
|
||||
* with Dummy for removeCompoundVariables and removeVariablesUsedByFuncGetArgs
|
||||
*/
|
||||
$methodNode->expects($this->atLeastOnce())
|
||||
->method('findChildrenOfType')
|
||||
->withConsecutive(['FormalParameters'], ['CompoundVariable'], ['FunctionPostfix'])
|
||||
->willReturnOnConsecutiveCalls([$parametersMock], [], []);
|
||||
|
||||
// Dummy result for removeRegularVariables
|
||||
$methodNode->expects($this->once())
|
||||
->method('findChildrenOfTypeVariable')
|
||||
->willReturn([]);
|
||||
|
||||
$classNode = $this->createASTNodeMock();
|
||||
$classNode->expects($this->once())
|
||||
->method('getNamespaceName')
|
||||
->willReturn($namespace);
|
||||
$methodNode->expects($this->once())
|
||||
->method('getParentType')
|
||||
->willReturn($classNode);
|
||||
|
||||
return $methodNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ASTNode mock
|
||||
*
|
||||
* @return ASTNode|MockObject
|
||||
*/
|
||||
private function createASTNodeMock()
|
||||
{
|
||||
return $this->createMock(ASTNode::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UnusedFormalParameter $rule
|
||||
* @param bool $expects
|
||||
*/
|
||||
private function expectsRuleViolation(UnusedFormalParameter $rule, bool $expects)
|
||||
{
|
||||
/** @var Report|MockObject $reportMock */
|
||||
$reportMock = $this->createMock(Report::class);
|
||||
if ($expects) {
|
||||
$violationExpectation = $this->atLeastOnce();
|
||||
} else {
|
||||
$violationExpectation = $this->never();
|
||||
}
|
||||
$reportMock->expects($violationExpectation)
|
||||
->method('addRuleViolation');
|
||||
$rule->setReport($reportMock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCases(): array
|
||||
{
|
||||
return [
|
||||
// Plugin methods
|
||||
[
|
||||
'beforePluginMethod',
|
||||
[
|
||||
'subject'
|
||||
],
|
||||
self::FAKE_PLUGIN_NAMESPACE,
|
||||
false
|
||||
],
|
||||
[
|
||||
'aroundPluginMethod',
|
||||
[
|
||||
'subject',
|
||||
'proceed'
|
||||
],
|
||||
self::FAKE_PLUGIN_NAMESPACE,
|
||||
false
|
||||
],
|
||||
[
|
||||
'aroundPluginMethod',
|
||||
[
|
||||
'subject',
|
||||
'result'
|
||||
],
|
||||
self::FAKE_PLUGIN_NAMESPACE,
|
||||
false
|
||||
],
|
||||
// Plugin method that contain unused parameter
|
||||
[
|
||||
'someMethod',
|
||||
[
|
||||
'unusedParameter'
|
||||
],
|
||||
self::FAKE_PLUGIN_NAMESPACE,
|
||||
true
|
||||
],
|
||||
// Non plugin method
|
||||
[
|
||||
'someMethod',
|
||||
[
|
||||
'subject',
|
||||
'result'
|
||||
],
|
||||
self::FAKE_NAMESPACE,
|
||||
true
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<ruleset name="Magento Specific Design Rules"
|
||||
xmlns="http://pmd.sf.net/ruleset/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
|
||||
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
|
||||
<rule name="AllPurposeAction"
|
||||
class="Magento\CodeMessDetector\Rule\Design\AllPurposeAction"
|
||||
message= "The class {0} does not restrict processed HTTP methods by implementing a Http*Method name*ActionInterface">
|
||||
<description>
|
||||
<![CDATA[
|
||||
Controllers (classes implementing ActionInterface) have to implement marker Http<Method>ActionInterface
|
||||
to restrict incoming requests by methods.
|
||||
]]>
|
||||
</description>
|
||||
<priority>2</priority>
|
||||
<properties />
|
||||
<example>
|
||||
<![CDATA[
|
||||
class PostOrder implements ActionInterface
|
||||
{
|
||||
public function execute()
|
||||
{
|
||||
//I process GET, POST, PATCH etc. while only intended for POST
|
||||
...
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
<rule name="CookieAndSessionMisuse"
|
||||
class="Magento\CodeMessDetector\Rule\Design\CookieAndSessionMisuse"
|
||||
message= "The class {0} uses sessions or cookies while not being a part of HTML Presentation layer">
|
||||
<description>
|
||||
<![CDATA[
|
||||
Sessions and cookies must only be used in classes directly responsible for HTML presentation because Web APIs do not
|
||||
rely on cookies and sessions. If you need to get current user use Magento\Authorization\Model\UserContextInterface
|
||||
]]>
|
||||
</description>
|
||||
<priority>2</priority>
|
||||
<properties />
|
||||
<example>
|
||||
<![CDATA[
|
||||
class OrderProcessor
|
||||
{
|
||||
public function __construct(SessionManagerInterface $session) {
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
public function place(OrderInterface $order)
|
||||
{
|
||||
//Will not be present if processing a WebAPI request
|
||||
$currentOrder = $this->session->get('current_order');
|
||||
...
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
</ruleset>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<ruleset name="Unused Code Rules"
|
||||
xmlns="http://pmd.sf.net/ruleset/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
|
||||
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
|
||||
|
||||
<rule name="UnusedFormalParameter"
|
||||
message="Avoid unused parameters such as '{0}'."
|
||||
class="Magento\CodeMessDetector\Rule\UnusedCode\UnusedFormalParameter">
|
||||
<description><![CDATA[Avoid passing parameters to methods or constructors and then not using those parameters except on plugins]]></description>
|
||||
<example>
|
||||
<![CDATA[
|
||||
class Foo
|
||||
{
|
||||
private function bar($howdy)
|
||||
{
|
||||
// $howdy is not used
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2019 Adobe
|
||||
* All Rights Reserved.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\PhpStan\Formatters;
|
||||
|
||||
use PHPStan\Command\AnalysisResult;
|
||||
use PHPStan\Command\ErrorFormatter\TableErrorFormatter;
|
||||
use PHPStan\Command\ErrorFormatter\ErrorFormatter;
|
||||
use PHPStan\Command\Output;
|
||||
|
||||
/**
|
||||
* To mute the PHPStan error message add a comment above the reported error line.
|
||||
*
|
||||
* Example of usage:
|
||||
*
|
||||
* // phpstan:ignore "Method Magento\TestModule\TestClass::testMethod() invoked with 1 parameter, 0 required."
|
||||
* $this->testMethod(1);
|
||||
*
|
||||
* or replace some part of error message with *
|
||||
*
|
||||
* // phpstan:ignore "Method * invoked with 1 parameter, 0 required."
|
||||
* $this->testMethod(1);
|
||||
*
|
||||
* or just
|
||||
*
|
||||
* // phpstan:ignore
|
||||
* $this->testMethod(1);
|
||||
*
|
||||
* or
|
||||
*
|
||||
* $this->testMethod(1); // phpstan:ignore
|
||||
*
|
||||
* The error message will be suppressed.
|
||||
*
|
||||
* @see \Magento\PhpStan\Formatters\Fixtures\ClassWithIgnoreAnnotation
|
||||
*/
|
||||
class FilteredErrorFormatter implements ErrorFormatter
|
||||
{
|
||||
private const MUTE_ERROR_ANNOTATION = 'phpstan:ignore';
|
||||
private const NO_ERRORS = 0;
|
||||
|
||||
/**
|
||||
* @var TableErrorFormatter
|
||||
*/
|
||||
private $tableErrorFormatter;
|
||||
|
||||
/**
|
||||
* @param TableErrorFormatter $tableErrorFormatter
|
||||
*/
|
||||
public function __construct(TableErrorFormatter $tableErrorFormatter)
|
||||
{
|
||||
$this->tableErrorFormatter = $tableErrorFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function formatErrors(AnalysisResult $analysisResult, Output $output): int
|
||||
{
|
||||
if (!$analysisResult->hasErrors()) {
|
||||
$output->getStyle()->success('No errors');
|
||||
return self::NO_ERRORS;
|
||||
}
|
||||
|
||||
//@phpstan:ignore-line
|
||||
$clearedAnalysisResult = new AnalysisResult(
|
||||
$this->clearIgnoredErrors($analysisResult->getFileSpecificErrors()),
|
||||
$analysisResult->getNotFileSpecificErrors(),
|
||||
$analysisResult->getInternalErrors(),
|
||||
$analysisResult->getWarnings(),
|
||||
$analysisResult->getCollectedData(),
|
||||
$analysisResult->isDefaultLevelUsed(),
|
||||
$analysisResult->getProjectConfigFile(),
|
||||
$analysisResult->isResultCacheSaved(),
|
||||
$analysisResult->getPeakMemoryUsageBytes(),
|
||||
$analysisResult->isResultCacheUsed(),
|
||||
$analysisResult->getChangedProjectExtensionFilesOutsideOfAnalysedPaths()
|
||||
);
|
||||
|
||||
return $this->tableErrorFormatter->formatErrors($clearedAnalysisResult, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters error list.
|
||||
*
|
||||
* @param array $fileSpecificErrors
|
||||
* @return array
|
||||
*/
|
||||
private function clearIgnoredErrors(array $fileSpecificErrors): array
|
||||
{
|
||||
foreach ($fileSpecificErrors as $index => $error) {
|
||||
$fileName = $error->getFile();
|
||||
// phpcs:ignore Magento2.Functions.DiscouragedFunction
|
||||
if (!file_exists($fileName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$line = $error->getLine() ? $this->getLineWithMuteErrorAnnotation($error->getLine(), $fileName) : null;
|
||||
if ($line === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$extractErrorPattern = '@' . self::MUTE_ERROR_ANNOTATION . '\s+"(.*?)"@';
|
||||
$errorPattern = preg_match($extractErrorPattern, $line, $result) ? $this->preparePattern($result[1]) : '';
|
||||
if ($errorPattern && !preg_match('@' . $errorPattern . '@i', $error->getMessage())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($fileSpecificErrors[$index]);
|
||||
}
|
||||
|
||||
return $fileSpecificErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns context of the line with mute error annotation.
|
||||
*
|
||||
* @param int $errorLine
|
||||
* @param string $fileName
|
||||
* @return string|null
|
||||
*/
|
||||
private function getLineWithMuteErrorAnnotation(int $errorLine, string $fileName): ?string
|
||||
{
|
||||
$file = new \SplFileObject($fileName);
|
||||
$lineNumbersToCheck = [
|
||||
$errorLine - 2, // the line above to the line that caused the error
|
||||
$errorLine - 1, // the line that caused the error
|
||||
$errorLine - 3, // the line two lines above to the line that caused the error
|
||||
];
|
||||
|
||||
foreach ($lineNumbersToCheck as $lineNumber) {
|
||||
$file->seek($lineNumber > 0 ? $lineNumber : 0);
|
||||
$line = $file->current();
|
||||
if (strpos($line, self::MUTE_ERROR_ANNOTATION) !== false) {
|
||||
return $line;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares error pattern.
|
||||
*
|
||||
* @param string $errorDescription
|
||||
* @return string
|
||||
*/
|
||||
private function preparePattern(string $errorDescription)
|
||||
{
|
||||
// phpcs:ignore Magento2.Functions.DiscouragedFunction
|
||||
return str_replace('*', '(?:.*?)', addcslashes(trim($errorDescription), '\()[]'));
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\PhpStan\Reflection\Php;
|
||||
|
||||
use Magento\Framework\DataObject;
|
||||
use Magento\Framework\Session\SessionManager;
|
||||
use PHPStan\DependencyInjection\Container;
|
||||
use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\MethodsClassReflectionExtension;
|
||||
|
||||
/**
|
||||
* Extension to add support of magic methods (get/set/uns/has) based on @see DataObject
|
||||
*/
|
||||
class DataObjectClassReflectionExtension implements MethodsClassReflectionExtension
|
||||
{
|
||||
private const MAGIC_METHODS_PREFIXES = [
|
||||
'get',
|
||||
'set',
|
||||
'uns',
|
||||
'has'
|
||||
];
|
||||
|
||||
private const PREFIX_LENGTH = 3;
|
||||
|
||||
/**
|
||||
* @var Container
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @var AnnotationsMethodsClassReflectionExtension
|
||||
*/
|
||||
private $annotationsMethodsClassReflectionExtension;
|
||||
|
||||
/**
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->annotationsMethodsClassReflectionExtension = $this->container
|
||||
->getByType(AnnotationsMethodsClassReflectionExtension::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if class has relations with DataObject and requested method can be considered as a magic method.
|
||||
*
|
||||
* @param ClassReflection $classReflection
|
||||
* @param string $methodName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
|
||||
{
|
||||
// Workaround due to annotation extension always loads last.
|
||||
if ($this->annotationsMethodsClassReflectionExtension->hasMethod($classReflection, $methodName)) {
|
||||
// In case when annotation already available for the method, we will not use 'magic methods' approach.
|
||||
return false;
|
||||
}
|
||||
if ($classReflection->isSubclassOf(DataObject::class) || $classReflection->getName() == DataObject::class) {
|
||||
return in_array($this->getPrefix($methodName), self::MAGIC_METHODS_PREFIXES);
|
||||
}
|
||||
/** SessionManager redirects all calls to `__call` to container which extends DataObject */
|
||||
if ($classReflection->isSubclassOf(SessionManager::class)
|
||||
|| $classReflection->getName() === SessionManager::class
|
||||
) {
|
||||
/** @see \Magento\Framework\Session\SessionManager::__call */
|
||||
return in_array($this->getPrefix($methodName), self::MAGIC_METHODS_PREFIXES);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prefix from method name.
|
||||
*
|
||||
* @param string $methodName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getPrefix(string $methodName): string
|
||||
{
|
||||
return (string)substr($methodName, 0, self::PREFIX_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method reflection instance.
|
||||
*
|
||||
* @param ClassReflection $classReflection
|
||||
* @param string $methodName
|
||||
*
|
||||
* @return MethodReflection
|
||||
*/
|
||||
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
|
||||
{
|
||||
return new DataObjectMethodReflection($classReflection, $methodName);
|
||||
}
|
||||
}
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\PhpStan\Reflection\Php;
|
||||
|
||||
use PHPStan\Reflection\ClassMemberReflection;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use PHPStan\Reflection\FunctionVariant;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptor;
|
||||
use PHPStan\Reflection\Php\DummyParameter;
|
||||
use PHPStan\TrinaryLogic;
|
||||
use PHPStan\Type\BooleanType;
|
||||
use PHPStan\Type\Generic\TemplateTypeMap;
|
||||
use PHPStan\Type\IntegerType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\UnionType;
|
||||
use PHPStan\Type\VoidType;
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class DataObjectMethodReflection implements MethodReflection
|
||||
{
|
||||
private const PREFIX_LENGTH = 3;
|
||||
|
||||
/**
|
||||
* @var ClassReflection
|
||||
*/
|
||||
private $classReflection;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $methodName;
|
||||
|
||||
/**
|
||||
* @param ClassReflection $classReflection
|
||||
* @param string $methodName
|
||||
*/
|
||||
public function __construct(ClassReflection $classReflection, string $methodName)
|
||||
{
|
||||
$this->classReflection = $classReflection;
|
||||
$this->methodName = $methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get methods class reflection.
|
||||
*
|
||||
* @return ClassReflection
|
||||
*/
|
||||
public function getDeclaringClass(): ClassReflection
|
||||
{
|
||||
return $this->classReflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if method is static.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isStatic(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if method is private.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if method is public.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Method PHP Doc comment message.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDocComment(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Method Name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Prototype.
|
||||
*
|
||||
* @return ClassMemberReflection
|
||||
*/
|
||||
public function getPrototype(): ClassMemberReflection
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Magic Methods variant based on type (get/set/has/uns).
|
||||
*
|
||||
* @return ParametersAcceptor[]
|
||||
*/
|
||||
public function getVariants(): array
|
||||
{
|
||||
return [
|
||||
new FunctionVariant(
|
||||
TemplateTypeMap::createEmpty(),
|
||||
TemplateTypeMap::createEmpty(),
|
||||
$this->getMethodParameters(),
|
||||
false,
|
||||
$this->getReturnType()
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prefix from method name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getMethodNamePrefix(): string
|
||||
{
|
||||
return (string)substr($this->methodName, 0, self::PREFIX_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Magic Methods parameters.
|
||||
*
|
||||
* @return ParameterReflection[]
|
||||
*/
|
||||
private function getMethodParameters(): array
|
||||
{
|
||||
$params = [];
|
||||
switch ($this->getMethodNamePrefix()) {
|
||||
case 'set':
|
||||
$params[] = new DummyParameter(
|
||||
'value',
|
||||
new MixedType(),
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
null
|
||||
);
|
||||
break;
|
||||
case 'get':
|
||||
$params[] = new DummyParameter(
|
||||
'index',
|
||||
new UnionType([new StringType(), new IntegerType()]),
|
||||
true,
|
||||
null,
|
||||
false,
|
||||
null
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Magic Methods return type.
|
||||
*
|
||||
* @return Type
|
||||
*/
|
||||
private function getReturnType(): Type
|
||||
{
|
||||
switch ($this->getMethodNamePrefix()) {
|
||||
case 'set':
|
||||
case 'uns':
|
||||
$returnType = new ObjectType($this->classReflection->getName());
|
||||
break;
|
||||
case 'has':
|
||||
$returnType = new BooleanType();
|
||||
break;
|
||||
default:
|
||||
$returnType = new MixedType();
|
||||
break;
|
||||
}
|
||||
|
||||
return $returnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if method is deprecated.
|
||||
*
|
||||
* @return TrinaryLogic
|
||||
*/
|
||||
public function isDeprecated(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get deprecated description.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDeprecatedDescription(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if method is final.
|
||||
*
|
||||
* @return TrinaryLogic
|
||||
*/
|
||||
public function isFinal(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if method is internal.
|
||||
*
|
||||
* @return TrinaryLogic
|
||||
*/
|
||||
public function isInternal(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createNo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method throw type.
|
||||
*
|
||||
* @return Type|null
|
||||
*/
|
||||
public function getThrowType(): ?Type
|
||||
{
|
||||
return new VoidType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if method has side effect.
|
||||
*
|
||||
* @return TrinaryLogic
|
||||
*/
|
||||
public function hasSideEffects(): TrinaryLogic
|
||||
{
|
||||
return TrinaryLogic::createYes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
// phpcs:disable
|
||||
|
||||
use Magento\Framework\App\Filesystem\DirectoryList;
|
||||
use Magento\Framework\Code\Generator\Io;
|
||||
use Magento\Framework\Filesystem\Driver\File;
|
||||
use Magento\Framework\TestFramework\Unit\Autoloader\ExtensionAttributesGenerator;
|
||||
use Magento\Framework\TestFramework\Unit\Autoloader\ExtensionAttributesInterfaceGenerator;
|
||||
use Magento\Framework\TestFramework\Unit\Autoloader\FactoryGenerator;
|
||||
use Magento\Framework\TestFramework\Unit\Autoloader\GeneratedClassesAutoloader;
|
||||
|
||||
if (!defined('TESTS_TEMP_DIR')) {
|
||||
//phpcs:ignore Magento2.Functions.DiscouragedFunction
|
||||
define('TESTS_TEMP_DIR', dirname(__DIR__) . '/../../tmp');
|
||||
}
|
||||
|
||||
$generatorIo = new Io(
|
||||
new File(),
|
||||
TESTS_TEMP_DIR . '/' . DirectoryList::getDefaultConfig()[DirectoryList::GENERATED_CODE][DirectoryList::PATH]
|
||||
);
|
||||
$generatedCodeAutoloader = new GeneratedClassesAutoloader(
|
||||
[
|
||||
new ExtensionAttributesGenerator(),
|
||||
new ExtensionAttributesInterfaceGenerator(),
|
||||
new FactoryGenerator(),
|
||||
],
|
||||
$generatorIo
|
||||
);
|
||||
spl_autoload_register([$generatedCodeAutoloader, 'load']);
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
interface BlacklistInterface
|
||||
{
|
||||
/**
|
||||
* Set list of paths to be excluded from tool run
|
||||
*
|
||||
* @param array $blackList
|
||||
* @return void
|
||||
*/
|
||||
public function setBlackList(array $blackList);
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PHP Code Mess v1.3.3 tool wrapper
|
||||
*/
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
use \Magento\TestFramework\CodingStandard\ToolInterface;
|
||||
use Magento\TestFramework\CodingStandard\Tool\CodeMessOutput;
|
||||
|
||||
class CodeMessDetector implements ToolInterface
|
||||
{
|
||||
/**
|
||||
* Ruleset directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $rulesetFile;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $reportFile;
|
||||
|
||||
/**
|
||||
* @param string $rulesetFile \Directory that locates the inspection rules
|
||||
* @param string $reportFile Destination file to write inspection report to
|
||||
*/
|
||||
public function __construct($rulesetFile, $reportFile)
|
||||
{
|
||||
$this->reportFile = $reportFile;
|
||||
$this->rulesetFile = $rulesetFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the tool can be ran on the current environment
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRun()
|
||||
{
|
||||
return class_exists(\PHPMD\TextUI\Command::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function run(array $whiteList)
|
||||
{
|
||||
if (empty($whiteList)) {
|
||||
return \PHPMD\TextUI\Command::EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
$commandLineArguments = [
|
||||
'run_file_mock', //emulate script name in console arguments
|
||||
implode(',', $whiteList),
|
||||
'text', //report format
|
||||
$this->rulesetFile,
|
||||
'--reportfile',
|
||||
$this->reportFile,
|
||||
];
|
||||
|
||||
$options = new \PHPMD\TextUI\CommandLineOptions($commandLineArguments);
|
||||
|
||||
$command = new \PHPMD\TextUI\Command(new CodeMessOutput());
|
||||
|
||||
return $command->run($options, new \PHPMD\RuleSetFactory());
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PHP Code Mess v1.3.3 tool wrapper
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
use PHPMD\Console\Output;
|
||||
|
||||
class CodeMessOutput extends Output
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function doWrite($message)
|
||||
{
|
||||
// TODO: Implement doWrite() method.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
use Magento\TestFramework\CodingStandard\Tool\CodeSniffer\Wrapper;
|
||||
use Magento\TestFramework\CodingStandard\ToolInterface;
|
||||
|
||||
/**
|
||||
* PHP Code Sniffer tool wrapper
|
||||
*/
|
||||
class CodeSniffer implements ToolInterface, ExtensionInterface
|
||||
{
|
||||
/**
|
||||
* Ruleset directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $rulesetDir;
|
||||
|
||||
/**
|
||||
* Report file
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $reportFile;
|
||||
|
||||
/**
|
||||
* PHPCS cli tool wrapper
|
||||
*
|
||||
* @var Wrapper
|
||||
*/
|
||||
private $wrapper;
|
||||
|
||||
/**
|
||||
* List of extensions for tool run
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $extensions = [
|
||||
'php' => 'PHP',
|
||||
'phtml' => 'PHP',
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $rulesetDir \Directory that locates the inspection rules
|
||||
* @param string $reportFile Destination file to write inspection report to
|
||||
* @param Wrapper $wrapper
|
||||
*/
|
||||
public function __construct($rulesetDir, $reportFile, Wrapper $wrapper)
|
||||
{
|
||||
$this->rulesetDir = $rulesetDir;
|
||||
// phpcs:ignore Magento2.Functions.DiscouragedFunction
|
||||
if (!file_exists($rulesetDir) && file_exists($fullPath = realpath(__DIR__ . '/../../../../' . $rulesetDir))) {
|
||||
$this->rulesetDir = $fullPath;
|
||||
}
|
||||
$this->reportFile = $reportFile;
|
||||
$this->wrapper = $wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setExtensions(array $extensions)
|
||||
{
|
||||
$this->extensions = $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the tool can be ran on the current environment
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRun()
|
||||
{
|
||||
return class_exists('\PHP_CodeSniffer\Runner');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function run(array $whiteList)
|
||||
{
|
||||
if (empty($whiteList)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!defined('PHP_CODESNIFFER_IN_TESTS')) {
|
||||
define('PHP_CODESNIFFER_IN_TESTS', true);
|
||||
}
|
||||
|
||||
$this->wrapper->checkRequirements();
|
||||
$settings = [];
|
||||
$settings['files'] = $whiteList;
|
||||
$settings['standards'] = [$this->rulesetDir];
|
||||
$settings['extensions'] = $this->extensions;
|
||||
$settings['reports']['full'] = $this->reportFile;
|
||||
$settings['reportWidth'] = 120;
|
||||
$this->wrapper->setSettings($settings);
|
||||
|
||||
// phpcs:ignore Magento2.Functions.DiscouragedFunction
|
||||
ob_start();
|
||||
$result = $this->wrapper->runPHPCS();
|
||||
ob_end_clean();
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PHP Code Sniffer Cli wrapper
|
||||
*/
|
||||
namespace Magento\TestFramework\CodingStandard\Tool\CodeSniffer;
|
||||
|
||||
use PHP_CodeSniffer\Config;
|
||||
use PHP_CodeSniffer\Runner;
|
||||
|
||||
/**
|
||||
* PHP Code Sniffer wrapper class
|
||||
*/
|
||||
class Wrapper extends Runner
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $settings = [];
|
||||
|
||||
/**
|
||||
* Return the current version of php code sniffer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
$version = '0.0.0';
|
||||
if (defined('\PHP_CodeSniffer\Config::VERSION')) {
|
||||
$version = Config::VERSION;
|
||||
}
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize PHPCS runner and modifies the configuration settings
|
||||
*
|
||||
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->config->extensions = $this->settings['extensions'];
|
||||
unset($this->settings['extensions']);
|
||||
|
||||
$settings = $this->config->getSettings();
|
||||
unset($settings['files']);
|
||||
|
||||
$this->config->setSettings($settings);
|
||||
|
||||
$this->config->setSettings(array_replace_recursive(
|
||||
$this->config->getSettings(),
|
||||
$this->settings
|
||||
));
|
||||
return parent::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the settings
|
||||
*
|
||||
* @param array $settings
|
||||
*/
|
||||
public function setSettings($settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
/**
|
||||
* Defines an interface for compatibility checks against a specific version.
|
||||
*/
|
||||
interface CompatibilityInterface
|
||||
{
|
||||
/**
|
||||
* Sets the version against which to test code.
|
||||
*
|
||||
* @param string $version
|
||||
* @return void
|
||||
*/
|
||||
public function setTestVersion(string $version): void;
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
use Magento\TestFramework\CodingStandard\ToolInterface;
|
||||
use SebastianBergmann\FileIterator\Facade;
|
||||
use SebastianBergmann\PHPCPD\Detector\Detector;
|
||||
use SebastianBergmann\PHPCPD\Detector\Strategy\DefaultStrategy;
|
||||
use SebastianBergmann\PHPCPD\Log\PMD;
|
||||
use SebastianBergmann\PHPCPD\Log\Text;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* PHP Copy Paste Detector tool wrapper
|
||||
*/
|
||||
class CopyPasteDetector implements ToolInterface, BlacklistInterface
|
||||
{
|
||||
/**
|
||||
* Minimum number of equal lines to identify a copy paste snippet
|
||||
*/
|
||||
private const MIN_LINES = 13;
|
||||
|
||||
/**
|
||||
* Destination file to write inspection report to
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $reportFile;
|
||||
|
||||
/**
|
||||
* List of paths to be excluded from tool run
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $blacklist;
|
||||
|
||||
/**
|
||||
* @param string $reportFile
|
||||
*/
|
||||
public function __construct(string $reportFile)
|
||||
{
|
||||
$this->reportFile = $reportFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setBlackList(array $blackList): void
|
||||
{
|
||||
$this->blacklist = $blackList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the tool can be run in the current environment
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRun(): bool
|
||||
{
|
||||
return class_exists(Detector::class)
|
||||
&& class_exists(Facade::class)
|
||||
&& class_exists(Finder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tool for files specified
|
||||
*
|
||||
* @param array $whiteList Files/directories to be inspected
|
||||
* @return bool
|
||||
*/
|
||||
public function run(array $whiteList): bool
|
||||
{
|
||||
$clones = (new Detector(new DefaultStrategy()))->copyPasteDetection(
|
||||
(new Facade())->getFilesAsArray(
|
||||
$whiteList,
|
||||
'',
|
||||
'',
|
||||
$this->getExclude()
|
||||
),
|
||||
self::MIN_LINES
|
||||
);
|
||||
|
||||
(new PMD($this->reportFile))->processClones($clones);
|
||||
(new Text)->printResult($clones, false);
|
||||
|
||||
return count($clones) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exclude params from blacklist
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function getExclude(): array
|
||||
{
|
||||
$exclude = [];
|
||||
$blacklistedDirs = [];
|
||||
$blacklistedFileNames = [];
|
||||
$blacklistedPatterns = [];
|
||||
foreach ($this->blacklist as $file) {
|
||||
$file = trim($file);
|
||||
if (!$file) {
|
||||
continue;
|
||||
}
|
||||
$realPath = realpath(BP . '/' . $file);
|
||||
if ($realPath === false) {
|
||||
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||
if ($ext != '') {
|
||||
$blacklistedFileNames[] = $file;
|
||||
} else {
|
||||
$blacklistedPatterns[] = $file;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$exclude[] = [$realPath];
|
||||
$blacklistedDirs[] = $file;
|
||||
}
|
||||
|
||||
foreach ($blacklistedPatterns as $pattern) {
|
||||
$files = $this->find($pattern, false, $blacklistedDirs);
|
||||
if (empty($files)) {
|
||||
continue;
|
||||
}
|
||||
$exclude[] = $files;
|
||||
}
|
||||
|
||||
|
||||
foreach ($blacklistedFileNames as $fileName) {
|
||||
$files = $this->find($fileName, true, $blacklistedDirs);
|
||||
if (empty($files)) {
|
||||
continue;
|
||||
}
|
||||
$exclude[] = $files;
|
||||
}
|
||||
|
||||
return array_unique(array_merge(...$exclude));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all files by pattern
|
||||
*
|
||||
* @param string $pattern
|
||||
* @param bool $searchFiles
|
||||
* @param array $excludePaths
|
||||
* @return array
|
||||
*/
|
||||
private function find(string $pattern, bool $searchFiles, array $excludePaths): array
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder->in(BP);
|
||||
$finder->notPath($excludePaths);
|
||||
if ($searchFiles) {
|
||||
$finder->files();
|
||||
$finder->name($pattern);
|
||||
} else {
|
||||
$finder->path($pattern);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($finder as $file) {
|
||||
$result[] = $file->getRealPath();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
interface ExtensionInterface
|
||||
{
|
||||
/**
|
||||
* Set extensions for tool to run
|
||||
* Example: 'php', 'xml', 'phtml', 'css'
|
||||
*
|
||||
* @param array $extensions
|
||||
* @return void
|
||||
*/
|
||||
public function setExtensions(array $extensions);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
use Magento\TestFramework\CodingStandard\ToolInterface;
|
||||
|
||||
/**
|
||||
* PhpStan tool wrapper.
|
||||
*/
|
||||
class PhpStan implements ToolInterface
|
||||
{
|
||||
/**
|
||||
* Rule level to be used.
|
||||
*
|
||||
* @see https://github.com/phpstan/phpstan#rule-levels
|
||||
*/
|
||||
private const RULE_LEVEL = 1;
|
||||
|
||||
/**
|
||||
* Memory limit required by PHPStan for full Magento project scan.
|
||||
*/
|
||||
private const MEMORY_LIMIT = '4G';
|
||||
|
||||
/**
|
||||
* Error formatter to be used.
|
||||
*
|
||||
* @see https://github.com/phpstan/phpstan#existing-error-formatters-to-be-used
|
||||
*/
|
||||
private const ERROR_FORMAT = 'filtered';
|
||||
|
||||
/**
|
||||
* Report file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $reportFile;
|
||||
|
||||
/**
|
||||
* PHPStan configuration file in neon format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $confFile;
|
||||
|
||||
/**
|
||||
* @param string $confFile
|
||||
* @param string $reportFile
|
||||
*/
|
||||
public function __construct(string $confFile, string $reportFile)
|
||||
{
|
||||
$this->reportFile = $reportFile;
|
||||
$this->confFile = $confFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
|
||||
*/
|
||||
public function canRun(): bool
|
||||
{
|
||||
// phpcs:disable Magento2.Security.InsecureFunction
|
||||
exec($this->getCommand() . ' --version', $output, $exitCode);
|
||||
return $exitCode === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
|
||||
*/
|
||||
public function run(array $whiteList): int
|
||||
{
|
||||
if (empty($whiteList)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$command = $this->getCommand() . ' analyse' .
|
||||
' --level ' . self::RULE_LEVEL .
|
||||
' --no-progress' .
|
||||
' --error-format=' . self::ERROR_FORMAT .
|
||||
' --memory-limit=' . self::MEMORY_LIMIT .
|
||||
// phpcs:ignore Magento2.Functions.DiscouragedFunction
|
||||
' --configuration ' . escapeshellarg($this->confFile) .
|
||||
' ' . implode(' ', $whiteList) .
|
||||
' > ' . $this->reportFile;
|
||||
|
||||
// phpcs:disable Magento2.Security.InsecureFunction
|
||||
exec($command, $output, $exitCode);
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PHPStan CLI command
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getCommand(): string
|
||||
{
|
||||
// phpcs:ignore Magento2.Security.IncludeFile
|
||||
$vendorDir = require BP . '/app/etc/vendor_path.php';
|
||||
return 'php ' . BP . '/' . $vendorDir . '/bin/phpstan';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Code standard tool wrapper interface
|
||||
*/
|
||||
namespace Magento\TestFramework\CodingStandard;
|
||||
|
||||
interface ToolInterface
|
||||
{
|
||||
/**
|
||||
* Whether the tool can be ran on the current environment
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRun();
|
||||
|
||||
/**
|
||||
* Run tool for files specified
|
||||
*
|
||||
* @param array $whiteList Files/directories to be inspected
|
||||
* @return int
|
||||
*/
|
||||
public function run(array $whiteList);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
/**
|
||||
* Class provides dependency rule for analytics.xml config file.
|
||||
*/
|
||||
class AnalyticsConfigRule implements RuleInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getDependencyInfo($currentModule, $fileType, $file, &$contents)
|
||||
{
|
||||
if ('config' != $fileType || !preg_match('#.*/analytics\.xml$#', $file)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependenciesInfo = [];
|
||||
if (preg_match_all('#<[customProvider|reportProvider][^>]*class=[\'"]([^\'"]+)[\'"]#i', $contents, $matches)) {
|
||||
$classes = array_pop($matches);
|
||||
foreach ($classes as $class) {
|
||||
$classParts = explode('\\', $class);
|
||||
$module = implode('\\', array_slice($classParts, 0, 2));
|
||||
if (strtolower($currentModule) !== strtolower($module)) {
|
||||
$dependenciesInfo[] = [
|
||||
'modules' => [$module],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => $file,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dependenciesInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Rule for searching DB dependency
|
||||
*
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
/**
|
||||
* Class to get DB dependencies information
|
||||
*/
|
||||
class DbRule implements \Magento\TestFramework\Dependency\RuleInterface
|
||||
{
|
||||
/**
|
||||
* Map of tables and modules
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_moduleTableMap;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $tables
|
||||
*/
|
||||
public function __construct(array $tables)
|
||||
{
|
||||
$this->_moduleTableMap = $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets alien dependencies information for current module by analyzing file's contents
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $fileType
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencyInfo($currentModule, $fileType, $file, &$contents)
|
||||
{
|
||||
if ('php' !== $fileType || !preg_match('#.*/(Setup|Resource|Query)/.*\.php$#', $file)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependenciesInfo = [];
|
||||
$unKnowTables = [];
|
||||
if (preg_match_all('#>gettable(name)?\([\'"]([^\'"]+)[\'"]\)#i', $contents, $matches)) {
|
||||
$tables = array_pop($matches);
|
||||
foreach ($tables as $table) {
|
||||
if (!isset($this->_moduleTableMap[$table])) {
|
||||
$unKnowTables[$file][$table] = $table;
|
||||
continue;
|
||||
}
|
||||
if (strtolower($currentModule) !== strtolower($this->_moduleTableMap[$table])) {
|
||||
$dependenciesInfo[] = [
|
||||
'modules' => [$this->_moduleTableMap[$table]],
|
||||
'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD,
|
||||
'source' => $table,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($unKnowTables as $tables) {
|
||||
foreach ($tables as $table) {
|
||||
$dependenciesInfo[] = ['modules' => ['Unknown'], 'source' => $table];
|
||||
}
|
||||
}
|
||||
return $dependenciesInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* Rule for searching php file dependency
|
||||
*
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
use Magento\Framework\App\Utility\Classes;
|
||||
use Magento\Framework\App\Utility\Files;
|
||||
use Magento\TestFramework\Dependency\VirtualType\VirtualTypeMapper;
|
||||
|
||||
/**
|
||||
* Class provide dependency rule for di.xml config files.
|
||||
*/
|
||||
class DiRule implements RuleInterface
|
||||
{
|
||||
/**
|
||||
* @var VirtualTypeMapper
|
||||
*/
|
||||
private $mapper;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $pattern;
|
||||
|
||||
/**
|
||||
* @param VirtualTypeMapper $mapper
|
||||
*/
|
||||
public function __construct(VirtualTypeMapper $mapper)
|
||||
{
|
||||
$this->mapper = $mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class name pattern.
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getPattern()
|
||||
{
|
||||
if ($this->pattern === null) {
|
||||
$this->pattern = '~\b(?<class>(?<module>('
|
||||
. implode('[_\\\\]|', Files::init()->getNamespaces())
|
||||
. '[_\\\\])[a-zA-Z0-9]+)[a-zA-Z0-9_\\\\]*)\b~';
|
||||
}
|
||||
|
||||
return $this->pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $tagNameMap = [
|
||||
'type' => ['name'],
|
||||
'preference' => [
|
||||
'type',
|
||||
'for'
|
||||
],
|
||||
'plugin' => ['type'],
|
||||
'virtualType' => ['type']
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets alien dependencies information for current module by analyzing file's contents
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $fileType
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function getDependencyInfo($currentModule, $fileType, $file, &$contents)
|
||||
{
|
||||
//phpcs:ignore Magento2.Functions.DiscouragedFunction
|
||||
if (pathinfo($file, PATHINFO_BASENAME) !== 'di.xml') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependenciesInfo = [];
|
||||
$scope = $this->mapper->getScopeFromFile($file);
|
||||
foreach ($this->fetchPossibleDependencies($contents) as $type => $deps) {
|
||||
foreach ($deps as $dep) {
|
||||
$dep = $this->mapper->getType($dep, $scope);
|
||||
if (preg_match($this->getPattern(), $dep, $matches)) {
|
||||
$referenceModule = str_replace('_', '\\', $matches['module']);
|
||||
if ($currentModule === $referenceModule) {
|
||||
continue;
|
||||
}
|
||||
$dependenciesInfo[] = [
|
||||
'modules' => [$referenceModule],
|
||||
'type' => $type,
|
||||
'source' => $matches['class'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $dependenciesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all possible dependencies.
|
||||
*
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
private function fetchPossibleDependencies($contents)
|
||||
{
|
||||
$doc = new \DOMDocument();
|
||||
$doc->loadXML($contents);
|
||||
return [
|
||||
RuleInterface::TYPE_SOFT => $this->getSoftDependencies($doc),
|
||||
RuleInterface::TYPE_HARD => $this->getHardDependencies($doc)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect soft dependencies.
|
||||
*
|
||||
* @param \DOMDocument $doc
|
||||
* @return array
|
||||
*/
|
||||
private function getSoftDependencies(\DOMDocument $doc)
|
||||
{
|
||||
$result = [];
|
||||
foreach (self::$tagNameMap as $tagName => $attributeNames) {
|
||||
$nodes = $doc->getElementsByTagName($tagName);
|
||||
/** @var \DOMElement $node */
|
||||
foreach ($nodes as $node) {
|
||||
if ($tagName === 'virtualType' && !$node->getAttribute('type')) {
|
||||
$result[] = Classes::resolveVirtualType($node->getAttribute('name'));
|
||||
continue;
|
||||
}
|
||||
foreach ($attributeNames as $attributeName) {
|
||||
$result[] = $node->getAttribute($attributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect hard dependencies.
|
||||
*
|
||||
* @param \DOMDocument $doc
|
||||
* @return array
|
||||
*/
|
||||
private function getHardDependencies(\DOMDocument $doc)
|
||||
{
|
||||
$result = [];
|
||||
$xpath = new \DOMXPath($doc);
|
||||
$textNodes = $xpath->query('//*[@xsi:type="object"]');
|
||||
/** @var \DOMElement $node */
|
||||
foreach ($textNodes as $node) {
|
||||
$result[] = $node->nodeValue;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
<?php
|
||||
/**
|
||||
* Rule for searching dependencies in layout files
|
||||
*
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
class LayoutRule implements \Magento\TestFramework\Dependency\RuleInterface
|
||||
{
|
||||
/**
|
||||
* Default modules list.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_defaultModules = [
|
||||
'frontend' => 'Magento\Theme',
|
||||
'adminhtml' => 'Magento\Adminhtml',
|
||||
];
|
||||
|
||||
/**
|
||||
* Namespaces to analyze
|
||||
*
|
||||
* Format: {Namespace}|{Namespace}|...
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_namespaces;
|
||||
|
||||
/**
|
||||
* List of routers
|
||||
*
|
||||
* Format: array(
|
||||
* '{Router}' => '{Module_Name}'
|
||||
* )
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_mapRouters = [];
|
||||
|
||||
/**
|
||||
* List of layout blocks
|
||||
*
|
||||
* Format: array(
|
||||
* '{Area}' => array(
|
||||
* '{Block_Name}' => array('{Module_Name}' => '{Module_Name}')
|
||||
* ))
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_mapLayoutBlocks = [];
|
||||
|
||||
/**
|
||||
* List of layout handles
|
||||
*
|
||||
* Format: array(
|
||||
* '{Area}' => array(
|
||||
* '{Handle_Name}' => array('{Module_Name}' => '{Module_Name}')
|
||||
* ))
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_mapLayoutHandles = [];
|
||||
|
||||
/**
|
||||
* Unknown layout handle
|
||||
*/
|
||||
const EXCEPTION_TYPE_UNKNOWN_HANDLE = 'UNKNOWN_HANDLE';
|
||||
|
||||
/**
|
||||
* Unknown layout block
|
||||
*/
|
||||
const EXCEPTION_TYPE_UNKNOWN_BLOCK = 'UNKNOWN_BLOCK';
|
||||
|
||||
/**
|
||||
* Undefined dependency
|
||||
*/
|
||||
const EXCEPTION_TYPE_UNDEFINED_DEPENDENCY = 'UNDEFINED_DEPENDENCY';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $mapRouters
|
||||
* @param array $mapLayoutBlocks
|
||||
* @param array $mapLayoutHandles
|
||||
*/
|
||||
public function __construct(array $mapRouters, array $mapLayoutBlocks, array $mapLayoutHandles)
|
||||
{
|
||||
$this->_mapRouters = $mapRouters;
|
||||
$this->_mapLayoutBlocks = $mapLayoutBlocks;
|
||||
$this->_mapLayoutHandles = $mapLayoutHandles;
|
||||
$this->_namespaces = implode('|', \Magento\Framework\App\Utility\Files::init()->getNamespaces());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve dependencies information for current module
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $fileType
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencyInfo($currentModule, $fileType, $file, &$contents)
|
||||
{
|
||||
if ('layout' != $fileType) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attributes = $this->_caseAttributeModule($currentModule, $contents);
|
||||
$blocks = $this->_caseElementBlock($currentModule, $contents);
|
||||
$actions = $this->_caseElementAction($currentModule, $contents);
|
||||
$handle = $this->_caseLayoutHandle($currentModule, $file, $contents);
|
||||
$handleParents = $this->_caseLayoutHandleParent($currentModule, $file, $contents);
|
||||
$handleUpdates = $this->_caseLayoutHandleUpdate($currentModule, $file, $contents);
|
||||
$references = $this->_caseLayoutReference($currentModule, $file, $contents);
|
||||
|
||||
$dependencies = array_merge(
|
||||
$attributes,
|
||||
$blocks,
|
||||
$actions,
|
||||
$handle,
|
||||
$handleParents,
|
||||
$handleUpdates,
|
||||
$references
|
||||
);
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check dependencies for 'module' attribute
|
||||
*
|
||||
* Ex.: <element module="{module}">
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
protected function _caseAttributeModule($currentModule, &$contents)
|
||||
{
|
||||
$patterns = [
|
||||
'/(?<source><.+module\s*=\s*[\'"](?<namespace>' .
|
||||
$this->_namespaces .
|
||||
')[_\\\\]' .
|
||||
'(?<module>[A-Z][a-zA-Z]+)[\'"].*>)/' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
];
|
||||
return $this->_checkDependenciesByRegexp($currentModule, $contents, $patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check dependencies for <block> element
|
||||
*
|
||||
* Ex.: <block class="{name}">
|
||||
* <block template="{path}">
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
protected function _caseElementBlock($currentModule, &$contents)
|
||||
{
|
||||
$patterns = [
|
||||
'/(?<source><block.*class\s*=\s*[\'"](?<namespace>' .
|
||||
$this->_namespaces .
|
||||
')[_\\\\]' .
|
||||
'(?<module>[A-Z][a-zA-Z]+)[_\\\\]' .
|
||||
'(?:[A-Z][a-zA-Z]+[_\\\\]?){1,}[\'"].*>)/' => \Magento\Test\Integrity\DependencyTest::TYPE_HARD,
|
||||
'/(?<source><block.*template\s*=\s*[\'"](?<namespace>' .
|
||||
$this->_namespaces .
|
||||
')[_\\\\]' .
|
||||
'(?<module>[A-Z][a-zA-Z]+)::[\w\/\.]+[\'"].*>)/' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
];
|
||||
return $this->_checkDependenciesByRegexp($currentModule, $contents, $patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check dependencies for <action> element
|
||||
*
|
||||
* Ex.: <block>{name}
|
||||
* <template>{path}
|
||||
* <file>{path}
|
||||
* <element helper="{name}">
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
protected function _caseElementAction($currentModule, &$contents)
|
||||
{
|
||||
$patterns = [
|
||||
'/(?<source><block\s*>(?<namespace>' .
|
||||
$this->_namespaces .
|
||||
')[_\\\\]' .
|
||||
'(?<module>[A-Z][a-zA-Z]+)[_\\\\]' .
|
||||
'(?:[A-Z][a-zA-Z]+[_\\\\]?){1,}<\/block\s*>)/' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'/(?<source><template\s*>(?<namespace>' .
|
||||
$this->_namespaces .
|
||||
')[_\\\\]' .
|
||||
'(?<module>[A-Z][a-zA-Z]+)::[\w\/\.]+' .
|
||||
'<\/template\s*>)/' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'/(?<source><file\s*>(?<namespace>' .
|
||||
$this->_namespaces .
|
||||
')[_\\\\]' .
|
||||
'(?<module>[A-Z][a-zA-Z]+)::[\w\/\.-]+' .
|
||||
'<\/file\s*>)/' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'/(?<source><.*helper\s*=\s*[\'"](?<namespace>' .
|
||||
$this->_namespaces .
|
||||
')[_\\\\]' .
|
||||
'(?<module>[A-Z][a-zA-Z]+)[_\\\\](?:[A-Z][a-z]+[_\\\\]?){1,}::[\w]+' .
|
||||
'[\'"].*>)/' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
];
|
||||
return $this->_checkDependenciesByRegexp($currentModule, $contents, $patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check layout handles
|
||||
*
|
||||
* Ex.: <layout><{name}>...</layout>
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
protected function _caseLayoutHandle($currentModule, $file, &$contents)
|
||||
{
|
||||
$xml = simplexml_load_string($contents);
|
||||
if (!$xml) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$area = $this->_getAreaByFile($file);
|
||||
|
||||
$result = [];
|
||||
foreach ((array)$xml->xpath('/layout/child::*') as $element) {
|
||||
$check = $this->_checkDependencyLayoutHandle($currentModule, $area, $element->getName());
|
||||
$modules = isset($check['modules']) ? $check['modules'] : null;
|
||||
if ($modules) {
|
||||
foreach ($modules as $module) {
|
||||
$result[$module] = [
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'source' => $element->getName(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_getUniqueDependencies($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check layout handles parents
|
||||
*
|
||||
* Ex.: <layout_name parent="{name}">
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
protected function _caseLayoutHandleParent($currentModule, $file, &$contents)
|
||||
{
|
||||
$xml = simplexml_load_string($contents);
|
||||
if (!$xml) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$area = $this->_getAreaByFile($file);
|
||||
|
||||
$result = [];
|
||||
foreach ((array)$xml->xpath('/layout/child::*/@parent') as $element) {
|
||||
$check = $this->_checkDependencyLayoutHandle($currentModule, $area, (string)$element);
|
||||
$modules = isset($check['modules']) ? $check['modules'] : null;
|
||||
if ($modules) {
|
||||
foreach ($modules as $module) {
|
||||
$result[$module] = [
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_HARD,
|
||||
'source' => (string)$element,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_getUniqueDependencies($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check layout handles updates
|
||||
*
|
||||
* Ex.: <update handle="{name}" />
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
protected function _caseLayoutHandleUpdate($currentModule, $file, &$contents)
|
||||
{
|
||||
$xml = simplexml_load_string($contents);
|
||||
if (!$xml) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$area = $this->_getAreaByFile($file);
|
||||
|
||||
$result = [];
|
||||
foreach ((array)$xml->xpath('//update/@handle') as $element) {
|
||||
$check = $this->_checkDependencyLayoutHandle($currentModule, $area, (string)$element);
|
||||
$modules = isset($check['modules']) ? $check['modules'] : null;
|
||||
if ($modules) {
|
||||
foreach ($modules as $module) {
|
||||
$result[$module] = [
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'source' => (string)$element,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_getUniqueDependencies($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check layout references
|
||||
*
|
||||
* Ex.: <reference name="{name}">
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
protected function _caseLayoutReference($currentModule, $file, &$contents)
|
||||
{
|
||||
$xml = simplexml_load_string($contents);
|
||||
if (!$xml) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$area = $this->_getAreaByFile($file);
|
||||
|
||||
$result = [];
|
||||
foreach ((array)$xml->xpath('//reference/@name') as $element) {
|
||||
$check = $this->_checkDependencyLayoutBlock($currentModule, $area, (string)$element);
|
||||
$modules = isset($check['modules']) ? $check['modules'] : null;
|
||||
if ($modules) {
|
||||
foreach ($modules as $module) {
|
||||
$result[$module] = [
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'source' => (string)$element,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_getUniqueDependencies($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search dependencies by defined regexp patterns
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $contents
|
||||
* @param array $patterns
|
||||
* @return array
|
||||
*/
|
||||
protected function _checkDependenciesByRegexp($currentModule, &$contents, $patterns = [])
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($patterns as $pattern => $type) {
|
||||
if (preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$module = $match['namespace'] . '\\' . $match['module'];
|
||||
|
||||
if ($currentModule != $module) {
|
||||
$result[$module] = ['type' => $type, 'source' => $match['source']];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_getUniqueDependencies($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check layout handle dependency
|
||||
*
|
||||
* Return: array(
|
||||
* 'modules' // dependent modules
|
||||
* 'source' // source text
|
||||
* )
|
||||
*
|
||||
* @param admin $currentModule
|
||||
* @param string $area
|
||||
* @param string $handle
|
||||
* @return string[]
|
||||
*/
|
||||
protected function _checkDependencyLayoutHandle($currentModule, $area, $handle)
|
||||
{
|
||||
$chunks = explode('_', $handle);
|
||||
if (count($chunks) > 1) {
|
||||
// Remove 'action' part from handle name
|
||||
array_pop($chunks);
|
||||
}
|
||||
$router = implode('_', $chunks);
|
||||
if (isset($this->_mapRouters[$router])) {
|
||||
// CASE 1: Single dependency
|
||||
$modules = $this->_mapRouters[$router];
|
||||
if (!in_array($currentModule, $modules)) {
|
||||
return ['modules' => $modules];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->_mapLayoutHandles[$area][$handle])) {
|
||||
// CASE 2: No dependencies
|
||||
$modules = $this->_mapLayoutHandles[$area][$handle];
|
||||
if (isset($modules[$currentModule])) {
|
||||
return ['modules' => []];
|
||||
}
|
||||
|
||||
// CASE 3: Single dependency
|
||||
if (1 == count($modules)) {
|
||||
return ['modules' => $modules];
|
||||
}
|
||||
|
||||
// CASE 4: Default module dependency
|
||||
$defaultModule = $this->_getDefaultModuleName($area);
|
||||
if (isset($modules[$defaultModule])) {
|
||||
return ['modules' => [$defaultModule]];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check layout block dependency
|
||||
*
|
||||
* Return: array(
|
||||
* 'modules' // dependent modules
|
||||
* 'source' // source text
|
||||
* )
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $area
|
||||
* @param string $block
|
||||
* @return string[]
|
||||
*/
|
||||
protected function _checkDependencyLayoutBlock($currentModule, $area, $block)
|
||||
{
|
||||
if (isset($this->_mapLayoutBlocks[$area][$block])) {
|
||||
// CASE 1: No dependencies
|
||||
$modules = $this->_mapLayoutBlocks[$area][$block];
|
||||
if (isset($modules[$currentModule])) {
|
||||
return ['modules' => []];
|
||||
}
|
||||
|
||||
// CASE 2: Single dependency
|
||||
if (1 == count($modules)) {
|
||||
return ['modules' => $modules];
|
||||
}
|
||||
|
||||
// CASE 3: Default module dependency
|
||||
$defaultModule = $this->_getDefaultModuleName($area);
|
||||
if (isset($modules[$defaultModule])) {
|
||||
return ['modules' => [$defaultModule]];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get area from file path
|
||||
*
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
protected function _getAreaByFile($file)
|
||||
{
|
||||
$area = 'default';
|
||||
if (preg_match('/\/(?<area>adminhtml|frontend)\//', $file, $matches)) {
|
||||
$area = $matches['area'];
|
||||
}
|
||||
return $area;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve unique dependencies
|
||||
*
|
||||
* @param array $dependencies
|
||||
* @return array
|
||||
*/
|
||||
protected function _getUniqueDependencies($dependencies = [])
|
||||
{
|
||||
$result = [];
|
||||
foreach ($dependencies as $module => $value) {
|
||||
$result[] = ['modules' => [$module], 'type' => $value['type'], 'source' => $value['source']];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve default module name (by area)
|
||||
*
|
||||
* @param string $area
|
||||
* @return string|null
|
||||
*/
|
||||
protected function _getDefaultModuleName($area = 'default')
|
||||
{
|
||||
if (isset($this->_defaultModules[$area])) {
|
||||
return $this->_defaultModules[$area];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,614 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
use Magento\Framework\App\Utility\Files;
|
||||
use Magento\Framework\Config\Reader\Filesystem as ConfigReader;
|
||||
use Magento\Framework\Exception\ConfigurationMismatchException;
|
||||
use Magento\Framework\Exception\LocalizedException;
|
||||
use Magento\Framework\UrlInterface;
|
||||
use Magento\TestFramework\Dependency\Reader\ClassScanner;
|
||||
use Magento\TestFramework\Dependency\Route\RouteMapper;
|
||||
use Magento\TestFramework\Exception\NoSuchActionException;
|
||||
use Magento\TestFramework\Inspection\Exception;
|
||||
|
||||
/**
|
||||
* Rule to check the dependencies between modules based on references, getUrl and layout blocks
|
||||
*/
|
||||
class PhpRule implements RuleInterface
|
||||
{
|
||||
/**
|
||||
* List of filepaths for DI files
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $diFiles;
|
||||
|
||||
/**
|
||||
* Map from plugin classes to the subjects they modify
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $pluginMap;
|
||||
|
||||
/**
|
||||
* List of routers
|
||||
*
|
||||
* Format: array(
|
||||
* '{Router}' => '{Module_Name}'
|
||||
* )
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_mapRouters = [];
|
||||
|
||||
/**
|
||||
* List of layout blocks
|
||||
*
|
||||
* Format: array(
|
||||
* '{Area}' => array(
|
||||
* '{Block_Name}' => array('{Module_Name}' => '{Module_Name}')
|
||||
* ))
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_mapLayoutBlocks = [];
|
||||
|
||||
/**
|
||||
* Used to retrieve information from WebApi urls
|
||||
* @var ConfigReader
|
||||
*/
|
||||
protected $configReader;
|
||||
|
||||
/**
|
||||
* Default modules list.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_defaultModules = [
|
||||
'frontend' => 'Magento\Theme',
|
||||
'adminhtml' => 'Magento\Adminhtml',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var RouteMapper
|
||||
*/
|
||||
private $routeMapper;
|
||||
|
||||
/**
|
||||
* Whitelists for dependency check
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $whitelists;
|
||||
|
||||
/**
|
||||
* @var ClassScanner
|
||||
*/
|
||||
private $classScanner;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $serviceMethods;
|
||||
|
||||
/**
|
||||
* @param array $mapRouters
|
||||
* @param array $mapLayoutBlocks
|
||||
* @param ConfigReader $configReader
|
||||
* @param array $pluginMap
|
||||
* @param array $whitelists
|
||||
* @param ClassScanner|null $classScanner
|
||||
* @param RouteMapper|null $routeMapper
|
||||
*/
|
||||
public function __construct(
|
||||
array $mapRouters,
|
||||
array $mapLayoutBlocks,
|
||||
ConfigReader $configReader,
|
||||
array $pluginMap = [],
|
||||
array $whitelists = [],
|
||||
ClassScanner $classScanner = null,
|
||||
RouteMapper $routeMapper = null
|
||||
) {
|
||||
$this->_mapRouters = $mapRouters;
|
||||
$this->_mapLayoutBlocks = $mapLayoutBlocks;
|
||||
$this->configReader = $configReader;
|
||||
$this->pluginMap = $pluginMap ?: null;
|
||||
$this->whitelists = $whitelists;
|
||||
$this->classScanner = $classScanner ?? new ClassScanner();
|
||||
$this->routeMapper = $routeMapper ?? new RouteMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets alien dependencies information for current module by analyzing file's contents
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $fileType
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getDependencyInfo($currentModule, $fileType, $file, &$contents)
|
||||
{
|
||||
if (!in_array($fileType, ['php', 'template', 'fixture'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependenciesInfo = [];
|
||||
$dependenciesInfo = $this->considerCaseDependencies(
|
||||
$dependenciesInfo,
|
||||
$this->caseClassesAndIdentifiers($currentModule, $file, $contents)
|
||||
);
|
||||
$dependenciesInfo = $this->considerCaseDependencies(
|
||||
$dependenciesInfo,
|
||||
$this->_caseGetUrl($currentModule, $contents, $file)
|
||||
);
|
||||
$dependenciesInfo = $this->considerCaseDependencies(
|
||||
$dependenciesInfo,
|
||||
$this->_caseLayoutBlock($currentModule, $fileType, $file, $contents)
|
||||
);
|
||||
return $dependenciesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get routes whitelist
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getRoutesWhitelist(): array
|
||||
{
|
||||
return $this->whitelists['routes'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check references to classes and identifiers defined in other modules
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function caseClassesAndIdentifiers($currentModule, $file, &$contents)
|
||||
{
|
||||
$pattern = '~\b(?<class>(?<module>('
|
||||
. implode(
|
||||
'[_\\\\]|',
|
||||
Files::init()->getNamespaces()
|
||||
)
|
||||
. '(?<delimiter>[_\\\\]))[a-zA-Z0-9]{2,})'
|
||||
. '(?<class_inside_module>\\4[a-zA-Z0-9_\\\\]{2,})?)\b'
|
||||
. '(?:::(?<module_scoped_key>[A-Za-z0-9_/.]+)[\'"])?~';
|
||||
|
||||
if (!preg_match_all($pattern, $contents, $matches)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependenciesInfo = [];
|
||||
$matches['module'] = array_unique($matches['module']);
|
||||
foreach ($matches['module'] as $i => $referenceModule) {
|
||||
$referenceModule = str_replace('_', '\\', $referenceModule);
|
||||
if ($currentModule == $referenceModule) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dependencyClass = trim($matches['class'][$i]);
|
||||
if (empty($matches['class_inside_module'][$i]) && !empty($matches['module_scoped_key'][$i])) {
|
||||
$dependencyType = RuleInterface::TYPE_SOFT;
|
||||
} else {
|
||||
$currentClass = $this->getClassFromFilepath($file);
|
||||
$dependencyType = $this->isPluginDependency($currentClass, $dependencyClass)
|
||||
? RuleInterface::TYPE_SOFT
|
||||
: RuleInterface::TYPE_HARD;
|
||||
}
|
||||
|
||||
$dependenciesInfo[] = [
|
||||
'modules' => [$referenceModule],
|
||||
'type' => $dependencyType,
|
||||
'source' => $dependencyClass,
|
||||
];
|
||||
}
|
||||
|
||||
return $dependenciesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class name from filename based on class/file naming conventions
|
||||
*
|
||||
* @param string $filepath
|
||||
* @return string
|
||||
*/
|
||||
private function getClassFromFilepath(string $filepath): string
|
||||
{
|
||||
return $this->classScanner->getClassName($filepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load DI configuration files
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function loadDiFiles()
|
||||
{
|
||||
if (!$this->diFiles) {
|
||||
$this->diFiles = Files::init()->getDiConfigs();
|
||||
}
|
||||
return $this->diFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an array of plugin info
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function loadPluginMap()
|
||||
{
|
||||
if (!$this->pluginMap) {
|
||||
foreach ($this->loadDiFiles() as $filepath) {
|
||||
$dom = new \DOMDocument();
|
||||
// phpcs:ignore Magento2.Functions.DiscouragedFunction
|
||||
$dom->loadXML(file_get_contents($filepath));
|
||||
$typeNodes = $dom->getElementsByTagName('type');
|
||||
/** @var \DOMElement $type */
|
||||
foreach ($typeNodes as $type) {
|
||||
/** @var \DOMElement $plugin */
|
||||
foreach ($type->getElementsByTagName('plugin') as $plugin) {
|
||||
$subject = $type->getAttribute('name');
|
||||
$pluginType = $plugin->getAttribute('type');
|
||||
$this->pluginMap[$pluginType] = $subject;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->pluginMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a the dependency relation is because of a plugin
|
||||
*
|
||||
* True IFF the dependent is a plugin for some class in the same module as the dependency.
|
||||
*
|
||||
* @param string $dependent
|
||||
* @param string $dependency
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function isPluginDependency($dependent, $dependency)
|
||||
{
|
||||
$pluginMap = $this->loadPluginMap();
|
||||
$subject = isset($pluginMap[$dependent])
|
||||
? $pluginMap[$dependent]
|
||||
: null;
|
||||
if ($subject === $dependency) {
|
||||
return true;
|
||||
} elseif ($subject) {
|
||||
$moduleNameLength = strpos($subject, '\\', strpos($subject, '\\') + 1);
|
||||
$subjectModule = substr($subject, 0, $moduleNameLength);
|
||||
return strpos($dependency, $subjectModule) === 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check get URL method
|
||||
*
|
||||
* Ex.: getUrl('{path}')
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $contents
|
||||
* @param string $file
|
||||
* @return array
|
||||
* @throws LocalizedException
|
||||
*/
|
||||
protected function _caseGetUrl(string $currentModule, string &$contents, string $file): array
|
||||
{
|
||||
$dependencies = [];
|
||||
$pattern = '#(\->|:)(?<source>getUrl\(([\'"])(?<path>[a-zA-Z0-9\-_*/]+)\3)\s*[,)]#';
|
||||
if (!preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER)) {
|
||||
return $dependencies;
|
||||
}
|
||||
try {
|
||||
foreach ($matches as $item) {
|
||||
$path = $item['path'];
|
||||
$modules = [];
|
||||
if (strpos($path, '*') !== false) {
|
||||
$modules = $this->processWildcardUrl($path, $file);
|
||||
} elseif (preg_match('#rest(?<service>/V1/.+)#i', $path, $apiMatch)) {
|
||||
$modules = $this->processApiUrl($apiMatch['service']);
|
||||
} else {
|
||||
$modules = $this->processStandardUrl($path);
|
||||
}
|
||||
if ($modules && !in_array($currentModule, $modules)) {
|
||||
$dependencies[] = [
|
||||
'modules' => $modules,
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => $item['source'],
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (NoSuchActionException $e) {
|
||||
if (array_search($e->getMessage(), $this->getRoutesWhitelist()) === false) {
|
||||
throw new LocalizedException(__('Invalid URL path: %1', $e->getMessage()), $e);
|
||||
}
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get module dependencies used by a wildcard Url
|
||||
*
|
||||
* @param string $urlPath
|
||||
* @param string $filePath
|
||||
* @return string[]
|
||||
* @throws NoSuchActionException
|
||||
*/
|
||||
private function processWildcardUrl(string $urlPath, string $filePath)
|
||||
{
|
||||
$filePath = strtolower($filePath);
|
||||
$urlRoutePieces = explode('/', $urlPath);
|
||||
$routeId = array_shift($urlRoutePieces);
|
||||
//Skip route wildcard processing as this requires using the routeMapper
|
||||
if ('*' === $routeId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Only handle Controllers. ie: Ignore Blocks, Templates, and Models due to complexity in static resolution
|
||||
* of route
|
||||
*/
|
||||
if (!preg_match(
|
||||
'#controller/(adminhtml/)?(?<controller_name>.+)/(?<action_name>\w+).php$#',
|
||||
$filePath,
|
||||
$fileParts
|
||||
)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$controllerName = array_shift($urlRoutePieces);
|
||||
if ('*' === $controllerName) {
|
||||
$controllerName = str_replace('/', '_', $fileParts['controller_name']);
|
||||
}
|
||||
|
||||
if (empty($urlRoutePieces) || !$urlRoutePieces[0]) {
|
||||
$actionName = UrlInterface::DEFAULT_ACTION_NAME;
|
||||
} else {
|
||||
$actionName = array_shift($urlRoutePieces);
|
||||
if ('*' === $actionName) {
|
||||
$actionName = $fileParts['action_name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->routeMapper->getDependencyByRoutePath(
|
||||
strtolower($routeId),
|
||||
strtolower($controllerName),
|
||||
strtolower($actionName)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get module dependencies used by a standard URL
|
||||
*
|
||||
* @param string $path
|
||||
* @return string[]
|
||||
* @throws NoSuchActionException
|
||||
*/
|
||||
private function processStandardUrl(string $path)
|
||||
{
|
||||
$pattern = '#(?<route_id>[a-z0-9\-_]{3,})'
|
||||
. '(/(?<controller_name>[a-z0-9\-_]+))?(/(?<action_name>[a-z0-9\-_]+))?#i';
|
||||
if (!preg_match($pattern, $path, $match)) {
|
||||
throw new NoSuchActionException('Failed to parse standard url path: ' . $path);
|
||||
}
|
||||
$routeId = $match['route_id'];
|
||||
$controllerName = $match['controller_name'] ?? UrlInterface::DEFAULT_CONTROLLER_NAME;
|
||||
$actionName = $match['action_name'] ?? UrlInterface::DEFAULT_ACTION_NAME;
|
||||
|
||||
return $this->routeMapper->getDependencyByRoutePath(
|
||||
$routeId,
|
||||
$controllerName,
|
||||
$actionName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create regex patterns from service url paths
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getServiceMethodRegexps(): array
|
||||
{
|
||||
if (!$this->serviceMethods) {
|
||||
$this->serviceMethods = [];
|
||||
$serviceRoutes = $this->configReader->read()['routes'];
|
||||
foreach ($serviceRoutes as $serviceRouteUrl => $methods) {
|
||||
$pattern = '#:\w+#';
|
||||
$replace = '\w+';
|
||||
$serviceRouteUrlRegex = preg_replace($pattern, $replace, $serviceRouteUrl);
|
||||
$serviceRouteUrlRegex = '#^' . $serviceRouteUrlRegex . '$#';
|
||||
$this->serviceMethods[$serviceRouteUrlRegex] = $methods;
|
||||
}
|
||||
}
|
||||
return $this->serviceMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get module dependencies used by an API URL
|
||||
*
|
||||
* @param string $path
|
||||
* @return string[]
|
||||
*
|
||||
* @throws NoSuchActionException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function processApiUrl(string $path): array
|
||||
{
|
||||
foreach ($this->getServiceMethodRegexps() as $serviceRouteUrlRegex => $methods) {
|
||||
/**
|
||||
* Since we expect that every service method should be within the same module, we can use the class from
|
||||
* any method
|
||||
*/
|
||||
if (preg_match($serviceRouteUrlRegex, $path)) {
|
||||
$method = reset($methods);
|
||||
|
||||
$className = $method['service']['class'];
|
||||
//get module from className
|
||||
if (preg_match('#^(?<module>\w+[\\\]\w+)#', $className, $match)) {
|
||||
return [$match['module']];
|
||||
}
|
||||
throw new Exception('Failed to parse class from className: ' . $className);
|
||||
}
|
||||
}
|
||||
throw new NoSuchActionException('Failed to match service with url path: ' . $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check layout blocks
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $fileType
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
protected function _caseLayoutBlock(string $currentModule, string $fileType, string $file, string &$contents): array
|
||||
{
|
||||
$pattern = '/[\->:]+(?<source>(?:getBlock|getBlockHtml)\([\'"](?<block>[\w\.\-]+)[\'"]\))/';
|
||||
|
||||
$area = $this->_getAreaByFile($file, $fileType);
|
||||
|
||||
$result = [];
|
||||
if (!preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($matches as $match) {
|
||||
if (in_array($match['block'], ['root', 'content'])) {
|
||||
continue;
|
||||
}
|
||||
$check = $this->_checkDependencyLayoutBlock($currentModule, $area, $match['block']);
|
||||
$modules = isset($check['modules']) ? $check['modules'] : null;
|
||||
if ($modules) {
|
||||
foreach ($modules as $module) {
|
||||
$result[$module] = [
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => $match['source'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->_getUniqueDependencies($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get area from file path
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $fileType
|
||||
* @return string|null
|
||||
*/
|
||||
protected function _getAreaByFile(string $file, string $fileType): ?string
|
||||
{
|
||||
if ($fileType == 'php') {
|
||||
return null;
|
||||
}
|
||||
$area = 'default';
|
||||
if (preg_match('/\/(?<area>adminhtml|frontend)\//', $file, $matches)) {
|
||||
$area = $matches['area'];
|
||||
}
|
||||
return $area;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check layout block dependency
|
||||
*
|
||||
* Return: array(
|
||||
* 'modules' // dependent modules
|
||||
* 'source' // source text
|
||||
* )
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string|null $area
|
||||
* @param string $block
|
||||
* @return array
|
||||
*/
|
||||
protected function _checkDependencyLayoutBlock(string $currentModule, ?string $area, string $block): array
|
||||
{
|
||||
if (isset($this->_mapLayoutBlocks[$area][$block]) || $area === null) {
|
||||
// CASE 1: No dependencies
|
||||
$modules = [];
|
||||
if ($area === null) {
|
||||
foreach ($this->_mapLayoutBlocks as $blocks) {
|
||||
if (array_key_exists($block, $blocks)) {
|
||||
$modules += $blocks[$block];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$modules = $this->_mapLayoutBlocks[$area][$block];
|
||||
}
|
||||
if (isset($modules[$currentModule])) {
|
||||
return ['modules' => []];
|
||||
}
|
||||
// CASE 2: Single dependency
|
||||
if (1 == count($modules)) {
|
||||
return ['modules' => $modules];
|
||||
}
|
||||
// CASE 3: Default module dependency
|
||||
$defaultModule = $this->_getDefaultModuleName($area);
|
||||
if (isset($modules[$defaultModule])) {
|
||||
return ['modules' => [$defaultModule]];
|
||||
}
|
||||
}
|
||||
// CASE 4: \Exception - Undefined block
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve default module name (by area)
|
||||
*
|
||||
* @param string $area
|
||||
* @return null
|
||||
*/
|
||||
protected function _getDefaultModuleName($area = 'default')
|
||||
{
|
||||
if (isset($this->_defaultModules[$area])) {
|
||||
return $this->_defaultModules[$area];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve unique dependencies
|
||||
*
|
||||
* @param array $dependencies
|
||||
* @return array
|
||||
*/
|
||||
protected function _getUniqueDependencies($dependencies = [])
|
||||
{
|
||||
$result = [];
|
||||
foreach ($dependencies as $module => $value) {
|
||||
$result[] = ['modules' => [$module], 'type' => $value['type'], 'source' => $value['source']];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge dependencies
|
||||
*
|
||||
* @param array $known
|
||||
* @param array $new
|
||||
* @return array
|
||||
*/
|
||||
private function considerCaseDependencies($known, $new)
|
||||
{
|
||||
return array_merge($known, $new);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Dependency\Reader;
|
||||
|
||||
use Magento\Setup\Module\Di\Code\Reader\FileClassScanner;
|
||||
|
||||
/**
|
||||
* Search classes in file by path.
|
||||
*/
|
||||
class ClassScanner
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $classNames = [];
|
||||
|
||||
/**
|
||||
* Get class name by file name.
|
||||
*
|
||||
* @param string $filePath
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClassName(string $filePath): string
|
||||
{
|
||||
if (!isset($this->classNames[$filePath])) {
|
||||
$this->classNames[$filePath] = $this->loadClassName($filePath);
|
||||
}
|
||||
|
||||
return $this->classNames[$filePath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load class name from file.
|
||||
*
|
||||
* @param string $filePath
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function loadClassName(string $filePath): string
|
||||
{
|
||||
$scanner = new FileClassScanner($filePath);
|
||||
return $scanner->getClassName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
/**
|
||||
* Class provides dependency rule for reports.xml config file.
|
||||
*/
|
||||
class ReportsConfigRule implements RuleInterface
|
||||
{
|
||||
/**
|
||||
* Map of tables and modules
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $moduleTableMap;
|
||||
|
||||
/**
|
||||
* @param array $tables
|
||||
*/
|
||||
public function __construct(array $tables)
|
||||
{
|
||||
$this->moduleTableMap = $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getDependencyInfo($currentModule, $fileType, $file, &$contents)
|
||||
{
|
||||
if ('config' != $fileType || !preg_match('#.*/reports\.xml$#', $file)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependenciesInfo = [];
|
||||
if (preg_match_all('#<source[^>]*name=[\'"]([^\'"]+)[\'"]#i', $contents, $matches)) {
|
||||
$tables = array_pop($matches);
|
||||
foreach ($tables as $table) {
|
||||
if (!isset($this->moduleTableMap[$table])) {
|
||||
continue;
|
||||
}
|
||||
if (strtolower($currentModule) !== strtolower($this->moduleTableMap[$table])) {
|
||||
$dependenciesInfo[] = [
|
||||
'modules' => [$this->moduleTableMap[$table]],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => $table,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dependenciesInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Dependency\Route;
|
||||
|
||||
use Exception;
|
||||
use Magento\Framework\App\Area;
|
||||
use Magento\Framework\App\Utility\Files;
|
||||
use Magento\Framework\Component\ComponentFile;
|
||||
use Magento\TestFramework\Exception\NoSuchActionException;
|
||||
|
||||
/**
|
||||
* Route mapper based on routes.xml declarations
|
||||
*/
|
||||
class RouteMapper
|
||||
{
|
||||
/**
|
||||
* List of routers
|
||||
*
|
||||
* Format: array(
|
||||
* '{Router_Id}' => '{Route_Id}' => ['{Module_Name}']
|
||||
* )
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $routers = [];
|
||||
|
||||
/**
|
||||
* List of routes.xml files by modules
|
||||
*
|
||||
* Format: array(
|
||||
* '{Module_Name}' => ['{Filename}']
|
||||
* )
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $routeConfigFiles = [];
|
||||
|
||||
/**
|
||||
* List of controllers actions
|
||||
*
|
||||
* Format: array(
|
||||
* '{Router_Id}' => '{Route_Id}' => '{Controller_Name}' => '{Action_Name}' => [{'Module_Name'}]
|
||||
* )
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $actions = [];
|
||||
|
||||
/**
|
||||
* List of reserved words
|
||||
*
|
||||
* @see \Magento\Framework\App\Router\ActionList
|
||||
* @var array
|
||||
*/
|
||||
private $reservedWords = [
|
||||
'abstract' => 1,
|
||||
'and' => 1,
|
||||
'array' => 1,
|
||||
'as' => 1,
|
||||
'break' => 1,
|
||||
'callable' => 1,
|
||||
'case' => 1,
|
||||
'catch' => 1,
|
||||
'class' => 1,
|
||||
'clone' => 1,
|
||||
'const' => 1,
|
||||
'continue' => 1,
|
||||
'declare' => 1,
|
||||
'default' => 1,
|
||||
'die' => 1,
|
||||
'do' => 1,
|
||||
'echo' => 1,
|
||||
'else' => 1,
|
||||
'elseif' => 1,
|
||||
'empty' => 1,
|
||||
'enddeclare' => 1,
|
||||
'endfor' => 1,
|
||||
'endforeach' => 1,
|
||||
'endif' => 1,
|
||||
'endswitch' => 1,
|
||||
'endwhile' => 1,
|
||||
'eval' => 1,
|
||||
'exit' => 1,
|
||||
'extends' => 1,
|
||||
'final' => 1,
|
||||
'finally' => 1,
|
||||
'fn' => 1,
|
||||
'for' => 1,
|
||||
'foreach' => 1,
|
||||
'function' => 1,
|
||||
'global' => 1,
|
||||
'goto' => 1,
|
||||
'if' => 1,
|
||||
'implements' => 1,
|
||||
'include' => 1,
|
||||
'instanceof' => 1,
|
||||
'insteadof' => 1,
|
||||
'interface' => 1,
|
||||
'isset' => 1,
|
||||
'list' => 1,
|
||||
'match' => 1,
|
||||
'namespace' => 1,
|
||||
'new' => 1,
|
||||
'or' => 1,
|
||||
'print' => 1,
|
||||
'private' => 1,
|
||||
'protected' => 1,
|
||||
'public' => 1,
|
||||
'require' => 1,
|
||||
'return' => 1,
|
||||
'static' => 1,
|
||||
'switch' => 1,
|
||||
'throw' => 1,
|
||||
'trait' => 1,
|
||||
'try' => 1,
|
||||
'unset' => 1,
|
||||
'use' => 1,
|
||||
'var' => 1,
|
||||
'void' => 1,
|
||||
'while' => 1,
|
||||
'xor' => 1,
|
||||
'yield' => 1
|
||||
];
|
||||
|
||||
/**
|
||||
* Provide routing declaration by router_id
|
||||
*
|
||||
* @param string $routerId
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getRoutes(string $routerId = ''): array
|
||||
{
|
||||
$routes = [];
|
||||
if (!$routerId) {
|
||||
foreach ($this->getRoutersMap() as $routesByRouterType) {
|
||||
$routes = array_merge_recursive($routes, $routesByRouterType);
|
||||
}
|
||||
array_walk(
|
||||
$routes,
|
||||
function (&$modules) {
|
||||
$modules = array_unique($modules);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$routes = $this->getRoutersMap()[$routerId] ?? [];
|
||||
}
|
||||
return $routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide dependencies for a specific URL path
|
||||
*
|
||||
* @param string $routeId
|
||||
* @param string $controllerName
|
||||
* @param string $actionName
|
||||
* @return string[]
|
||||
* @throws NoSuchActionException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getDependencyByRoutePath(
|
||||
string $routeId,
|
||||
string $controllerName,
|
||||
string $actionName
|
||||
): array {
|
||||
$routeId = strtolower($routeId);
|
||||
$controllerName = strtolower($controllerName);
|
||||
$actionName = strtolower($actionName);
|
||||
|
||||
if (isset($this->reservedWords[$actionName])) {
|
||||
$actionName .= 'action';
|
||||
}
|
||||
|
||||
$dependencies = [];
|
||||
foreach ($this->getRouterTypes() as $routerId) {
|
||||
if (isset($this->getActionsMap()[$routerId][$routeId][$controllerName][$actionName])) {
|
||||
$dependencies[] = $this->getActionsMap()[$routerId][$routeId][$controllerName][$actionName];
|
||||
}
|
||||
}
|
||||
$dependencies = array_merge([], ...$dependencies);
|
||||
|
||||
if (empty($dependencies)) {
|
||||
throw new NoSuchActionException(implode('/', [$routeId, $controllerName, $actionName]));
|
||||
} else {
|
||||
$dependencies = array_unique($dependencies);
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of router type
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getRouterTypes()
|
||||
{
|
||||
return array_keys($this->getActionsMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide routing declaration
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getRoutersMap()
|
||||
{
|
||||
if (empty($this->routers)) {
|
||||
foreach ($this->getListRoutesXml() as $module => $configFiles) {
|
||||
foreach ($configFiles as $configFile) {
|
||||
$this->processConfigFile($module, $configFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->routers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update routers map for the module basing on the routing config file
|
||||
*
|
||||
* @param string $module
|
||||
* @param string $configFile
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function processConfigFile(string $module, string $configFile)
|
||||
{
|
||||
// Read module's routes.xml file
|
||||
$config = simplexml_load_file($configFile);
|
||||
|
||||
$routers = $config->xpath("/config/router");
|
||||
foreach ($routers as $router) {
|
||||
$routerId = (string)$router['id'];
|
||||
foreach ($router->xpath('route') as $route) {
|
||||
$routeId = (string)$route['id'];
|
||||
if (!isset($this->routers[$routerId][$routeId])) {
|
||||
$this->routers[$routerId][$routeId] = [];
|
||||
}
|
||||
if (!in_array($module, $this->routers[$routerId][$routeId])) {
|
||||
$this->routers[$routerId][$routeId][] = $module;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the list of routes.xml files (by modules)
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getListRoutesXml()
|
||||
{
|
||||
if (empty($this->routeConfigFiles)) {
|
||||
$files = Files::init()->getConfigFiles('*/routes.xml', [], false, true);
|
||||
/** @var ComponentFile $componentFile */
|
||||
foreach ($files as $componentFile) {
|
||||
$module = str_replace('_', '\\', $componentFile->getComponentName());
|
||||
$this->routeConfigFiles[$module][] = $componentFile->getFullPath();
|
||||
}
|
||||
}
|
||||
return $this->routeConfigFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of available actions
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getActionsMap(): array
|
||||
{
|
||||
if (empty($this->actions)) {
|
||||
$files = Files::init()->getPhpFiles(Files::INCLUDE_APP_CODE);
|
||||
$actionsMap = [];
|
||||
foreach ($this->getRoutersMap() as $routerId => $routes) {
|
||||
$actionsMapPerArea = [];
|
||||
foreach ($routes as $routeId => $dependencies) {
|
||||
$actionsMapPerArea[$routeId] = [];
|
||||
foreach ($dependencies as $module) {
|
||||
$moduleActions = $this->getModuleActionsMapping($module, $routerId, $files);
|
||||
$actionsMapPerArea[$routeId] =
|
||||
array_merge_recursive($actionsMapPerArea[$routeId], $moduleActions);
|
||||
}
|
||||
}
|
||||
$actionsMap[$routerId] = $actionsMapPerArea;
|
||||
}
|
||||
$this->actions = $actionsMap;
|
||||
}
|
||||
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of available module actions by router_id
|
||||
*
|
||||
* @param string $module
|
||||
* @param string $routerId
|
||||
* @param array $files
|
||||
* @return array
|
||||
*/
|
||||
private function getModuleActionsMapping(string $module, string $routerId, array $files): array
|
||||
{
|
||||
$subdirectoryPattern = str_replace('\\', DIRECTORY_SEPARATOR, $module);
|
||||
$subdirectoryPattern .= DIRECTORY_SEPARATOR . 'Controller/';
|
||||
if (array_search($routerId, [Area::AREA_ADMINHTML, Area::AREA_ADMIN], true) !== false) {
|
||||
$subdirectoryPattern .= ucfirst(Area::AREA_ADMINHTML) . DIRECTORY_SEPARATOR;
|
||||
} else {
|
||||
$subdirectoryPattern .= '(?!' . ucfirst(Area::AREA_ADMINHTML) . ')';
|
||||
}
|
||||
|
||||
$actions = preg_grep('~' . $subdirectoryPattern . '~', $files);
|
||||
|
||||
$actionsPattern = '#' . $subdirectoryPattern . '(?<controller>\S+)/'
|
||||
. '(?<action_name>\w+)\.php$#';
|
||||
|
||||
$moduleActionsMapping = [];
|
||||
foreach ($actions as $action) {
|
||||
if (preg_match($actionsPattern, $action, $matches)) {
|
||||
$controllerName = strtolower(str_replace('/', '_', $matches['controller']));
|
||||
$actionName = strtolower($matches['action_name']);
|
||||
if (!isset($moduleActionsMapping[$controllerName][$actionName])) {
|
||||
$moduleActionsMapping[$controllerName][$actionName] = [];
|
||||
}
|
||||
$moduleActionsMapping[$controllerName][$actionName][] = $module;
|
||||
}
|
||||
}
|
||||
|
||||
return $moduleActionsMapping;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Rule for searching dependencies in layout files
|
||||
*
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
interface RuleInterface
|
||||
{
|
||||
/**
|
||||
* Types of dependencies between modules
|
||||
*/
|
||||
const TYPE_SOFT = 'soft';
|
||||
|
||||
const TYPE_HARD = 'hard';
|
||||
|
||||
/**
|
||||
* Gets alien dependencies information for current module by analyzing file's contents
|
||||
*
|
||||
* @param string $currentModule
|
||||
* @param string $fileType
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencyInfo($currentModule, $fileType, $file, &$contents);
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* Rule for searching php file dependency
|
||||
*
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency\VirtualType;
|
||||
|
||||
use DOMDocument;
|
||||
use Magento\Framework\App\Utility\Files;
|
||||
|
||||
class VirtualTypeMapper
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $map = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private static $mainScope = 'global';
|
||||
|
||||
/**
|
||||
* VirtualTypeMapper constructor.
|
||||
* @param array $map
|
||||
*/
|
||||
public function __construct(array $map = [])
|
||||
{
|
||||
$this->map = $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $scope
|
||||
* @return string|null
|
||||
*/
|
||||
public function getType($name, $scope = null)
|
||||
{
|
||||
if (empty($this->map)) {
|
||||
$this->loadMap();
|
||||
}
|
||||
|
||||
$scopes = [];
|
||||
if ($scope !== null && $scope !== self::$mainScope) {
|
||||
array_unshift($scopes, $scope);
|
||||
}
|
||||
$scopes[] = self::$mainScope;
|
||||
foreach ($scopes as $scp) {
|
||||
if (isset($this->map[$scp][$name])) {
|
||||
return $this->map[$scp][$name];
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
public function getScopeFromFile($file)
|
||||
{
|
||||
$basename = basename(pathinfo($file, PATHINFO_DIRNAME));
|
||||
return $basename === 'etc' ? 'global' : $basename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function loadDiConfigs()
|
||||
{
|
||||
return Files::init()->getDiConfigs();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $diFiles
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function loadMap(array $diFiles = [])
|
||||
{
|
||||
if (empty($diFiles)) {
|
||||
$diFiles = $this->loadDiConfigs();
|
||||
}
|
||||
|
||||
foreach ($diFiles as $file) {
|
||||
$scope = $this->getScopeFromFile($file);
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadXML(file_get_contents($file));
|
||||
$nodes = $doc->getElementsByTagName('virtualType');
|
||||
/** @var \DOMElement $node */
|
||||
foreach ($nodes as $node) {
|
||||
$name = $node->getAttribute('name');
|
||||
$type = $node->getAttribute('type');
|
||||
if (empty($type)) {
|
||||
continue;
|
||||
}
|
||||
$this->map[$scope][$name] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Exception;
|
||||
|
||||
/**
|
||||
* An exception is thrown if such a controller action does not exist
|
||||
*/
|
||||
class NoSuchActionException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract shell command for the static code inspection
|
||||
*/
|
||||
namespace Magento\TestFramework\Inspection;
|
||||
|
||||
/**
|
||||
* Abstract class for commands
|
||||
*/
|
||||
abstract class AbstractCommand
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $_reportFile;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $_lastExitCode;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $_lastOutput;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $_lastRunMessage;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $reportFile Destination file to write inspection report to
|
||||
*/
|
||||
public function __construct($reportFile)
|
||||
{
|
||||
$this->_reportFile = $reportFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and execute the shell command
|
||||
*
|
||||
* @param array $whiteList Files/directories to be inspected
|
||||
* @param array $blackList Files/directories to be excluded from the inspection
|
||||
* @return bool
|
||||
* phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod
|
||||
*/
|
||||
public function run(array $whiteList, array $blackList = [])
|
||||
{
|
||||
if (file_exists($this->_reportFile)) {
|
||||
unlink($this->_reportFile);
|
||||
}
|
||||
$shellCmd = $this->_buildShellCmd($whiteList, $blackList);
|
||||
$result = $this->_execShellCmd($shellCmd);
|
||||
$this->_generateLastRunMessage();
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the command can be ran on the current environment
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canRun()
|
||||
{
|
||||
return $this->_execShellCmd($this->_buildVersionShellCmd()) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the shell command version
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
$versionOutput = $this->_execShellCmd($this->_buildVersionShellCmd());
|
||||
if (!$versionOutput) {
|
||||
return null;
|
||||
}
|
||||
return preg_match('/[^\d]*([^\s]+)/', $versionOutput, $matches) ? $matches[1] : $versionOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to the report file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReportFile()
|
||||
{
|
||||
return $this->_reportFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the shell command that outputs the version
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function _buildVersionShellCmd();
|
||||
|
||||
/**
|
||||
* Build the valid shell command
|
||||
*
|
||||
* @param array $whiteList
|
||||
* @param array $blackList
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function _buildShellCmd($whiteList, $blackList);
|
||||
|
||||
/**
|
||||
* Execute a shell command on the current environment and return its output or FALSE on failure
|
||||
*
|
||||
* @param string $shellCmd
|
||||
* @return string|false
|
||||
*/
|
||||
protected function _execShellCmd($shellCmd)
|
||||
{
|
||||
$output = [];
|
||||
//phpcs:disable
|
||||
exec($shellCmd . ' 2>&1', $output, $this->_lastExitCode);
|
||||
//phpcs:enable
|
||||
$this->_lastOutput = implode(PHP_EOL, $output);
|
||||
return $this->_lastExitCode === 0 ? $this->_lastOutput : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate message about last execution result, prepared for output to a user
|
||||
*
|
||||
* @return \Magento\TestFramework\Inspection\AbstractCommand
|
||||
*/
|
||||
protected function _generateLastRunMessage()
|
||||
{
|
||||
if ($this->_lastExitCode === null) {
|
||||
$this->_lastRunMessage = "Nothing was executed.";
|
||||
} elseif (!$this->_lastExitCode) {
|
||||
$this->_lastRunMessage = 'Success reported.';
|
||||
} elseif (file_exists($this->_reportFile)) {
|
||||
$this->_lastRunMessage = "See detailed report in '{$this->_reportFile}'.";
|
||||
} else {
|
||||
$this->_lastRunMessage = 'Command-line tool reports: ' . $this->_lastOutput;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return message from the last run of the command
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLastRunMessage()
|
||||
{
|
||||
return $this->_lastRunMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \Exceptional situation during the static inspection
|
||||
*/
|
||||
namespace Magento\TestFramework\Inspection;
|
||||
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Finder for a list of preconfigured words
|
||||
*/
|
||||
namespace Magento\TestFramework\Inspection;
|
||||
|
||||
class WordsFinder
|
||||
{
|
||||
/**
|
||||
* List of file extensions, that indicate a binary file
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_binaryExtensions = [
|
||||
'jpg', 'jpeg', 'png', 'gif', 'swf', 'mp3', 'avi', 'mov', 'flv', 'jar', 'zip',
|
||||
'eot', 'ttf', 'woff', 'woff2', 'ico', 'svg',
|
||||
];
|
||||
|
||||
/**
|
||||
* Copyright string which must be present in every non-binary file
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $copyrightString = 'Copyright © Magento, Inc. All rights reserved.';
|
||||
|
||||
/**
|
||||
* Copying string which must be present in every non-binary file right after copyright string
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $copyingString = 'See COPYING.txt for license details.';
|
||||
|
||||
/**
|
||||
* List of extensions for which copyright check must be skipped
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $copyrightSkipExtensions = ['csv', 'json', 'lock', 'md', 'txt'];
|
||||
|
||||
/**
|
||||
* List of paths where copyright check must be skipped
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $copyrightSkipList = [
|
||||
'lib/web/legacy-build.min.js'
|
||||
];
|
||||
|
||||
/**
|
||||
* Whether copyright presence should be checked or not
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isCopyrightChecked;
|
||||
|
||||
/**
|
||||
* Words to search for
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_words = [];
|
||||
|
||||
/**
|
||||
* Map of whitelisted paths to whitelisted words
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_whitelist = [];
|
||||
|
||||
/**
|
||||
* Path to base dir, used to calculate relative paths
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_baseDir;
|
||||
|
||||
/**
|
||||
* Component Registrar
|
||||
*
|
||||
* @var \Magento\Framework\Component\ComponentRegistrar
|
||||
*/
|
||||
protected $componentRegistrar;
|
||||
|
||||
/**
|
||||
* Map of phrase to exclude from the file content
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $exclude = [];
|
||||
|
||||
/**
|
||||
* @param string|array $configFiles
|
||||
* @param string $baseDir
|
||||
* @param \Magento\Framework\Component\ComponentRegistrar $componentRegistrar
|
||||
* @param bool $isCopyrightChecked
|
||||
* @throws \Magento\TestFramework\Inspection\Exception
|
||||
*/
|
||||
public function __construct($configFiles, $baseDir, $componentRegistrar, $isCopyrightChecked = false)
|
||||
{
|
||||
if (!is_dir($baseDir)) {
|
||||
throw new \Magento\TestFramework\Inspection\Exception("Base directory {$baseDir} does not exist");
|
||||
}
|
||||
$this->_baseDir = str_replace('\\', '/', realpath($baseDir));
|
||||
$this->componentRegistrar = $componentRegistrar;
|
||||
|
||||
// Load config files
|
||||
if (!is_array($configFiles)) {
|
||||
$configFiles = [$configFiles];
|
||||
}
|
||||
foreach ($configFiles as $configFile) {
|
||||
$this->_loadConfig($configFile);
|
||||
}
|
||||
|
||||
// Add config files to whitelist, as they surely contain banned words
|
||||
foreach ($configFiles as $configFile) {
|
||||
$configFile = str_replace('\\', '/', realpath($configFile));
|
||||
$this->_whitelist[$configFile] = [];
|
||||
}
|
||||
|
||||
$this->_normalizeWhitelistPaths();
|
||||
|
||||
// Final verifications
|
||||
if (!$this->_words) {
|
||||
throw new \Magento\TestFramework\Inspection\Exception('No words to check');
|
||||
}
|
||||
|
||||
$this->isCopyrightChecked = $isCopyrightChecked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from file, adding words and whitelisted entries to main config
|
||||
*
|
||||
* @param string $configFile
|
||||
* @throws \Magento\TestFramework\Inspection\Exception
|
||||
*/
|
||||
protected function _loadConfig($configFile)
|
||||
{
|
||||
if (!file_exists($configFile)) {
|
||||
throw new \Magento\TestFramework\Inspection\Exception("Configuration file {$configFile} does not exist");
|
||||
}
|
||||
try {
|
||||
$xml = new \SimpleXMLElement(file_get_contents($configFile));
|
||||
} catch (\Exception $e) {
|
||||
throw new \Magento\TestFramework\Inspection\Exception($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$this->_extractWords($xml)->_extractWhitelist($xml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract words from configuration xml
|
||||
*
|
||||
* @param \SimpleXMLElement $configXml
|
||||
* @return \Magento\TestFramework\Inspection\WordsFinder
|
||||
* @throws \Magento\TestFramework\Inspection\Exception
|
||||
*/
|
||||
protected function _extractWords(\SimpleXMLElement $configXml)
|
||||
{
|
||||
$words = [];
|
||||
$nodes = $configXml->xpath('//config/words/word');
|
||||
foreach ($nodes as $node) {
|
||||
$words[] = (string)$node;
|
||||
}
|
||||
$words = array_filter($words);
|
||||
|
||||
$words = array_merge($this->_words, $words);
|
||||
$this->_words = array_unique($words);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract whitelisted entries and words from configuration xml
|
||||
*
|
||||
* @param \SimpleXMLElement $configXml
|
||||
* @return \Magento\TestFramework\Inspection\WordsFinder
|
||||
* @throws \Magento\TestFramework\Inspection\Exception
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
*/
|
||||
protected function _extractWhitelist(\SimpleXMLElement $configXml)
|
||||
{
|
||||
// Load whitelist entries
|
||||
$whitelist = [];
|
||||
$exclude = [];
|
||||
$nodes = $configXml->xpath('//config/whitelist/item');
|
||||
foreach ($nodes as $node) {
|
||||
$path = $node->xpath('path');
|
||||
if (!$path) {
|
||||
throw new \Magento\TestFramework\Inspection\Exception(
|
||||
'A "path" must be defined for the whitelisted item'
|
||||
);
|
||||
}
|
||||
$component = $node->xpath('component');
|
||||
if ($component) {
|
||||
$componentType = $component[0]->xpath('@type')[0];
|
||||
$componentName = $component[0]->xpath('@name')[0];
|
||||
$path = $this->componentRegistrar->getPath((string)$componentType, (string)$componentName)
|
||||
. '/' . (string)$path[0];
|
||||
} else {
|
||||
$path = $this->_baseDir . '/' . (string)$path[0];
|
||||
}
|
||||
|
||||
// Words
|
||||
$words = [];
|
||||
$wordNodes = $node->xpath('word');
|
||||
if ($wordNodes) {
|
||||
foreach ($wordNodes as $wordNode) {
|
||||
$words[] = (string)$wordNode;
|
||||
}
|
||||
}
|
||||
$whitelist[$path] = $words;
|
||||
|
||||
$excludeNodes = $node->xpath('exclude');
|
||||
$excludes = [];
|
||||
if ($excludeNodes) {
|
||||
foreach ($excludeNodes as $extractNode) {
|
||||
$excludes[] = (string)$extractNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($exclude[$path])) {
|
||||
$exclude[$path] = array_merge($excludes, $exclude[$path]);
|
||||
} else {
|
||||
$exclude[$path] = $excludes;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge with already present whitelist
|
||||
foreach ($whitelist as $newPath => $newWords) {
|
||||
if (isset($this->_whitelist[$newPath])) {
|
||||
$newWords = array_merge($this->_whitelist[$newPath], $newWords);
|
||||
}
|
||||
$this->_whitelist[$newPath] = array_unique($newWords);
|
||||
}
|
||||
|
||||
foreach ($exclude as $newPath => $newWords) {
|
||||
if (isset($this->exclude[$newPath])) {
|
||||
$newWords = array_merge($this->exclude[$newPath], $newWords);
|
||||
}
|
||||
$this->exclude[$newPath] = array_unique($newWords);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize whitelist paths, so that they containt only native directory separators
|
||||
*/
|
||||
protected function _normalizeWhitelistPaths()
|
||||
{
|
||||
$whitelist = $this->_whitelist;
|
||||
$this->_whitelist = [];
|
||||
foreach ($whitelist as $whitelistFile => $whitelistWords) {
|
||||
$whitelistFile = str_replace('\\', '/', $whitelistFile);
|
||||
$this->_whitelist[$whitelistFile] = $whitelistWords;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the file content and name against the list of words. Do not check content of binary files.
|
||||
* Exclude whitelisted entries.
|
||||
*
|
||||
* @param string $file
|
||||
* @return array Words, found
|
||||
*/
|
||||
public function findWords($file)
|
||||
{
|
||||
$foundWords = $this->_findWords($file);
|
||||
if (!$foundWords) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return self::_removeWhitelistedWords($file, $foundWords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the file content and name against the list of words. Do not check content of binary files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return array
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
*/
|
||||
protected function _findWords($file)
|
||||
{
|
||||
$checkContents = !$this->_isBinaryFile($file);
|
||||
$path = $this->getSearchablePath($file);
|
||||
$contents = $checkContents ? file_get_contents($file) : '';
|
||||
if (isset($this->exclude[$file]) && !empty($this->exclude[$file])) {
|
||||
foreach ($this->exclude[$file] as $stringToEliminate) {
|
||||
$contents = str_replace($stringToEliminate, "", $contents);
|
||||
}
|
||||
}
|
||||
|
||||
$foundWords = [];
|
||||
foreach ($this->_words as $word) {
|
||||
if (stripos($path, $word) !== false || stripos($contents, $word) !== false) {
|
||||
$foundWords[] = $word;
|
||||
}
|
||||
}
|
||||
if ($contents && $this->isCopyrightChecked && !$this->isCopyrightCheckSkipped($file)
|
||||
&& (($copyrightStringPosition = mb_strpos($contents, $this->copyrightString)) === false
|
||||
|| ($copyingStringPosition = strpos($contents, $this->copyingString)) === false
|
||||
|| $copyingStringPosition - $copyrightStringPosition - mb_strlen($this->copyrightString) > 10)
|
||||
) {
|
||||
$foundWords[] = 'Copyright string is missing';
|
||||
}
|
||||
return $foundWords;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
protected function isCopyrightCheckSkipped($path)
|
||||
{
|
||||
if (in_array(pathinfo($path, PATHINFO_EXTENSION), $this->copyrightSkipExtensions)) {
|
||||
return true;
|
||||
}
|
||||
foreach ($this->copyrightSkipList as $dir) {
|
||||
if (strpos($path, $dir) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check, whether file is a binary one
|
||||
*
|
||||
* @param string $file
|
||||
* @return bool
|
||||
*/
|
||||
protected function _isBinaryFile($file)
|
||||
{
|
||||
return in_array(pathinfo($file, PATHINFO_EXTENSION), $this->_binaryExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes whitelisted words from array of found words
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $foundWords
|
||||
* @return array
|
||||
*/
|
||||
protected function _removeWhitelistedWords($path, $foundWords)
|
||||
{
|
||||
$path = str_replace('\\', '/', $path);
|
||||
foreach ($this->_whitelist as $whitelistPath => $whitelistWords) {
|
||||
if (strncmp($whitelistPath, $path, strlen($whitelistPath)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$whitelistWords) {
|
||||
// All words are permitted there
|
||||
return [];
|
||||
}
|
||||
$foundWords = array_diff($foundWords, $whitelistWords);
|
||||
}
|
||||
return $foundWords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path for words search
|
||||
*
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
protected function getSearchablePath($file)
|
||||
{
|
||||
if (strpos($file, $this->_baseDir) === false) {
|
||||
return $file;
|
||||
}
|
||||
return substr($file, strlen($this->_baseDir) + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* An abstract test class for XML/XSD validation
|
||||
*
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity;
|
||||
|
||||
abstract class AbstractConfig extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testXmlFiles()
|
||||
{
|
||||
if (null === $this->_getXmlName()) {
|
||||
$this->markTestSkipped('No XML validation of files requested');
|
||||
}
|
||||
$invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
|
||||
$invoker(
|
||||
/**
|
||||
* @param string $configFile
|
||||
*/
|
||||
function ($configFile) {
|
||||
$this->_validateFileExpectSuccess($configFile, $this->_getXsd(), $this->_getFileXsd());
|
||||
},
|
||||
\Magento\Framework\App\Utility\Files::init()->getConfigFiles($this->_getXmlName())
|
||||
);
|
||||
}
|
||||
|
||||
public function testSchemaUsingValidXml()
|
||||
{
|
||||
$xmlFile = $this->_getKnownValidXml();
|
||||
$schema = $this->_getXsd();
|
||||
$this->_validateFileExpectSuccess($xmlFile, $schema);
|
||||
}
|
||||
|
||||
public function testSchemaUsingInvalidXml($expectedErrors = null)
|
||||
{
|
||||
if (!function_exists('libxml_set_external_entity_loader')) {
|
||||
$this->markTestSkipped('Skipped due to MAGETWO-45033');
|
||||
}
|
||||
$xmlFile = $this->_getKnownInvalidXml();
|
||||
$schema = $this->_getXsd();
|
||||
$this->_validateFileExpectFailure($xmlFile, $schema, $expectedErrors);
|
||||
}
|
||||
|
||||
public function testFileSchemaUsingPartialXml()
|
||||
{
|
||||
if (!function_exists('libxml_set_external_entity_loader')) {
|
||||
$this->markTestSkipped('Skipped due to MAGETWO-45033');
|
||||
}
|
||||
$xmlFile = $this->_getKnownValidPartialXml();
|
||||
if ($xmlFile === null) {
|
||||
$this->markTestSkipped('No Partial File');
|
||||
return;
|
||||
}
|
||||
$schema = $this->_getFileXsd();
|
||||
$this->_validateFileExpectSuccess($xmlFile, $schema);
|
||||
}
|
||||
|
||||
public function testFileSchemaUsingInvalidXml($expectedErrors = null)
|
||||
{
|
||||
if (!function_exists('libxml_set_external_entity_loader')) {
|
||||
$this->markTestSkipped('Skipped due to MAGETWO-45033');
|
||||
}
|
||||
$xmlFile = $this->_getKnownInvalidPartialXml();
|
||||
if ($xmlFile === null) {
|
||||
$this->markTestSkipped('No Partial File');
|
||||
return;
|
||||
}
|
||||
$schema = $this->_getFileXsd();
|
||||
$this->_validateFileExpectFailure($xmlFile, $schema, $expectedErrors);
|
||||
}
|
||||
|
||||
public function testSchemaUsingPartialXml($expectedErrors = null)
|
||||
{
|
||||
if (!function_exists('libxml_set_external_entity_loader')) {
|
||||
$this->markTestSkipped('Skipped due to MAGETWO-45033');
|
||||
}
|
||||
$xmlFile = $this->_getKnownValidPartialXml();
|
||||
if ($xmlFile === null) {
|
||||
$this->markTestSkipped('No Partial File');
|
||||
return;
|
||||
}
|
||||
$schema = $this->_getXsd();
|
||||
$this->_validateFileExpectFailure($xmlFile, $schema, $expectedErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run schema validation against a known bad xml file with a provided schema.
|
||||
*
|
||||
* This helper expects the validation to fail and will fail a test if no errors are found.
|
||||
*
|
||||
* @param $xmlFile string a known bad xml file.
|
||||
* @param $schemaFile string schema that should find errors in the known bad xml file.
|
||||
* @param $fileSchemaFile string schema that should find errors in the known bad xml file
|
||||
*/
|
||||
protected function _validateFileExpectSuccess($xmlFile, $schemaFile, $fileSchemaFile = null)
|
||||
{
|
||||
$dom = new \DOMDocument();
|
||||
$dom->loadXML(file_get_contents($xmlFile));
|
||||
$errors = \Magento\Framework\Config\Dom::validateDomDocument($dom, $schemaFile);
|
||||
if ($errors) {
|
||||
if ($fileSchemaFile !== null) {
|
||||
$moreErrors = \Magento\Framework\Config\Dom::validateDomDocument($dom, $fileSchemaFile);
|
||||
if (empty($moreErrors)) {
|
||||
return;
|
||||
} else {
|
||||
$errors = array_merge($errors, $moreErrors);
|
||||
}
|
||||
}
|
||||
$this->fail(
|
||||
'There is a problem with the schema. A known good XML file failed validation: ' . PHP_EOL . implode(
|
||||
PHP_EOL . PHP_EOL,
|
||||
$errors
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run schema validation against an xml file with a provided schema.
|
||||
*
|
||||
* This helper expects the validation to pass and will fail a test if any errors are found.
|
||||
*
|
||||
* @param $xmlFile string a known good xml file.
|
||||
* @param $schemaFile string schema that should find no errors in the known good xml file.
|
||||
* @param $expectedErrors null|array that may contain a list of expected errors. Each element can be a substring
|
||||
* of an error, but all errors must be listed.
|
||||
*/
|
||||
protected function _validateFileExpectFailure($xmlFile, $schemaFile, $expectedErrors = null)
|
||||
{
|
||||
$dom = new \DOMDocument();
|
||||
$dom->loadXML(file_get_contents($xmlFile));
|
||||
$actualErrors = \Magento\Framework\Config\Dom::validateDomDocument($dom, $schemaFile);
|
||||
|
||||
if (isset($expectedErrors)) {
|
||||
$this->assertNotEmpty(
|
||||
$actualErrors,
|
||||
'No schema validation errors found, expected errors: ' . PHP_EOL . implode(PHP_EOL, $expectedErrors)
|
||||
);
|
||||
foreach ($expectedErrors as $expectedError) {
|
||||
$found = false;
|
||||
|
||||
foreach ($actualErrors as $errorKey => $actualError) {
|
||||
if (!(strpos($actualError, $expectedError) === false)) {
|
||||
// found expected string
|
||||
$found = true;
|
||||
// remove found error from list of actual errors
|
||||
unset($actualErrors[$errorKey]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertTrue(
|
||||
$found,
|
||||
'Failed asserting that ' . $expectedError . " is in: \n" . implode(PHP_EOL, $actualErrors)
|
||||
);
|
||||
}
|
||||
// list of actual errors should now be empty
|
||||
$this->assertEmpty($actualErrors, "There were unexpected errors: \n" . implode(PHP_EOL, $actualErrors));
|
||||
} elseif (!$actualErrors) {
|
||||
$this->fail('There is a problem with the schema. A known bad XML file passed validation');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the XSD file to be used to validate the XML
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function _getXsd();
|
||||
|
||||
/**
|
||||
* The location of a single valid complete xml file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function _getKnownValidXml();
|
||||
|
||||
/**
|
||||
* The location of a single known invalid complete xml file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function _getKnownInvalidXml();
|
||||
|
||||
/**
|
||||
* The location of a single known valid partial xml file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function _getKnownValidPartialXml();
|
||||
|
||||
/**
|
||||
* Returns the name of the XSD file to be used to validate partial XML
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function _getFileXsd();
|
||||
|
||||
/**
|
||||
* The location of a single known invalid partial xml file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function _getKnownInvalidPartialXml();
|
||||
|
||||
/**
|
||||
* Returns the name of the xml files to validate
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function _getXmlName();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library;
|
||||
|
||||
use Laminas\Code\Reflection\ClassReflection;
|
||||
use Magento\Framework\GetParameterClassTrait;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* Provide dependencies for the file
|
||||
*/
|
||||
class Injectable
|
||||
{
|
||||
use GetParameterClassTrait;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* Get dependencies
|
||||
*
|
||||
* @param ClassReflection $class
|
||||
*
|
||||
* @return string[]
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function getDependencies(ClassReflection $class): array
|
||||
{
|
||||
foreach ($class->getMethods() as $method) {
|
||||
if ($method->getDeclaringClass()->getName() !== $class->getName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($method->getParameters() as $parameter) {
|
||||
try {
|
||||
$dependency = $this->getParameterClass($parameter);
|
||||
if ($dependency !== null) {
|
||||
$this->dependencies[] = $dependency->getName();
|
||||
}
|
||||
} catch (ReflectionException $e) {
|
||||
if (preg_match('#^Class ([A-Za-z0-9_\"\\\\]+) does not exist$#', $e->getMessage(), $result)) {
|
||||
$this->dependencies[] = trim($result[1], '"');
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->dependencies;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library\PhpParser;
|
||||
|
||||
/**
|
||||
* Collect dependencies
|
||||
*
|
||||
*/
|
||||
interface DependenciesCollectorInterface
|
||||
{
|
||||
/**
|
||||
* Return list of dependencies
|
||||
*
|
||||
* @param Uses $uses
|
||||
* @return string[]
|
||||
*/
|
||||
public function getDependencies(Uses $uses);
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library\PhpParser;
|
||||
|
||||
/**
|
||||
* Class know how create any parser
|
||||
*
|
||||
*/
|
||||
class ParserFactory
|
||||
{
|
||||
/**
|
||||
* @var ParserInterface[]
|
||||
*/
|
||||
protected $parsers = [];
|
||||
|
||||
/**
|
||||
* @var Uses
|
||||
*/
|
||||
protected $uses;
|
||||
|
||||
/**
|
||||
* @var StaticCalls
|
||||
*/
|
||||
protected $staticCalls;
|
||||
|
||||
/**
|
||||
* @var Throws
|
||||
*/
|
||||
protected $throws;
|
||||
|
||||
/**
|
||||
* @var Tokens
|
||||
*/
|
||||
protected $tokens;
|
||||
|
||||
/**
|
||||
* Return all parsers
|
||||
*
|
||||
* @param Tokens $tokens
|
||||
* @return ParserInterface[]
|
||||
*/
|
||||
public function createParsers(Tokens $tokens)
|
||||
{
|
||||
if (empty($this->parsers)) {
|
||||
$this->parsers = [
|
||||
$this->uses = new Uses(),
|
||||
$this->staticCalls = new StaticCalls($tokens),
|
||||
$this->throws = new Throws($tokens),
|
||||
];
|
||||
}
|
||||
return $this->parsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uses
|
||||
*
|
||||
* @return Uses
|
||||
*/
|
||||
public function getUses()
|
||||
{
|
||||
return $this->uses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get static calls
|
||||
*
|
||||
* @return StaticCalls
|
||||
*/
|
||||
public function getStaticCalls()
|
||||
{
|
||||
return $this->staticCalls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get throws
|
||||
*
|
||||
* @return Throws
|
||||
*/
|
||||
public function getThrows()
|
||||
{
|
||||
return $this->throws;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library\PhpParser;
|
||||
|
||||
/**
|
||||
* Parser for each token type
|
||||
*
|
||||
*/
|
||||
interface ParserInterface
|
||||
{
|
||||
/**
|
||||
* Parse specific token
|
||||
*
|
||||
* @param array|string $value
|
||||
* @param int $key
|
||||
*/
|
||||
public function parse($value, $key);
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library\PhpParser;
|
||||
|
||||
/**
|
||||
* Parse static calls and collect dependencies for it
|
||||
*
|
||||
*/
|
||||
class StaticCalls implements ParserInterface, DependenciesCollectorInterface
|
||||
{
|
||||
/**
|
||||
* @var Tokens
|
||||
*/
|
||||
protected $tokens;
|
||||
|
||||
/**
|
||||
* Save static calls token key
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $staticCalls = [];
|
||||
|
||||
/**
|
||||
* Collect dependencies
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* @param Tokens $tokens
|
||||
*/
|
||||
public function __construct(Tokens $tokens)
|
||||
{
|
||||
$this->tokens = $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it's foreign dependency
|
||||
*
|
||||
* @param array $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function isTokenClass($token)
|
||||
{
|
||||
return is_array(
|
||||
$token
|
||||
) && !(in_array(
|
||||
$token[1],
|
||||
['static', 'self', 'parent']
|
||||
) || preg_match(
|
||||
'#^\$#',
|
||||
$token[1]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function parse($token, $key)
|
||||
{
|
||||
if (is_array(
|
||||
$token
|
||||
) && $token[0] == T_PAAMAYIM_NEKUDOTAYIM && $this->isTokenClass(
|
||||
$this->tokens->getPreviousToken($key)
|
||||
)
|
||||
) {
|
||||
$this->staticCalls[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return class name from token
|
||||
*
|
||||
* @param int $staticCall
|
||||
* @return string
|
||||
*/
|
||||
protected function getClassByStaticCall($staticCall)
|
||||
{
|
||||
$step = 1;
|
||||
$staticClassParts = [];
|
||||
|
||||
$token = $this->tokens->getTokenCodeByKey($staticCall - $step);
|
||||
if ($token === T_NAME_FULLY_QUALIFIED || $token === T_NAME_QUALIFIED) {
|
||||
return $this->tokens->getTokenValueByKey($staticCall - $step);
|
||||
}
|
||||
|
||||
// PHP 7 compatibility
|
||||
while ($this->tokens->getTokenCodeByKey(
|
||||
$staticCall - $step
|
||||
) == T_STRING || $this->tokens->getTokenCodeByKey(
|
||||
$staticCall - $step
|
||||
) == T_NS_SEPARATOR) {
|
||||
$staticClassParts[] = $this->tokens->getTokenValueByKey($staticCall - $step);
|
||||
$step++;
|
||||
}
|
||||
|
||||
return implode(array_reverse($staticClassParts));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getDependencies(Uses $uses)
|
||||
{
|
||||
foreach ($this->staticCalls as $staticCall) {
|
||||
$class = $this->getClassByStaticCall($staticCall);
|
||||
if ($uses->hasUses()) {
|
||||
$class = $uses->getClassNameWithNamespace($class);
|
||||
}
|
||||
$this->dependencies[] = $class;
|
||||
}
|
||||
|
||||
return $this->dependencies;
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library\PhpParser;
|
||||
|
||||
/**
|
||||
* Parse throws and collect dependencies for it
|
||||
*
|
||||
*/
|
||||
class Throws implements ParserInterface, DependenciesCollectorInterface
|
||||
{
|
||||
/**
|
||||
* @var Tokens
|
||||
*/
|
||||
protected $tokens;
|
||||
|
||||
/**
|
||||
* Collect dependencies
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* Save throw token key
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $throws = [];
|
||||
|
||||
/**
|
||||
* @param Tokens $tokens
|
||||
*/
|
||||
public function __construct(Tokens $tokens)
|
||||
{
|
||||
$this->tokens = $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function parse($token, $key)
|
||||
{
|
||||
if (is_array($token) && $token[0] == T_THROW) {
|
||||
$this->throws[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getDependencies(Uses $uses)
|
||||
{
|
||||
foreach ($this->throws as $throw) {
|
||||
$class = '';
|
||||
if ($this->tokens->getTokenCodeByKey($throw + 2) == T_NEW) {
|
||||
$step = 4;
|
||||
|
||||
$token = $this->tokens->getTokenCodeByKey($throw + $step);
|
||||
if ($token === T_NAME_FULLY_QUALIFIED || $token === T_NAME_QUALIFIED) {
|
||||
$class = $this->tokens->getTokenValueByKey($throw + $step);
|
||||
} else {
|
||||
// PHP 7 compatibility
|
||||
while ($this->tokens->getTokenCodeByKey($throw + $step) === T_STRING
|
||||
|| $this->tokens->getTokenCodeByKey($throw + $step) === T_NS_SEPARATOR) {
|
||||
$class .= trim($this->tokens->getTokenValueByKey($throw + $step));
|
||||
$step++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($uses->hasUses()) {
|
||||
$class = $uses->getClassNameWithNamespace($class);
|
||||
}
|
||||
$this->dependencies[] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->dependencies;
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library\PhpParser;
|
||||
|
||||
/**
|
||||
* Parse php code and found dependencies
|
||||
*
|
||||
*/
|
||||
class Tokens
|
||||
{
|
||||
/**
|
||||
* Collect all tokens
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tokens = [];
|
||||
|
||||
/**
|
||||
* Collect dependencies
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* Collect all parsers
|
||||
*
|
||||
* @var ParserInterface[]
|
||||
*/
|
||||
protected $parsers = [];
|
||||
|
||||
/**
|
||||
* Parser factory for creating parsers
|
||||
*
|
||||
* @var ParserFactory
|
||||
*/
|
||||
protected $parserFactory;
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param ParserFactory $parserFactory
|
||||
*/
|
||||
public function __construct($content, ParserFactory $parserFactory)
|
||||
{
|
||||
$this->tokens = token_get_all($content);
|
||||
$this->parserFactory = $parserFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse content
|
||||
*/
|
||||
public function parseContent()
|
||||
{
|
||||
foreach ($this->tokens as $k => $token) {
|
||||
foreach ($this->getParsers() as $parser) {
|
||||
$parser->parse($token, $k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all parsers
|
||||
*
|
||||
* @return ParserInterface[]
|
||||
*/
|
||||
protected function getParsers()
|
||||
{
|
||||
return $this->parserFactory->createParsers($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parsed dependencies
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencies()
|
||||
{
|
||||
return array_merge(
|
||||
$this->parserFactory->getStaticCalls()->getDependencies($this->parserFactory->getUses()),
|
||||
$this->parserFactory->getThrows()->getDependencies($this->parserFactory->getUses())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return previous token
|
||||
*
|
||||
* @param int $key
|
||||
* @param int $step
|
||||
* @return array
|
||||
*/
|
||||
public function getPreviousToken($key, $step = 1)
|
||||
{
|
||||
return $this->tokens[$key - $step];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return token code by key
|
||||
*
|
||||
* @param $key
|
||||
* @return null|int
|
||||
*/
|
||||
public function getTokenCodeByKey($key)
|
||||
{
|
||||
return is_array($this->tokens[$key]) ? $this->tokens[$key][0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return token value by key
|
||||
*
|
||||
* @param $key
|
||||
* @return string
|
||||
*/
|
||||
public function getTokenValueByKey($key)
|
||||
{
|
||||
return is_array($this->tokens[$key]) ? $this->tokens[$key][1] : $this->tokens[$key];
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library\PhpParser;
|
||||
|
||||
/**
|
||||
* Parse uses block
|
||||
*
|
||||
*/
|
||||
class Uses implements ParserInterface
|
||||
{
|
||||
/**
|
||||
* Flag for parse use block
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $parseUse = false;
|
||||
|
||||
/**
|
||||
* Collect all uses
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $uses = [];
|
||||
|
||||
/**
|
||||
* Check if uses present in content
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUses()
|
||||
{
|
||||
return !empty($this->uses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create empty uses in collection
|
||||
*/
|
||||
protected function createEmptyItem()
|
||||
{
|
||||
$this->uses[] = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return class name with namespace
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
public function getClassNameWithNamespace($class)
|
||||
{
|
||||
if (preg_match('#^\\\\#', $class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
preg_match('#^([A-Za-z0-9_]+)(.*)$#', $class, $match);
|
||||
foreach ($this->uses as $use) {
|
||||
if (preg_match('#^([A-Za-z0-9_\\\\]+)\s+as\s+(.*)$#', $use, $useMatch) && $useMatch[2] == $match[1]) {
|
||||
$class = $useMatch[1] . $match[2];
|
||||
break;
|
||||
}
|
||||
$packages = explode('\\', $use);
|
||||
end($packages);
|
||||
$lastPackageKey = key($packages);
|
||||
if ($packages[$lastPackageKey] == $match[1]) {
|
||||
$class = $use . $match[2];
|
||||
}
|
||||
}
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append part of uses into last item
|
||||
*
|
||||
* @param string $value
|
||||
*/
|
||||
protected function appendToLast($value)
|
||||
{
|
||||
end($this->uses);
|
||||
$this->uses[key($this->uses)] = ltrim($this->uses[key($this->uses)] . $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check flag parse
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isParseInProgress()
|
||||
{
|
||||
return $this->parseUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start parse
|
||||
*/
|
||||
protected function stopParse()
|
||||
{
|
||||
$this->parseUse = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop parse
|
||||
*/
|
||||
protected function startParse()
|
||||
{
|
||||
$this->parseUse = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function parse($token, $key)
|
||||
{
|
||||
if (is_array($token)) {
|
||||
if ($this->isParseInProgress()) {
|
||||
$this->appendToLast($token[1]);
|
||||
}
|
||||
if (T_USE == $token[0]) {
|
||||
$this->startParse();
|
||||
$this->createEmptyItem();
|
||||
}
|
||||
} else {
|
||||
if ($this->isParseInProgress()) {
|
||||
if ($token == ';') {
|
||||
$this->stopParse();
|
||||
}
|
||||
if ($token == ',') {
|
||||
$this->createEmptyItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity;
|
||||
|
||||
use Magento\Framework\Exception\ValidatorException;
|
||||
use Magento\Framework\Interception\Code\InterfaceValidator;
|
||||
use Magento\Framework\Phrase;
|
||||
|
||||
class PluginValidator
|
||||
{
|
||||
/** @var InterfaceValidator */
|
||||
private $frameworkValidator;
|
||||
|
||||
/**
|
||||
* @param InterfaceValidator $frameworkValidator
|
||||
*/
|
||||
public function __construct(InterfaceValidator $frameworkValidator)
|
||||
{
|
||||
$this->frameworkValidator = $frameworkValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate plugin and intercepted class
|
||||
*
|
||||
* @param string $pluginClass
|
||||
* @param string $interceptedType
|
||||
* @throws ValidatorException
|
||||
*/
|
||||
public function validate($pluginClass, $interceptedType)
|
||||
{
|
||||
$this->frameworkValidator->validate($pluginClass, $interceptedType);
|
||||
$this->validateClassNameMatchesCase($pluginClass);
|
||||
$this->validateClassNameMatchesCase($interceptedType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the class name has same capitalization as the class declaration
|
||||
*
|
||||
* @param string $className
|
||||
* @throws ValidatorException
|
||||
*/
|
||||
private function validateClassNameMatchesCase($className)
|
||||
{
|
||||
$declarationName = (new \ReflectionClass($className))->getName();
|
||||
|
||||
if (ltrim($className, '\\') != ltrim($declarationName)) {
|
||||
throw new ValidatorException(
|
||||
new Phrase(
|
||||
"Capitalization of class name '%1' does not match expected '%2'",
|
||||
[$className, $declarationName]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Utility;
|
||||
|
||||
/**
|
||||
* Helper class to add list of added new files.
|
||||
*/
|
||||
class AddedFiles
|
||||
{
|
||||
/**
|
||||
* Provide list of new files.
|
||||
*
|
||||
* @param string $changedFilesBaseDir
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getAddedFilesList(string $changedFilesBaseDir): array
|
||||
{
|
||||
return FilesSearch::getFilesFromListFile(
|
||||
$changedFilesBaseDir,
|
||||
'changed_files*.added.*',
|
||||
function () {
|
||||
// if no list files, probably, this is the dev environment
|
||||
// phpcs:ignore Generic.PHP.NoSilencedErrors,Magento2.Security.InsecureFunction
|
||||
@exec('git diff --cached --name-only --diff-filter=A', $addedFiles);
|
||||
return $addedFiles;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Utility;
|
||||
|
||||
use ReflectionNamedType;
|
||||
|
||||
/**
|
||||
* Find classes created by object manager that are not requested in constructor. All autogenerated classes should be
|
||||
* requested in constructor, otherwise compiler will not be able to find and generate these classes
|
||||
*/
|
||||
class AutogeneratedClassNotInConstructorFinder
|
||||
{
|
||||
/**
|
||||
* @var ClassNameExtractor
|
||||
*/
|
||||
private $classNameExtractor;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ClassNameExtractor $classNameExtractor
|
||||
*/
|
||||
public function __construct(
|
||||
ClassNameExtractor $classNameExtractor
|
||||
) {
|
||||
$this->classNameExtractor = $classNameExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find classes created by object manager that are not requested in constructor
|
||||
*
|
||||
* @param string $fileContent
|
||||
* @return array
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function find($fileContent)
|
||||
{
|
||||
$classNames = [];
|
||||
|
||||
preg_match_all(
|
||||
'/(get|create)\(\s*([a-z0-9\\\\]+)::class\s*\)/im',
|
||||
$fileContent,
|
||||
$shortNameMatches
|
||||
);
|
||||
|
||||
if (isset($shortNameMatches[2])) {
|
||||
foreach ($shortNameMatches[2] as $shortName) {
|
||||
if (substr($shortName, 0, 1) !== '\\') {
|
||||
$class = $this->matchPartialNamespace($fileContent, $shortName);
|
||||
} else {
|
||||
$class = $shortName;
|
||||
}
|
||||
$class = ltrim($class, '\\');
|
||||
|
||||
if (\Magento\Framework\App\Utility\Classes::isVirtual($class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$className = $this->classNameExtractor->getNameWithNamespace($fileContent);
|
||||
if ($className) {
|
||||
$arguments = $this->getConstructorArguments($className);
|
||||
if (in_array($class, $arguments)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$classNames[] = $class;
|
||||
}
|
||||
}
|
||||
return $classNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get constructor arguments
|
||||
*
|
||||
* @param string $className
|
||||
* @return string[]
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
private function getConstructorArguments($className)
|
||||
{
|
||||
$arguments = [];
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
$constructor = $reflectionClass->getConstructor();
|
||||
if ($constructor) {
|
||||
$classParameters = $constructor->getParameters();
|
||||
foreach ($classParameters as $classParameter) {
|
||||
if ($classParameter->getType()) {
|
||||
/** @var ReflectionNamedType $parameterType */
|
||||
$parameterType = $classParameter->getType();
|
||||
$arguments[] = ltrim($parameterType->getName(), '\\');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match partial namespace
|
||||
*
|
||||
* @param string $fileContent
|
||||
* @param string $shortName
|
||||
* @return string
|
||||
*/
|
||||
private function matchPartialNamespace(string $fileContent, string $shortName): string
|
||||
{
|
||||
preg_match_all(
|
||||
'/^use\s([a-z0-9\\\\]+' . str_replace('\\', '\\\\', $shortName) . ');$/im',
|
||||
$fileContent,
|
||||
$fullNameMatches
|
||||
);
|
||||
if (isset($fullNameMatches[1][0])) {
|
||||
$class = $fullNameMatches[1][0];
|
||||
} else {
|
||||
preg_match_all(
|
||||
'/^use\s([a-z0-9\\\\]+)\sas\s' . str_replace('\\', '\\\\', $shortName) . ';$/im',
|
||||
$fileContent,
|
||||
$fullNameAliasMatches
|
||||
);
|
||||
if (isset($fullNameAliasMatches[1][0])) {
|
||||
$class = $fullNameAliasMatches[1][0];
|
||||
} else {
|
||||
$forwardSlashPos = strpos($shortName, '\\');
|
||||
if (is_int($forwardSlashPos)) {
|
||||
$partialNamespace = substr($shortName, 0, $forwardSlashPos);
|
||||
} else {
|
||||
$partialNamespace = $shortName;
|
||||
}
|
||||
|
||||
preg_match_all(
|
||||
'/^use\s([a-z0-9\\\\]+)' . $partialNamespace . ';$/im',
|
||||
$fileContent,
|
||||
$partialNamespaceMatches
|
||||
);
|
||||
if ($forwardSlashPos && isset($partialNamespaceMatches[1][0])) {
|
||||
$class = $partialNamespaceMatches[1][0] . $shortName;
|
||||
} else {
|
||||
$class = $this->classNameExtractor->getNamespace($fileContent) . '\\' . $shortName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
namespace Magento\TestFramework\Utility;
|
||||
|
||||
use Magento\Framework\App\Utility\Files;
|
||||
|
||||
/**
|
||||
* A helper to gather various changed files
|
||||
* if INCREMENTAL_BUILD env variable is set by CI build infrastructure, only files changed in the
|
||||
* branch are gathered, otherwise all files
|
||||
*/
|
||||
class ChangedFiles
|
||||
{
|
||||
/**
|
||||
* File path with changed files content.
|
||||
*/
|
||||
const CHANGED_FILES_CONTENT_FILE = '/dev/tests/static/testsuite/Magento/Test/_files/changed_%s_files_content.json';
|
||||
|
||||
/**
|
||||
* Returns array of PHP-files, that use or declare Magento application classes and Magento libs
|
||||
*
|
||||
* @param string $changedFilesList
|
||||
* @param int $fileTypes
|
||||
* @return array
|
||||
*/
|
||||
public static function getPhpFiles($changedFilesList, $fileTypes = 0)
|
||||
{
|
||||
$fileUtilities = Files::init();
|
||||
if (isset($_ENV['INCREMENTAL_BUILD'])) {
|
||||
$phpFiles = [];
|
||||
foreach (glob($changedFilesList, GLOB_NOSORT) as $listFile) {
|
||||
$phpFiles = array_merge($phpFiles, file($listFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
|
||||
}
|
||||
array_walk(
|
||||
$phpFiles,
|
||||
function (&$file) {
|
||||
$file = BP . '/' . $file;
|
||||
}
|
||||
);
|
||||
if (!empty($phpFiles)) {
|
||||
$phpFiles = Files::composeDataSets($phpFiles);
|
||||
$phpFiles = array_intersect_key($phpFiles, $fileUtilities->getPhpFiles($fileTypes));
|
||||
}
|
||||
} else {
|
||||
$phpFiles = $fileUtilities->getPhpFiles($fileTypes);
|
||||
}
|
||||
|
||||
return $phpFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get changed content.
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return string
|
||||
*/
|
||||
public static function getChangedContent($fileName)
|
||||
{
|
||||
$data = [];
|
||||
$extension = self::getFileExtension($fileName);
|
||||
$fileName = ltrim(str_replace(BP, '', $fileName), DIRECTORY_SEPARATOR);
|
||||
$changedFilesContentFile = BP . sprintf(self::CHANGED_FILES_CONTENT_FILE, $extension);
|
||||
if (file_exists($changedFilesContentFile)) {
|
||||
$changedContent = file_get_contents($changedFilesContentFile);
|
||||
$data = json_decode($changedContent, true);
|
||||
}
|
||||
|
||||
return isset($data[$fileName]) ? $data[$fileName] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file extension.
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return string
|
||||
*/
|
||||
public static function getFileExtension($fileName)
|
||||
{
|
||||
$fileInfo = pathinfo($fileName);
|
||||
return isset($fileInfo['extension']) ? $fileInfo['extension'] : 'unknown';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Utility;
|
||||
|
||||
/**
|
||||
* Search for children classes in list of files.
|
||||
*/
|
||||
class ChildrenClassesSearch
|
||||
{
|
||||
/**
|
||||
* @var ClassNameExtractor
|
||||
*/
|
||||
private $classNameExtractor;
|
||||
|
||||
/**
|
||||
* ChildrenClassesSearch constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->classNameExtractor = new ClassNameExtractor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of classes name which are subclasses of mentioned class.
|
||||
*
|
||||
* @param array $fileList
|
||||
* @param string $parent
|
||||
* @param bool $asDataSet
|
||||
*
|
||||
* @return array
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getClassesWhichAreChildrenOf(array $fileList, string $parent, bool $asDataSet = true): array
|
||||
{
|
||||
$found = [];
|
||||
|
||||
foreach ($fileList as $file) {
|
||||
$name = $asDataSet ? $file[0] : $file;
|
||||
$class = $this->classNameExtractor->getNameWithNamespace(file_get_contents($name));
|
||||
|
||||
if ($class) {
|
||||
$classReflection = new \ReflectionClass($class);
|
||||
if ($classReflection->isSubclassOf($parent)) {
|
||||
$found[] = $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Utility;
|
||||
|
||||
class ClassNameExtractor
|
||||
{
|
||||
/**
|
||||
* Get class name with namespace
|
||||
*
|
||||
* @param string $fileContent
|
||||
* @return bool|string
|
||||
*/
|
||||
public function getNameWithNamespace($fileContent)
|
||||
{
|
||||
$namespace = $this->getNamespace($fileContent);
|
||||
$name = $this->getName($fileContent);
|
||||
if ($name) {
|
||||
return $namespace ? $namespace . '\\' . $name : $name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class name
|
||||
*
|
||||
* @param string $fileContent
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getName($fileContent)
|
||||
{
|
||||
$namespace = $this->getNamespace($fileContent);
|
||||
if (isset($namespace)) {
|
||||
preg_match_all(
|
||||
'/^\s*(class|abstract\sclass|interface)\s+([a-z0-9]+)(\s+(extends|implements)|\s*$)/im',
|
||||
$fileContent,
|
||||
$classMatches
|
||||
);
|
||||
if (isset($classMatches[2][0])) {
|
||||
return $classMatches[2][0];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class namespace
|
||||
*
|
||||
* @param string $fileContent
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getNamespace($fileContent)
|
||||
{
|
||||
preg_match_all(
|
||||
'/namespace\s([a-z0-9\\\\]+);/im',
|
||||
$fileContent,
|
||||
$namespaceMatches
|
||||
);
|
||||
if (isset($namespaceMatches[1][0])) {
|
||||
return $namespaceMatches[1][0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
namespace Magento\TestFramework\Utility;
|
||||
|
||||
/**
|
||||
* Searches for usage of classes, namespaces, functions, etc in PHP files
|
||||
*/
|
||||
class CodeCheck
|
||||
{
|
||||
/**
|
||||
* Check if the class is used in the content
|
||||
*
|
||||
* @param string $className
|
||||
* @param string $content
|
||||
* @return bool
|
||||
*/
|
||||
public static function isClassUsed($className, $content)
|
||||
{
|
||||
/* avoid matching namespace instead of class */
|
||||
$content = preg_replace('/namespace[^;]+;/', '', $content);
|
||||
return self::_isRegexpMatched('/[^a-z\d_\$]' . preg_quote($className, '/') . '[^a-z\d_\\\\]/iS', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the namespace is used in the content
|
||||
*
|
||||
* @param string $namespace
|
||||
* @param string $content
|
||||
* @return bool
|
||||
*/
|
||||
public static function isNamespaceUsed($namespace, $content)
|
||||
{
|
||||
return self::_isRegexpMatched('/namespace\s+' . preg_quote($namespace, '/') . ';/S', $content)
|
||||
|| self::_isRegexpMatched('/[^a-zA-Z\d_]' . preg_quote($namespace . '\\', '/') . '/S', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the specified function is called in the content.
|
||||
* Note that declarations are not considered.
|
||||
*
|
||||
* If class context is not specified, invocation of all functions or methods (of any class)
|
||||
* will be matched across the board
|
||||
*
|
||||
* If some class is specified, only its methods will be matched as follows:
|
||||
* - usage of class::method
|
||||
* - usage of $this, self and static within the class and its descendants
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $content
|
||||
* @param string $class
|
||||
* @return bool
|
||||
*/
|
||||
public static function isFunctionCalled($method, $content, $class = null)
|
||||
{
|
||||
$quotedMethod = preg_quote($method, '/');
|
||||
if (!$class) {
|
||||
return self::_isRegexpMatched(
|
||||
'/(?<![a-z\d_:]|->|function\s)' . $quotedMethod . '\s*\(/iS',
|
||||
$content
|
||||
);
|
||||
}
|
||||
// without opening parentheses to match static callbacks notation
|
||||
if (self::_isRegexpMatched(
|
||||
'/' . preg_quote($class, '/') . '::\s*' . $quotedMethod . '[^a-z\d_]/iS',
|
||||
$content
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (self::isClassOrInterface($content, $class) || self::isDirectDescendant($content, $class)) {
|
||||
return self::_isRegexpMatched('/this->' . $quotedMethod . '\s*\(/iS', $content)
|
||||
|| self::_isRegexpMatched(
|
||||
'/(self|static|parent)::\s*' . $quotedMethod . '\s*\(/iS',
|
||||
$content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if methods or functions are used in the content
|
||||
*
|
||||
* If class context is specified, only the class and its descendants will be checked.
|
||||
*
|
||||
* @param string $property
|
||||
* @param string $content
|
||||
* @param string $class
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPropertyUsed($property, $content, $class = null)
|
||||
{
|
||||
if ($class) {
|
||||
if (!self::isClassOrInterface($content, $class) && !self::isDirectDescendant($content, $class)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return self::_isRegexpMatched(
|
||||
'/[^a-z\d_]' . preg_quote($property, '/') . '[^a-z\d_]/iS',
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze content to determine whether it is a declaration of the specified class/interface
|
||||
*
|
||||
* @param string $content
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public static function isClassOrInterface($content, $name)
|
||||
{
|
||||
return self::_isRegexpMatched('/\b(?:class|interface)\s+' . preg_quote($name, '/') . '\b[^{]*\{/iS', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze content to determine whether this is a direct descendant of the specified class/interface
|
||||
*
|
||||
* @param string $content
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public static function isDirectDescendant($content, $name)
|
||||
{
|
||||
$name = preg_quote($name, '/');
|
||||
return self::_isRegexpMatched(
|
||||
'/\s+extends\s+\\\\?' . $name . '\b|\s+implements\s+[^{]*\b' . $name . '\b[^{^\\\\]*\{/iS',
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content matches the regexp
|
||||
*
|
||||
* @param string $regexp
|
||||
* @param string $content
|
||||
* @throws \Exception
|
||||
* @return bool True if the content matches the regexp
|
||||
*/
|
||||
protected static function _isRegexpMatched($regexp, $content)
|
||||
{
|
||||
$result = preg_match($regexp, $content);
|
||||
if ($result === false) {
|
||||
throw new \Exception('An error occurred when matching regexp "' . $regexp . '""');
|
||||
}
|
||||
return 1 === $result;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Utility\File;
|
||||
|
||||
/**
|
||||
* Factory for \RegexIterator
|
||||
*/
|
||||
class RegexIteratorFactory
|
||||
{
|
||||
/**
|
||||
* Create instance of \RegexIterator
|
||||
*
|
||||
* @param string $directoryPath
|
||||
* @param string $regexp
|
||||
* @return \RegexIterator
|
||||
*/
|
||||
public function create($directoryPath, $regexp)
|
||||
{
|
||||
$directory = new \RecursiveDirectoryIterator($directoryPath);
|
||||
$recursiveIterator = new \RecursiveIteratorIterator($directory);
|
||||
return new \RegexIterator($recursiveIterator, $regexp, \RegexIterator::GET_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Utility;
|
||||
|
||||
/**
|
||||
* Helper class to search files by provided directory and file pattern.
|
||||
*/
|
||||
class FilesSearch
|
||||
{
|
||||
/**
|
||||
* Read files from generated lists.
|
||||
*
|
||||
* @param string $listsBaseDir
|
||||
* @param string $listFilePattern
|
||||
* @param callable $noListCallback
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getFilesFromListFile(
|
||||
string $listsBaseDir,
|
||||
string $listFilePattern,
|
||||
callable $noListCallback
|
||||
): array {
|
||||
$filesDefinedInList = [];
|
||||
$listFiles = glob($listsBaseDir . '/_files/' . $listFilePattern);
|
||||
if (!empty($listFiles)) {
|
||||
foreach ($listFiles as $listFile) {
|
||||
$filesDefinedInList[] = file($listFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
}
|
||||
$filesDefinedInList = array_merge([], ...$filesDefinedInList);
|
||||
} else {
|
||||
$filesDefinedInList = call_user_func($noListCallback);
|
||||
}
|
||||
array_walk(
|
||||
$filesDefinedInList,
|
||||
function (&$file) {
|
||||
$file = BP . '/' . $file;
|
||||
}
|
||||
);
|
||||
$filesDefinedInList = array_values(array_unique($filesDefinedInList));
|
||||
|
||||
return $filesDefinedInList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Utility;
|
||||
|
||||
/**
|
||||
* Check if one or more functions are used in the file
|
||||
*/
|
||||
class FunctionDetector
|
||||
{
|
||||
/**
|
||||
* Detect functions in given file
|
||||
*
|
||||
* return result in this format:
|
||||
* [
|
||||
* line_number => [
|
||||
* function_name_1,
|
||||
* function_name_2,
|
||||
* function_name_3,
|
||||
* ],
|
||||
* line_number => [
|
||||
* function_name_1,
|
||||
* function_name_2,
|
||||
* function_name_3,
|
||||
* ],
|
||||
* ]
|
||||
*
|
||||
* @param string $filePath
|
||||
* @param string[] $functions
|
||||
* @return array
|
||||
*/
|
||||
public function detect($filePath, $functions)
|
||||
{
|
||||
$result = [];
|
||||
$regexp = $this->composeRegexp($functions);
|
||||
|
||||
if (!$regexp) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$fileContent = \Magento\TestFramework\Utility\ChangedFiles::getChangedContent($filePath);
|
||||
$file = file($filePath);
|
||||
|
||||
return $fileContent
|
||||
? $this->grepChangedContent($file, $regexp, $functions, $fileContent)
|
||||
: $this->grepFile($file, $regexp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grep only changed content.
|
||||
*
|
||||
* @param array $file
|
||||
* @param string $regexp
|
||||
* @param string[] $functions
|
||||
* @param string $fileContent
|
||||
* @return array
|
||||
*/
|
||||
public function grepChangedContent(array $file, $regexp, $functions, $fileContent)
|
||||
{
|
||||
$result = [];
|
||||
$matches = preg_grep($regexp, explode("\n", $fileContent));
|
||||
if (!empty($matches)) {
|
||||
foreach ($matches as $line) {
|
||||
$actualFunctions = [];
|
||||
foreach ($functions as $function) {
|
||||
if (false !== strpos($line, $function)) {
|
||||
$actualFunctions[] = $function;
|
||||
}
|
||||
}
|
||||
$result[array_search($line . "\n", $file) + 1] = $actualFunctions;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grep File.
|
||||
*
|
||||
* @param array $file
|
||||
* @param string $regexp
|
||||
* @return array
|
||||
*/
|
||||
public function grepFile(array $file, $regexp)
|
||||
{
|
||||
$result = [];
|
||||
array_unshift($file, '');
|
||||
$lines = preg_grep($regexp, $file);
|
||||
foreach ($lines as $lineNumber => $line) {
|
||||
if (preg_match_all($regexp, $line, $matches)) {
|
||||
$result[$lineNumber] = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose regular expression
|
||||
*
|
||||
* @param array $functions
|
||||
* @return string
|
||||
*/
|
||||
private function composeRegexp(array $functions)
|
||||
{
|
||||
if (empty($functions)) {
|
||||
return '';
|
||||
}
|
||||
return '/(?<!function |->|::)\b(' . join('|', $functions) . ')\s*\(/i';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<ruleset name="Magento">
|
||||
<description>Custom Magento coding standard.</description>
|
||||
<rule ref="./../../../../../vendor/magento/magento-coding-standard/Magento2"/>
|
||||
<rule ref="./../../../../../vendor/magento/magento-coding-standard/Magento2Framework"/>
|
||||
<exclude-pattern>*\.min.js$</exclude-pattern>
|
||||
<exclude-pattern>lib\/web\/(?!mage)</exclude-pattern>
|
||||
</ruleset>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
use \Magento\Framework\App\Filesystem\DirectoryList;
|
||||
|
||||
//phpcs:ignore Magento2.Functions.DiscouragedFunction
|
||||
$baseDir = realpath(__DIR__ . '/../../../../');
|
||||
// phpcs:ignore Magento2.Security.IncludeFile.FoundIncludeFile
|
||||
require $baseDir . '/app/autoload.php';
|
||||
// phpcs:ignore Magento2.Security.IncludeFile.FoundIncludeFile
|
||||
require $baseDir . '/vendor/squizlabs/php_codesniffer/autoload.php';
|
||||
$testsBaseDir = $baseDir . '/dev/tests/static';
|
||||
$autoloadWrapper = \Magento\Framework\Autoload\AutoloaderRegistry::getAutoloader();
|
||||
$autoloadWrapper->addPsr4(
|
||||
'Magento\\',
|
||||
[
|
||||
$testsBaseDir . '/testsuite/Magento/',
|
||||
$testsBaseDir . '/framework/Magento/',
|
||||
$testsBaseDir . '/framework/tests/unit/testsuite/Magento',
|
||||
]
|
||||
);
|
||||
$autoloadWrapper->addPsr4(
|
||||
'Magento\\TestFramework\\',
|
||||
[
|
||||
$testsBaseDir . '/framework/Magento/TestFramework/',
|
||||
$testsBaseDir . '/../integration/framework/Magento/TestFramework/',
|
||||
$testsBaseDir . '/../api-functional/framework/Magento/TestFramework/',
|
||||
]
|
||||
);
|
||||
$autoloadWrapper->addPsr4('Magento\\CodeMessDetector\\', $testsBaseDir . '/framework/Magento/CodeMessDetector');
|
||||
|
||||
$generatedCode = DirectoryList::getDefaultConfig()[DirectoryList::GENERATED_CODE][DirectoryList::PATH];
|
||||
$autoloadWrapper->addPsr4('Magento\\', $baseDir . '/' . $generatedCode . '/Magento/');
|
||||
|
||||
$setup = DirectoryList::getDefaultConfig()[DirectoryList::SETUP][DirectoryList::PATH];
|
||||
$autoloadWrapper->addPsr4('Magento\\Setup\\', $baseDir . '/' . $setup . '/Magento/Setup/');
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
// phpcs:disable
|
||||
|
||||
use Magento\Framework\App\Utility\Files;
|
||||
use Magento\Framework\Component\ComponentRegistrar;
|
||||
use Magento\Framework\Component\DirSearch;
|
||||
use Magento\Framework\Filesystem\Directory\ReadFactory;
|
||||
use Magento\Framework\Filesystem\DriverPool;
|
||||
use Magento\Framework\View\Design\Theme\ThemePackageList;
|
||||
use Magento\Framework\View\Design\Theme\ThemePackageFactory;
|
||||
|
||||
require __DIR__ . '/autoload.php';
|
||||
|
||||
error_reporting(E_ALL);
|
||||
if (!defined('TESTS_TEMP_DIR')) {
|
||||
define('TESTS_TEMP_DIR', dirname(__DIR__) . '/tmp');
|
||||
}
|
||||
|
||||
// PHP 8 compatibility. Define constants that are not present in PHP < 8.0
|
||||
if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 80000) {
|
||||
if (!defined('T_NAME_QUALIFIED')) {
|
||||
define('T_NAME_QUALIFIED', 24001);
|
||||
}
|
||||
if (!defined('T_NAME_FULLY_QUALIFIED')) {
|
||||
define('T_NAME_FULLY_QUALIFIED', 24002);
|
||||
}
|
||||
}
|
||||
|
||||
setCustomErrorHandler();
|
||||
|
||||
$componentRegistrar = new ComponentRegistrar();
|
||||
$dirSearch = new DirSearch($componentRegistrar, new ReadFactory(new DriverPool()));
|
||||
$themePackageList = new ThemePackageList($componentRegistrar, new ThemePackageFactory());
|
||||
$serializer = new \Magento\Framework\Serialize\Serializer\Json();
|
||||
$regexIteratorFactory = new Magento\Framework\App\Utility\RegexIteratorFactory();
|
||||
\Magento\Framework\App\Utility\Files::setInstance(
|
||||
new Files($componentRegistrar, $dirSearch, $themePackageList, $serializer, $regexIteratorFactory)
|
||||
);
|
||||
|
||||
/**
|
||||
* Set custom error handler
|
||||
*/
|
||||
function setCustomErrorHandler()
|
||||
{
|
||||
set_error_handler(
|
||||
function ($errNo, $errStr, $errFile, $errLine) {
|
||||
$errLevel = error_reporting();
|
||||
if (($errLevel & $errNo) !== 0) {
|
||||
$errorNames = [
|
||||
E_ERROR => 'Error',
|
||||
E_WARNING => 'Warning',
|
||||
E_PARSE => 'Parse',
|
||||
E_NOTICE => 'Notice',
|
||||
E_CORE_ERROR => 'Core Error',
|
||||
E_CORE_WARNING => 'Core Warning',
|
||||
E_COMPILE_ERROR => 'Compile Error',
|
||||
E_COMPILE_WARNING => 'Compile Warning',
|
||||
E_USER_ERROR => 'User Error',
|
||||
E_USER_WARNING => 'User Warning',
|
||||
E_USER_NOTICE => 'User Notice',
|
||||
E_STRICT => 'Strict',
|
||||
E_RECOVERABLE_ERROR => 'Recoverable Error',
|
||||
E_DEPRECATED => 'Deprecated',
|
||||
E_USER_DEPRECATED => 'User Deprecated',
|
||||
];
|
||||
|
||||
$errName = isset($errorNames[$errNo]) ? $errorNames[$errNo] : "";
|
||||
|
||||
throw new \PHPUnit\Framework\Exception(
|
||||
sprintf("%s: %s in %s:%s.", $errName, $errStr, $errFile, $errLine),
|
||||
$errNo
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
|
||||
colors="true"
|
||||
columns="max"
|
||||
beStrictAboutTestsThatDoNotTestAnything="false"
|
||||
bootstrap="../../bootstrap.php">
|
||||
<testsuites>
|
||||
<testsuite name="Magento Unit Tests for Static Code Analysis Framework">
|
||||
<directory>testsuite/Magento/TestFramework</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<php>
|
||||
<ini name="date.timezone" value="America/Los_Angeles"/>
|
||||
</php>
|
||||
<extensions>
|
||||
<extension class="Qameta\Allure\PHPUnit\AllureExtension">
|
||||
<!-- Optional arguments block; omit it if you want to use default values -->
|
||||
<arguments>
|
||||
<!-- Path to config file (default is config/allure.config.php) -->
|
||||
<string>../../../allure/allure.config.php</string>
|
||||
</arguments>
|
||||
</extension>
|
||||
</extensions>
|
||||
</phpunit>
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Magento\PhpStan\Formatters;
|
||||
|
||||
use PHPStan\Analyser\Error;
|
||||
use PHPStan\Command\AnalysisResult;
|
||||
use PHPStan\File\FuzzyRelativePathHelper;
|
||||
use PHPStan\ShouldNotHappenException;
|
||||
use PHPStan\Testing\ErrorFormatterTestCase;
|
||||
|
||||
/**
|
||||
* Tests filter error formatter.
|
||||
*/
|
||||
class FilteredErrorFormatterTest extends ErrorFormatterTestCase
|
||||
{
|
||||
protected const DIRECTORY_PATH = __DIR__ . '/Fixtures';
|
||||
|
||||
/**
|
||||
* Tests errors filtering.
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $exitCode
|
||||
* @param array $fileErrors
|
||||
* @param string $expected
|
||||
* @throws ShouldNotHappenException
|
||||
*
|
||||
* @dataProvider dataFormatterOutputProvider
|
||||
*/
|
||||
public function testFormatErrors(
|
||||
string $message,
|
||||
int $exitCode,
|
||||
array $fileErrors,
|
||||
string $expected
|
||||
): void {
|
||||
$formatter = new FilteredErrorFormatter(
|
||||
new FuzzyRelativePathHelper(self::DIRECTORY_PATH, [], '/'),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
$analysisResult = new AnalysisResult(
|
||||
$fileErrors,
|
||||
[],
|
||||
[],
|
||||
false,
|
||||
false,
|
||||
null
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
$exitCode,
|
||||
$formatter->formatErrors(
|
||||
$analysisResult,
|
||||
$this->getOutput()
|
||||
),
|
||||
sprintf('%s: response code do not match', $message)
|
||||
);
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$this->getOutputContent(),
|
||||
sprintf('%s: output do not match', $message)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function dataFormatterOutputProvider(): array
|
||||
{
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
$errorMessage = 'Method Magento\PhpStan\Formatters\Fixtures\ClassWithIgnoreAnnotation::testMethod() invoked with 2 parameters, 1 required.';
|
||||
// phpcs:enable Generic.Files.LineLength.TooLong
|
||||
|
||||
return [
|
||||
[
|
||||
'No errors',
|
||||
0,
|
||||
[],
|
||||
"\n [OK] No errors\n\n",
|
||||
],
|
||||
[
|
||||
'All errors are suppressed by ignore annotations',
|
||||
0,
|
||||
[
|
||||
new Error(
|
||||
'Method level error',
|
||||
self::DIRECTORY_PATH . '/ClassWithIgnoreAnnotation.php',
|
||||
22
|
||||
),
|
||||
new Error(
|
||||
$errorMessage,
|
||||
self::DIRECTORY_PATH . '/ClassWithIgnoreAnnotation.php',
|
||||
25
|
||||
),
|
||||
new Error(
|
||||
$errorMessage,
|
||||
self::DIRECTORY_PATH . '/ClassWithIgnoreAnnotation.php',
|
||||
28
|
||||
),
|
||||
new Error(
|
||||
$errorMessage,
|
||||
self::DIRECTORY_PATH . '/ClassWithIgnoreAnnotation.php',
|
||||
31
|
||||
),
|
||||
new Error(
|
||||
$errorMessage,
|
||||
self::DIRECTORY_PATH . '/ClassWithIgnoreAnnotation.php',
|
||||
33
|
||||
),
|
||||
],
|
||||
"\n [OK] No errors\n\n",
|
||||
],
|
||||
[
|
||||
'Errors aren\'t suppressed by ignore annotations',
|
||||
1,
|
||||
[
|
||||
new Error(
|
||||
$errorMessage,
|
||||
self::DIRECTORY_PATH . '/ClassWithoutIgnoreAnnotation.php',
|
||||
21
|
||||
),
|
||||
],
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
' ------ ---------------------------------------------------------------------------------------------------------------------------
|
||||
Line ClassWithoutIgnoreAnnotation.php
|
||||
------ ---------------------------------------------------------------------------------------------------------------------------
|
||||
21 Method Magento\PhpStan\Formatters\Fixtures\ClassWithIgnoreAnnotation::testMethod() invoked with 2 parameters, 1 required.
|
||||
------ ---------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
[ERROR] Found 1 error
|
||||
|
||||
',
|
||||
// phpcs:enable Generic.Files.LineLength.TooLong
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\PhpStan\Formatters\Fixtures;
|
||||
|
||||
/**
|
||||
* Class ClassWithIgnoreAnnotation
|
||||
*
|
||||
* phpcs:ignoreFile
|
||||
*/
|
||||
class ClassWithIgnoreAnnotation
|
||||
{
|
||||
/**
|
||||
* Test method.
|
||||
* phpstan:ignore "Method level error"
|
||||
*/
|
||||
public function getProductList()
|
||||
{
|
||||
// phpstan:ignore "Method Magento\PhpStan\Formatters\Fixtures\ClassWithIgnoreAnnotation::testMethod() invoked with 2 parameters, 1 required."
|
||||
$this->testMethod('test1', 'test2');
|
||||
|
||||
// phpstan:ignore "Method * invoked with 2 parameters, 1 required."
|
||||
$this->testMethod('test1', 'test2');
|
||||
|
||||
// phpstan:ignore
|
||||
$this->testMethod('test1', 'test2');
|
||||
|
||||
$this->testMethod('test1', 'test2'); // phpstan:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $arg1
|
||||
* @return string
|
||||
*/
|
||||
private function testMethod(string $arg1)
|
||||
{
|
||||
return $arg1;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\PhpStan\Formatters\Fixtures;
|
||||
|
||||
/**
|
||||
* Class ClassWithoutIgnoreAnnotation
|
||||
* phpcs:ignoreFile
|
||||
*/
|
||||
class ClassWithoutIgnoreAnnotation
|
||||
{
|
||||
/**
|
||||
* Test method.
|
||||
*/
|
||||
public function getProductList()
|
||||
{
|
||||
$this->testMethod('test1', 'test2');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $arg1
|
||||
* @return string
|
||||
*/
|
||||
private function testMethod(string $arg1)
|
||||
{
|
||||
return $arg1;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\PhpStan\Reflection\DataObject\Fixtures;
|
||||
|
||||
use Magento\Framework\DataObject;
|
||||
|
||||
class ClassWithCorrectUsageOfDataObject
|
||||
{
|
||||
/**
|
||||
* @var DataObject
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* ClassWithCorrectUsageOfDataObject constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->container = new DataObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process with amazing stuff.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function doStuff(): void
|
||||
{
|
||||
$a = $this->container->getSomething('1');
|
||||
|
||||
if ($this->container->hasSomething()) {
|
||||
$this->container->setSomething2($a);
|
||||
} else {
|
||||
$this->container->unsetSomething();
|
||||
$this->container->setSometing2($this->container->getStuff());
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\PhpStan\Reflection\DataObject\Fixtures;
|
||||
|
||||
use Magento\Framework\DataObject;
|
||||
|
||||
class ClassWithIncorrectUsageOfDataObject
|
||||
{
|
||||
/**
|
||||
* @var DataObject
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* ClassWithIncorrectUsageOfDataObject constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->container = new DataObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do Magic Stuff.
|
||||
*
|
||||
* 'get' - args: $index[optional] - string|int, return: mixed;
|
||||
* 'set' - args: $value - mixed, return: self;
|
||||
* 'uns' - args: -, return: self;
|
||||
* 'has' - args: -, return: bool;
|
||||
*/
|
||||
public function doStuff(): void
|
||||
{
|
||||
$this->container->getBaz(
|
||||
$this->container->unsFoo(
|
||||
$this->container->setBaz()
|
||||
)
|
||||
);
|
||||
$this->container->hasFoo(
|
||||
$this->container->setStuff()
|
||||
);
|
||||
|
||||
$this->container->getSomething($this->container->hasFoo());
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\PhpStan\Reflection\DataObject\Fixtures;
|
||||
|
||||
use Magento\Framework\Session\SessionManager;
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
|
||||
*/
|
||||
class ClassWithSessionManagerUsage
|
||||
{
|
||||
/**
|
||||
* @var SessionManager
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @param SessionManager $sessionManager
|
||||
*/
|
||||
public function __construct(SessionManager $sessionManager)
|
||||
{
|
||||
$this->container = $sessionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do Magic Stuff.
|
||||
*
|
||||
* 'get' - args: $index[optional] - string|int, return: mixed;
|
||||
* 'set' - args: $value - mixed, return: self;
|
||||
* 'uns' - args: -, return: self;
|
||||
* 'has' - args: -, return: bool;
|
||||
*/
|
||||
public function doStuff(): void
|
||||
{
|
||||
$this->container->getBaz(
|
||||
$this->container->unsFoo(
|
||||
$this->container->setBaz()
|
||||
)
|
||||
);
|
||||
$this->container->hasFoo(
|
||||
$this->container->setStuff()
|
||||
);
|
||||
|
||||
$this->container->getSomething($this->container->hasFoo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct usage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function doCorrectStuff(): void
|
||||
{
|
||||
$a = $this->container->getSomething('1');
|
||||
|
||||
if ($this->container->hasSomething()) {
|
||||
$this->container->setSomething2($a);
|
||||
} else {
|
||||
$this->container->unsetSomething();
|
||||
$this->container->setSometing2($this->container->getStuff());
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
services:
|
||||
-
|
||||
class: Magento\PhpStan\Reflection\Php\DataObjectClassReflectionExtension
|
||||
tags: {phpstan.broker.methodsClassReflectionExtension: {priority: 100}}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\PhpStan\Reflection\DataObject;
|
||||
|
||||
use PHPStan\Rules\Methods\CallMethodsRule;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Testing\RuleTestCase;
|
||||
|
||||
class MagicMethodsTest extends RuleTestCase
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function getRule(): Rule
|
||||
{
|
||||
return $this->getContainer()->getByType(CallMethodsRule::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return directory path of fixture directory.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getFixturesDir(): string
|
||||
{
|
||||
return __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add config files.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getAdditionalConfigFiles(): array
|
||||
{
|
||||
return [self::getFixturesDir() . 'config.neon'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Data Object Reflection extension.
|
||||
*/
|
||||
public function testExtension()
|
||||
{
|
||||
$this->analyse(
|
||||
[
|
||||
self::getFixturesDir() . 'ClassWithCorrectUsageOfDataObject.php',
|
||||
self::getFixturesDir() . 'ClassWithIncorrectUsageOfDataObject.php',
|
||||
self::getFixturesDir() . 'ClassWithSessionManagerUsage.php'
|
||||
],
|
||||
[
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
[
|
||||
'Parameter #1 $index of method Magento\Framework\DataObject::getBaz() expects int|string, Magento\Framework\DataObject given.',
|
||||
38
|
||||
],
|
||||
[
|
||||
'Method Magento\Framework\DataObject::unsFoo() invoked with 1 parameter, 0 required.',
|
||||
39
|
||||
],
|
||||
[
|
||||
'Method Magento\Framework\DataObject::setBaz() invoked with 0 parameters, 1 required.',
|
||||
40
|
||||
],
|
||||
[
|
||||
'Method Magento\Framework\DataObject::hasFoo() invoked with 1 parameter, 0 required.',
|
||||
43
|
||||
],
|
||||
[
|
||||
'Method Magento\Framework\DataObject::setStuff() invoked with 0 parameters, 1 required.',
|
||||
44
|
||||
],
|
||||
[
|
||||
'Parameter #1 $index of method Magento\Framework\DataObject::getSomething() expects int|string, bool given.',
|
||||
47
|
||||
],
|
||||
[
|
||||
'Parameter #1 $index of method Magento\Framework\Session\SessionManager::getBaz() expects int|string, Magento\Framework\Session\SessionManager given.',
|
||||
41
|
||||
],
|
||||
[
|
||||
'Method Magento\Framework\Session\SessionManager::unsFoo() invoked with 1 parameter, 0 required.',
|
||||
42
|
||||
],
|
||||
[
|
||||
'Method Magento\Framework\Session\SessionManager::setBaz() invoked with 0 parameters, 1 required.',
|
||||
43
|
||||
],
|
||||
[
|
||||
'Method Magento\Framework\Session\SessionManager::hasFoo() invoked with 1 parameter, 0 required.',
|
||||
46
|
||||
],
|
||||
[
|
||||
'Method Magento\Framework\Session\SessionManager::setStuff() invoked with 0 parameters, 1 required.',
|
||||
47
|
||||
],
|
||||
[
|
||||
'Parameter #1 $index of method Magento\Framework\Session\SessionManager::getSomething() expects int|string, bool given.',
|
||||
50
|
||||
]
|
||||
// phpcs:enable Generic.Files.LineLength.TooLong
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
class CodeMessDetectorTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testCanRun()
|
||||
{
|
||||
$messDetector = new \Magento\TestFramework\CodingStandard\Tool\CodeMessDetector(
|
||||
'some/ruleset/file.xml',
|
||||
'some/report/file.xml'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
class_exists(\PHPMD\TextUI\Command::class),
|
||||
$messDetector->canRun()
|
||||
);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\CodingStandard\Tool\CodeSniffer;
|
||||
|
||||
class WrapperTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testSetValues()
|
||||
{
|
||||
if (!class_exists('\PHP_CodeSniffer\Runner')) {
|
||||
$this->markTestSkipped('Code Sniffer is not installed');
|
||||
}
|
||||
$wrapper = new Wrapper();
|
||||
$expected = ['some_key' => 'some_value'];
|
||||
$wrapper->setSettings($expected);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\CodingStandard\Tool;
|
||||
|
||||
use PHP_CodeSniffer\Runner;
|
||||
|
||||
/**
|
||||
* Unit test to check CodeSniffer tool.
|
||||
*/
|
||||
class CodeSnifferTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var \Magento\TestFramework\CodingStandard\Tool\CodeSniffer
|
||||
*/
|
||||
protected $_tool;
|
||||
|
||||
/**
|
||||
* @var Runner
|
||||
*/
|
||||
protected $_wrapper;
|
||||
|
||||
/**
|
||||
* Rule set directory
|
||||
*/
|
||||
const RULE_SET = 'some/ruleset/directory';
|
||||
|
||||
/**
|
||||
* Report file path
|
||||
*/
|
||||
const REPORT_FILE = 'some/report/file.xml';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->_wrapper = $this->createMock(\Magento\TestFramework\CodingStandard\Tool\CodeSniffer\Wrapper::class);
|
||||
$this->_tool = new \Magento\TestFramework\CodingStandard\Tool\CodeSniffer(
|
||||
self::RULE_SET,
|
||||
self::REPORT_FILE,
|
||||
$this->_wrapper
|
||||
);
|
||||
}
|
||||
|
||||
public function testRun()
|
||||
{
|
||||
$whiteList = ['test' . rand(), 'test' . rand()];
|
||||
$extensions = ['test' . rand(), 'test' . rand()];
|
||||
|
||||
$expectedCliEmulation = [
|
||||
'files' => $whiteList,
|
||||
'standards' => [self::RULE_SET],
|
||||
'extensions' => $extensions,
|
||||
'reports' => ['full' => self::REPORT_FILE],
|
||||
'reportWidth' => 120
|
||||
];
|
||||
|
||||
$this->_tool->setExtensions($extensions);
|
||||
|
||||
$this->_wrapper->expects($this->once())
|
||||
->method('setSettings')
|
||||
->with($this->equalTo($expectedCliEmulation));
|
||||
|
||||
$this->_wrapper->expects($this->once())
|
||||
->method('runPHPCS');
|
||||
|
||||
$this->_tool->run($whiteList);
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
class DbRuleTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var DbRule
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->model = new DbRule(['some_table' => 'SomeModule']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $module
|
||||
* @param string $file
|
||||
* @param string $contents
|
||||
* @param array $expected
|
||||
* @dataProvider getDependencyInfoDataProvider
|
||||
*/
|
||||
public function testGetDependencyInfo($module, $file, $contents, array $expected)
|
||||
{
|
||||
$this->assertEquals($expected, $this->model->getDependencyInfo($module, 'php', $file, $contents));
|
||||
}
|
||||
|
||||
public function getDependencyInfoDataProvider()
|
||||
{
|
||||
return [
|
||||
['any', 'non-resource-file-path.php', 'any', []],
|
||||
[
|
||||
'any',
|
||||
'/app/some/path/Setup/some-file.php',
|
||||
'$install->getTableName("unknown_table")',
|
||||
[['modules' => ['Unknown'], 'source' => 'unknown_table']]
|
||||
],
|
||||
[
|
||||
'SomeModule',
|
||||
'/app/some/path/Resource/Setup.php',
|
||||
'$install->getTableName("some_table")',
|
||||
[]
|
||||
],
|
||||
[
|
||||
'any',
|
||||
'/app/some/path/Resource/Setup.php',
|
||||
'$install->getTableName("some_table")',
|
||||
[
|
||||
[
|
||||
'modules' => ['SomeModule'],
|
||||
'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD,
|
||||
'source' => 'some_table',
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
use Magento\TestFramework\Dependency\VirtualType\VirtualTypeMapper;
|
||||
|
||||
class DiRuleTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @param string $module
|
||||
* @param string $contents
|
||||
* @param array $expected
|
||||
* @dataProvider getDependencyInfoDataProvider
|
||||
*/
|
||||
public function testGetDependencyInfo($module, $contents, array $expected)
|
||||
{
|
||||
$diRule = new DiRule(new VirtualTypeMapper([
|
||||
'scope' => [
|
||||
'someVirtualType1' => 'Magento\AnotherModule\Some\Class1',
|
||||
'someVirtualType2' => 'Magento\AnotherModule\Some\Class2'
|
||||
]
|
||||
]));
|
||||
$file = '/some/path/scope/di.xml';
|
||||
static::assertEquals($expected, $diRule->getDependencyInfo($module, null, $file, $contents));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencyInfoDataProvider()
|
||||
{
|
||||
return [
|
||||
'Di without dependencies' => [
|
||||
'Magento\SomeModule',
|
||||
$this->getFileContent('di_no_dependency.xml'),
|
||||
[]
|
||||
],
|
||||
'Di only in module dependencies' => [
|
||||
'Magento\SomeModule',
|
||||
$this->getFileContent('di_in_module_dependency.xml'),
|
||||
[]
|
||||
],
|
||||
'Di external dependencies' => [
|
||||
'Magento\SomeModule',
|
||||
$this->getFileContent('di_external_dependency.xml'),
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\ExternalModule3'],
|
||||
'type' => RuleInterface::TYPE_SOFT,
|
||||
'source' => 'Magento\ExternalModule3\Some\Another\Class'
|
||||
],
|
||||
[
|
||||
'modules' => ['Magento\ExternalModule5'],
|
||||
'type' => RuleInterface::TYPE_SOFT,
|
||||
'source' => 'Magento\ExternalModule5\Some\Another\Class'
|
||||
],
|
||||
[
|
||||
'modules' => ['Magento\ExternalModule6'],
|
||||
'type' => RuleInterface::TYPE_SOFT,
|
||||
'source' => 'Magento\ExternalModule6\Some\Plugin\Class'
|
||||
],
|
||||
[
|
||||
'modules' => ['Magento\ExternalModule1'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'Magento\ExternalModule1\Some\Argument1'
|
||||
],
|
||||
[
|
||||
'modules' => ['Magento\ExternalModule2'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'Magento\ExternalModule2\Some\Argument2'
|
||||
],
|
||||
[
|
||||
'modules' => ['Magento\ExternalModule4'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'Magento\ExternalModule4\Some\Argument3'
|
||||
]
|
||||
]
|
||||
],
|
||||
'Di virtual dependencies' => [
|
||||
'Magento\SomeModule',
|
||||
$this->getFileContent('di_virtual_dependency.xml'),
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\AnotherModule'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'Magento\AnotherModule\Some\Class1',
|
||||
],
|
||||
[
|
||||
'modules' => ['Magento\AnotherModule'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'Magento\AnotherModule\Some\Class2',
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content of di file
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return string
|
||||
*/
|
||||
private function getFileContent($fileName)
|
||||
{
|
||||
return file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . $fileName);
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
class LayoutRuleTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testNonLayoutGetDependencyInfo()
|
||||
{
|
||||
$model = new LayoutRule([], [], []);
|
||||
$content = 'any content';
|
||||
$this->assertEmpty($model->getDependencyInfo('any', 'not layout', 'any', $content));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contents
|
||||
* @param array $expected
|
||||
* @dataProvider getDependencyInfoDataProvider
|
||||
*/
|
||||
public function testGetDependencyInfo($contents, array $expected)
|
||||
{
|
||||
$model = new LayoutRule([], [], []);
|
||||
$this->assertEquals($expected, $model->getDependencyInfo('Magento\SomeModule', 'layout', 'any', $contents));
|
||||
}
|
||||
|
||||
public function getDependencyInfoDataProvider()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'<element module="Magento\AnotherModule"/>',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\AnotherModule'],
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'source' => '<element module="Magento\AnotherModule"/>',
|
||||
]
|
||||
],
|
||||
],
|
||||
['<element module="Magento\SomeModule"/>', []],
|
||||
[
|
||||
'<block class="Magento\AnotherModule\Several\Chunks"/>',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\AnotherModule'],
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_HARD,
|
||||
'source' => '<block class="Magento\AnotherModule\Several\Chunks"/>',
|
||||
]
|
||||
]
|
||||
],
|
||||
['<block class="Magento\SomeModule\Several\Chunks"/>', []],
|
||||
[
|
||||
'<any>
|
||||
<extra></extra><block template="Magento_AnotherModule::template/path.phtml"/>
|
||||
</any>',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\AnotherModule'],
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'source' => '<block template="Magento_AnotherModule::template/path.phtml"/>',
|
||||
]
|
||||
]
|
||||
],
|
||||
['<block template="Magento_SomeModule::template/path.phtml"/>', []],
|
||||
[
|
||||
'<block>Magento\AnotherModule\Several\Chunks</block>',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\AnotherModule'],
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'source' => '<block>Magento\AnotherModule\Several\Chunks</block>',
|
||||
]
|
||||
]
|
||||
],
|
||||
['<block>Magento\SomeModule\Several\Chunks</block>', []],
|
||||
[
|
||||
'<template>Magento_AnotherModule::template/path.phtml</template>',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\AnotherModule'],
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'source' => '<template>Magento_AnotherModule::template/path.phtml</template>',
|
||||
]
|
||||
]
|
||||
],
|
||||
['<template>Magento_SomeModule::template/path.phtml</template>', []],
|
||||
[
|
||||
'<file>Magento_AnotherModule::file/path.txt</file>',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\AnotherModule'],
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'source' => '<file>Magento_AnotherModule::file/path.txt</file>',
|
||||
]
|
||||
]
|
||||
],
|
||||
['<file>Magento_SomeModule::file/path.txt</file>', []],
|
||||
[
|
||||
'<any helper="Magento\AnotherModule\Several\Chunks::text"/>',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\AnotherModule'],
|
||||
'type' => \Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
'source' => '<any helper="Magento\AnotherModule\Several\Chunks::text"/>',
|
||||
]
|
||||
]
|
||||
],
|
||||
['<any helper="Magento\SomeModule\Several\Chunks::text"/>', []]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contents
|
||||
* @param string $type
|
||||
* @dataProvider layoutGetDependencyInfoDataProvider
|
||||
*/
|
||||
public function testUpdatesRouterGetDependencyInfo($contents, $type)
|
||||
{
|
||||
$model = new LayoutRule(['router_name' => ['Magento\RouterModule']], [], []);
|
||||
$this->assertEquals([], $model->getDependencyInfo('Magento\RouterModule', 'layout', 'any', $contents));
|
||||
$this->assertEquals(
|
||||
[['modules' => ['Magento\RouterModule'], 'type' => $type, 'source' => 'router_name_action']],
|
||||
$model->getDependencyInfo('Magento\AnotherModule', 'layout', 'any', $contents)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contents
|
||||
* @param string $type
|
||||
* @param bool $isHandle
|
||||
* @dataProvider layoutGetDependencyInfoWithReferenceDataProvider
|
||||
*/
|
||||
public function testLayoutGetDependencyInfo($contents, $type, $isHandle)
|
||||
{
|
||||
// test one module
|
||||
$data = [
|
||||
'frontend' => ['any_handle_name' => ['Magento\AnyHandleModule' => 'Magento\AnyHandleModule']],
|
||||
'default' => ['singlechunk' => ['Magento\DefaultHandleModule' => 'Magento\DefaultHandleModule']],
|
||||
];
|
||||
$model = $isHandle ? new LayoutRule([], [], $data) : new LayoutRule([], $data, []);
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$model->getDependencyInfo('Magento\AnyHandleModule', 'layout', 'path/frontend/file.txt', $contents)
|
||||
);
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$model->getDependencyInfo('Magento\DefaultHandleModule', 'layout', 'any', $contents)
|
||||
);
|
||||
$this->assertEquals(
|
||||
[['modules' => ['Magento\DefaultHandleModule'], 'type' => $type, 'source' => 'singlechunk']],
|
||||
$model->getDependencyInfo('any', 'layout', 'any', $contents)
|
||||
);
|
||||
$this->assertEquals(
|
||||
[['modules' => ['Magento\AnyHandleModule'], 'type' => $type, 'source' => 'any_handle_name']],
|
||||
$model->getDependencyInfo('any', 'layout', 'path/frontend/file.txt', $contents)
|
||||
);
|
||||
// test several modules
|
||||
$data = [
|
||||
'frontend' => [
|
||||
'any_handle_name' => [
|
||||
'Magento\Theme' => 'Magento\Theme',
|
||||
'Magento\HandleModule' => 'Magento\HandleModule',
|
||||
],
|
||||
],
|
||||
];
|
||||
$model = $isHandle ? new LayoutRule([], [], $data) : new LayoutRule([], $data, []);
|
||||
$this->assertEquals(
|
||||
[['modules' => ['Magento\Theme'], 'type' => $type, 'source' => 'any_handle_name']],
|
||||
$model->getDependencyInfo('any', 'layout', 'path/frontend/file.txt', $contents)
|
||||
);
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$model->getDependencyInfo('Magento\HandleModule', 'layout', 'path/frontend/file.txt', $contents)
|
||||
);
|
||||
}
|
||||
|
||||
public function layoutGetDependencyInfoDataProvider()
|
||||
{
|
||||
return [
|
||||
[
|
||||
$this->_getLayoutFileContent('layout_handle.xml'),
|
||||
\Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
true,
|
||||
],
|
||||
[
|
||||
$this->_getLayoutFileContent('layout_handle_parent.xml'),
|
||||
\Magento\Test\Integrity\DependencyTest::TYPE_HARD,
|
||||
true
|
||||
],
|
||||
[
|
||||
$this->_getLayoutFileContent('layout_handle_update.xml'),
|
||||
\Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
true
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function layoutGetDependencyInfoWithReferenceDataProvider()
|
||||
{
|
||||
return array_merge(
|
||||
$this->layoutGetDependencyInfoDataProvider(),
|
||||
[
|
||||
[
|
||||
$this->_getLayoutFileContent('layout_reference.xml'),
|
||||
\Magento\Test\Integrity\DependencyTest::TYPE_SOFT,
|
||||
false,
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content of layout file
|
||||
*
|
||||
* @param string $fileName
|
||||
* @return string
|
||||
*/
|
||||
protected function _getLayoutFileContent($fileName)
|
||||
{
|
||||
return file_get_contents(str_replace('\\', '/', realpath(__DIR__)) . '/_files/' . $fileName);
|
||||
}
|
||||
}
|
||||
+545
@@ -0,0 +1,545 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
|
||||
namespace Magento\TestFramework\Dependency;
|
||||
|
||||
use Magento\Framework\Config\Reader\Filesystem;
|
||||
use Magento\Framework\Exception\LocalizedException;
|
||||
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
|
||||
use Magento\TestFramework\Dependency\Reader\ClassScanner;
|
||||
use Magento\TestFramework\Dependency\Route\RouteMapper;
|
||||
|
||||
/**
|
||||
* Test for PhpRule dependency check
|
||||
*/
|
||||
class PhpRuleTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var PhpRule
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* @var ObjectManagerHelper
|
||||
*/
|
||||
private $objectManagerHelper;
|
||||
|
||||
/**
|
||||
* @var ClassScanner
|
||||
*/
|
||||
private $classScanner;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject | Filesystem
|
||||
*/
|
||||
private $webApiConfigReader;
|
||||
|
||||
private $pluginMap;
|
||||
private $mapRoutes;
|
||||
private $mapLayoutBlocks;
|
||||
private $whitelist;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->mapRoutes = ['someModule' => ['Magento\SomeModule'], 'anotherModule' => ['Magento\OneModule']];
|
||||
$this->mapLayoutBlocks = ['area' => ['block.name' => ['Magento\SomeModule' => 'Magento\SomeModule']]];
|
||||
$this->pluginMap = [
|
||||
'Magento\Module1\Plugin1' => 'Magento\Module1\Subject',
|
||||
'Magento\Module1\Plugin2' => 'Magento\Module2\Subject',
|
||||
];
|
||||
$this->whitelist = [];
|
||||
|
||||
$this->objectManagerHelper = new ObjectManagerHelper($this);
|
||||
$this->classScanner = $this->createMock(ClassScanner::class);
|
||||
$this->webApiConfigReader = $this->makeWebApiConfigReaderMock();
|
||||
|
||||
$this->model = new PhpRule(
|
||||
$this->mapRoutes,
|
||||
$this->mapLayoutBlocks,
|
||||
$this->webApiConfigReader,
|
||||
$this->pluginMap,
|
||||
$this->whitelist,
|
||||
$this->classScanner
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testNonPhpGetDependencyInfo()
|
||||
{
|
||||
$content = 'any content';
|
||||
$this->assertEmpty($this->model->getDependencyInfo('any', 'not php', 'any', $content));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $content
|
||||
* @param int $expectedScans
|
||||
* @param array $expected
|
||||
*
|
||||
* @throws \Exception
|
||||
* @dataProvider getDependencyInfoDataProvider
|
||||
*/
|
||||
public function testGetDependencyInfo(string $class, string $content, int $expectedScans, array $expected): void
|
||||
{
|
||||
$file = $this->makeMockFilepath($class);
|
||||
$module = $this->getModuleFromClass($class);
|
||||
$this->classScanner->expects($this->exactly($expectedScans))
|
||||
->method('getClassName')
|
||||
->with($file)
|
||||
->willReturn($class);
|
||||
$this->assertEquals($expected, $this->model->getDependencyInfo($module, 'php', $file, $content));
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
public function getDependencyInfoDataProvider()
|
||||
{
|
||||
return [
|
||||
'Extend class in same module' => [
|
||||
'Magento\SomeModule\SomeClass',
|
||||
'something extends \Magento\SomeModule\Any\ClassName {',
|
||||
0,
|
||||
[]
|
||||
],
|
||||
'Extend class in different module' => [
|
||||
'Magento\AnotherModule\SomeClass',
|
||||
'something extends \Magento\SomeModule\Any\ClassName {',
|
||||
1,
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\SomeModule'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'Magento\SomeModule\Any\ClassName',
|
||||
]
|
||||
]
|
||||
],
|
||||
'getViewFileUrl in same module' => [
|
||||
'Magento\SomeModule\SomeClass',
|
||||
'$this->getViewFileUrl("Magento_SomeModule::js/order-by-sku-failure.js")',
|
||||
0,
|
||||
[]
|
||||
],
|
||||
'getViewFileUrl in different module' => [
|
||||
'Magento\AnotherModule\SomeClass',
|
||||
'$this->getViewFileUrl("Magento_SomeModule::js/order-by-sku-failure.js")',
|
||||
1,
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\SomeModule'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'Magento_SomeModule',
|
||||
]
|
||||
]
|
||||
],
|
||||
'Helper class from same module' => [
|
||||
'Magento\SomeModule\SomeClass',
|
||||
'$this->helper("Magento\SomeModule\Any\ClassName")',
|
||||
0,
|
||||
[]
|
||||
],
|
||||
'Helper class from another module' => [
|
||||
'Magento\AnotherModule\SomeClass',
|
||||
'$this->helper("Magento\SomeModule\Any\ClassName")',
|
||||
1,
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\SomeModule'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'Magento\SomeModule\Any\ClassName',
|
||||
]
|
||||
]
|
||||
],
|
||||
'getBlock from same module' => [
|
||||
'Magento\SomeModule\SomeClass',
|
||||
'$this->getLayout()->getBlock(\'block.name\');',
|
||||
0,
|
||||
[]
|
||||
],
|
||||
'getBlock from another module' => [
|
||||
'Magento\AnotherModule\SomeClass',
|
||||
'$this->getLayout()->getBlock(\'block.name\');',
|
||||
0,
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\SomeModule'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'getBlock(\'block.name\')',
|
||||
]
|
||||
]
|
||||
],
|
||||
'Plugin on class in same module' => [
|
||||
'Magento\Module1\Plugin1',
|
||||
', \Magento\Module1\Subject $variable',
|
||||
0,
|
||||
[]
|
||||
],
|
||||
'Plugin depends on arbitrary class in same module' => [
|
||||
'Magento\Module1\Plugin1',
|
||||
', \Magento\Module1\NotSubject $variable',
|
||||
0,
|
||||
[]
|
||||
],
|
||||
'Plugin on class in different module' => [
|
||||
'Magento\Module1\Plugin2',
|
||||
'Magento\Module2\Subject',
|
||||
1,
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\Module2'],
|
||||
'type' => RuleInterface::TYPE_SOFT,
|
||||
'source' => 'Magento\Module2\Subject',
|
||||
]
|
||||
],
|
||||
],
|
||||
'Plugin depends on arbitrary class in same module as subject' => [
|
||||
'Magento\Module1\Plugin2',
|
||||
'Magento\Module2\NotSubject',
|
||||
1,
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\Module2'],
|
||||
'type' => RuleInterface::TYPE_SOFT,
|
||||
'source' => 'Magento\Module2\NotSubject',
|
||||
]
|
||||
]
|
||||
],
|
||||
'Plugin depends on arbitrary class in arbitrary module' => [
|
||||
'Magento\Module1\Plugin2',
|
||||
'Magento\OtherModule\NotSubject',
|
||||
1,
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\OtherModule'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'Magento\OtherModule\NotSubject',
|
||||
]
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $content
|
||||
* @param array $expected
|
||||
* @throws \Exception
|
||||
* @dataProvider getDependencyInfoDataCaseGetUrlDataProvider
|
||||
*/
|
||||
public function testGetDependencyInfoCaseGetUrl(
|
||||
string $class,
|
||||
string $content,
|
||||
array $expected
|
||||
) {
|
||||
$file = $this->makeMockFilepath($class);
|
||||
$module = $this->getModuleFromClass($class);
|
||||
|
||||
$this->assertEquals($expected, $this->model->getDependencyInfo($module, 'php', $file, $content));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencyInfoDataCaseGetUrlDataProvider()
|
||||
{
|
||||
return [
|
||||
'getUrl from same module' => [
|
||||
'Magento\Cms\SomeClass',
|
||||
'$this->getUrl("cms/index/index")',
|
||||
[]
|
||||
],
|
||||
'getUrl from another module' => [
|
||||
'Magento\SomeModule\SomeClass',
|
||||
'$this->getUrl("cms/index/index")',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\Cms'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'getUrl("cms/index/index"',
|
||||
]
|
||||
]
|
||||
],
|
||||
'getUrl from API of same module with parameter' => [
|
||||
'Magento\Catalog\SomeClass',
|
||||
'$this->getUrl("rest/V1/products/3")',
|
||||
[]
|
||||
],
|
||||
'getUrl from API of same module without parameter' => [
|
||||
'Magento\Catalog\SomeClass',
|
||||
'$this->getUrl("rest/V1/products")',
|
||||
[]
|
||||
],
|
||||
'getUrl from API of different module with parameter' => [
|
||||
'Magento\Backend\SomeClass',
|
||||
'$this->getUrl("rest/V1/products/43/options")',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\Catalog'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'getUrl("rest/V1/products/43/options"'
|
||||
]
|
||||
],
|
||||
],
|
||||
'getUrl from routeid wildcard' => [
|
||||
'Magento\Catalog\Controller\ControllerName\SomeClass',
|
||||
'$this->getUrl("*/Invalid/*")',
|
||||
[]
|
||||
],
|
||||
'getUrl from wildcard url within ignored Block class' => [
|
||||
'Magento\Cms\Block\SomeClass',
|
||||
'$this->getUrl("Catalog/*/View")',
|
||||
[]
|
||||
],
|
||||
'getUrl from wildcard url within ignored Model file' => [
|
||||
'Magento\Cms\Model\SomeClass',
|
||||
'$this->getUrl("Catalog/*/View")',
|
||||
[]
|
||||
],
|
||||
'getUrl with in admin controller for controllerName wildcard' => [
|
||||
'Magento\Backend\Controller\Adminhtml\System\Store\DeleteStore',
|
||||
'$this->getUrl("adminhtml/*/deleteStorePost")',
|
||||
[]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @param string $content
|
||||
* @param array $expected
|
||||
* @throws \Exception
|
||||
* @dataProvider getDependencyInfoDataCaseGetTemplateUrlDataProvider
|
||||
*/
|
||||
public function testGetDependencyInfoCaseTemplateGetUrl(
|
||||
string $template,
|
||||
string $content,
|
||||
array $expected
|
||||
) {
|
||||
$module = $this->getModuleFromClass($template);
|
||||
|
||||
$this->assertEquals($expected, $this->model->getDependencyInfo($module, 'php', $template, $content));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function getDependencyInfoDataCaseGetTemplateUrlDataProvider()
|
||||
{
|
||||
return [ 'getUrl from ignore template' => [
|
||||
'app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar/script.phtml',
|
||||
'$getUrl("adminhtml/*/ajaxBlock")',
|
||||
[]]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $content
|
||||
* @param array $expected
|
||||
* @dataProvider processWildcardUrlDataProvider
|
||||
*/
|
||||
public function testProcessWildcardUrl(
|
||||
string $class,
|
||||
string $content,
|
||||
array $expected
|
||||
) {
|
||||
$routeMapper = $this->createMock(RouteMapper::class);
|
||||
$routeMapper->expects($this->once())
|
||||
->method('getDependencyByRoutePath')
|
||||
->with(
|
||||
$this->equalTo($expected['route_id']),
|
||||
$this->equalTo($expected['controller_name']),
|
||||
$this->equalTo($expected['action_name'])
|
||||
);
|
||||
$phpRule = new PhpRule(
|
||||
$this->mapRoutes,
|
||||
$this->mapLayoutBlocks,
|
||||
$this->webApiConfigReader,
|
||||
$this->pluginMap,
|
||||
$this->whitelist,
|
||||
$this->classScanner,
|
||||
$routeMapper
|
||||
);
|
||||
$file = $this->makeMockFilepath($class);
|
||||
$module = $this->getModuleFromClass($class);
|
||||
|
||||
$phpRule->getDependencyInfo($module, 'php', $file, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function processWildcardUrlDataProvider()
|
||||
{
|
||||
return [
|
||||
'wildcard controller route' => [
|
||||
'Magento\SomeModule\Controller\ControllerName\SomeClass',
|
||||
'$this->getUrl("cms/*/index")',
|
||||
[
|
||||
'route_id' => 'cms',
|
||||
'controller_name' => 'controllername',
|
||||
'action_name' => 'index'
|
||||
]
|
||||
],
|
||||
'adminhtml wildcard controller route' => [
|
||||
'Magento\Backend\Controller\Adminhtml\System\Store\DeleteStore',
|
||||
'$this->getUrl("adminhtml/*/deleteStorePost")',
|
||||
[
|
||||
'route_id' => 'adminhtml',
|
||||
'controller_name' => 'system_store',
|
||||
'action_name' => 'deletestorepost'
|
||||
]
|
||||
],
|
||||
'index wildcard' => [
|
||||
'Magento\Backend\Controller\System\Store\DeleteStore',
|
||||
'$this->getUrl("routeid/controllername/*")',
|
||||
[
|
||||
'route_id' => 'routeid',
|
||||
'controller_name' => 'controllername',
|
||||
'action_name' => 'deletestore'
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $content
|
||||
* @param \Exception $expected
|
||||
* @throws \Exception
|
||||
* @dataProvider getDependencyInfoDataCaseGetUrlExceptionDataProvider
|
||||
*/
|
||||
public function testGetDependencyInfoCaseGetUrlException(
|
||||
string $class,
|
||||
string $content,
|
||||
\Exception $expected
|
||||
) {
|
||||
$file = $this->makeMockFilepath($class);
|
||||
$module = $this->getModuleFromClass($class);
|
||||
$this->expectExceptionObject($expected);
|
||||
|
||||
$this->model->getDependencyInfo($module, 'php', $file, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencyInfoDataCaseGetUrlExceptionDataProvider()
|
||||
{
|
||||
return [
|
||||
'getUrl from same module' => [
|
||||
'Magento\SomeModule\SomeClass',
|
||||
'$this->getUrl("someModule")',
|
||||
new LocalizedException(__('Invalid URL path: %1', 'somemodule/index/index')),
|
||||
],
|
||||
'getUrl from unknown wildcard path' => [
|
||||
'Magento\Catalog\Controller\Product\View',
|
||||
'$this->getUrl("Catalog/*/INVALID")',
|
||||
new LocalizedException(__('Invalid URL path: %1', 'catalog/product/invalid')),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $module
|
||||
* @param string $content
|
||||
* @param array $expected
|
||||
* @dataProvider getDefaultModelDependencyDataProvider
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetDefaultModelDependency($module, $content, array $expected)
|
||||
{
|
||||
$mapLayoutBlocks = [
|
||||
'default' => [
|
||||
'block.name' => [
|
||||
'Magento\SomeModule' => 'Magento\SomeModule',
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->model = new PhpRule([], $mapLayoutBlocks, $this->webApiConfigReader);
|
||||
$this->assertEquals($expected, $this->model->getDependencyInfo($module, 'template', 'any', $content));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultModelDependencyDataProvider()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'Magento\AnotherModule',
|
||||
'$this->getLayout()->getBlock(\'block.name\');',
|
||||
[
|
||||
[
|
||||
'modules' => ['Magento\SomeModule'],
|
||||
'type' => RuleInterface::TYPE_HARD,
|
||||
'source' => 'getBlock(\'block.name\')',
|
||||
]
|
||||
],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make some fake filepath to correspond to the class name
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
private function makeMockFilepath($class)
|
||||
{
|
||||
return 'ClassRoot' . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the module name like Magento\Module out of a classname, assuming for test purpose that
|
||||
* all modules are from "Magento" vendor
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
private function getModuleFromClass(string $class): string
|
||||
{
|
||||
$moduleNameLength = strpos($class, '\\', strpos($class, '\\') + 1);
|
||||
return substr($class, 0, $moduleNameLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an example list of services that would be parsed via the configReader
|
||||
*
|
||||
* @return \PHPUnit\Framework\MockObject\MockObject | Filesystem
|
||||
*/
|
||||
private function makeWebApiConfigReaderMock()
|
||||
{
|
||||
$services = [ 'routes' => [
|
||||
'/V1/products/:sku' => [
|
||||
'GET' => ['service' => [
|
||||
'class' => 'Magento\Catalog\Api\ProductRepositoryInterface',
|
||||
'method' => 'get'
|
||||
] ],
|
||||
'PUT' => ['service' => [
|
||||
'class' => 'Magento\Catalog\Api\ProductRepositoryInterface',
|
||||
'method' => 'save'
|
||||
] ],
|
||||
],
|
||||
'/V1/products/:sku/options' => ['GET' => ['service' => [
|
||||
'class' => 'Magento\Catalog\Api\ProductCustomOptionRepositoryInterface',
|
||||
'method' => 'getList'
|
||||
] ] ],
|
||||
'/V1/products' => ['GET' => ['service' => [
|
||||
'class' => 'Magento\Catalog\Api\ProductCustomOptionRepositoryInterface',
|
||||
'method' => 'getList'
|
||||
] ] ]
|
||||
] ];
|
||||
|
||||
return $this->createConfiguredMock(Filesystem::class, [ 'read' => $services ]);
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Dependency\VirtualType;
|
||||
|
||||
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
|
||||
|
||||
class VirtualTypeMapperTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var VirtualTypeMapper
|
||||
*/
|
||||
private $mapper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$managerHelper = new ObjectManager($this);
|
||||
$this->mapper = $managerHelper->getObject(VirtualTypeMapper::class, [
|
||||
'map' => [
|
||||
'global' => [
|
||||
'virtualType1' => 'Magento\SomeModule\Some\Class1',
|
||||
'virtualType2' => 'Magento\SomeModule\Some\Class2',
|
||||
'virtualType3' => 'Magento\SomeModule\Some\Class3',
|
||||
],
|
||||
'adminhtml' => [
|
||||
'virtualType1' => 'Magento\SomeModule\Some\Class4',
|
||||
'virtualType4' => 'Magento\SomeModule\Some\Class5',
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function testGetScopeFromFile()
|
||||
{
|
||||
$file = '/path/to/file/scope/filename.ext';
|
||||
static::assertEquals('scope', $this->mapper->getScopeFromFile($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $scope
|
||||
* @param string $type
|
||||
* @param string $expected
|
||||
* @dataProvider getTypeDataProvider
|
||||
*/
|
||||
public function testGetType($scope, $type, $expected)
|
||||
{
|
||||
static::assertEquals($expected, $this->mapper->getType($type, $scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTypeDataProvider()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'global',
|
||||
'virtualType1',
|
||||
'Magento\SomeModule\Some\Class1'
|
||||
],
|
||||
[
|
||||
'global',
|
||||
'virtualType2',
|
||||
'Magento\SomeModule\Some\Class2'
|
||||
],
|
||||
[
|
||||
'adminhtml',
|
||||
'virtualType3',
|
||||
'Magento\SomeModule\Some\Class3'
|
||||
],
|
||||
[
|
||||
'adminhtml',
|
||||
'virtualType1',
|
||||
'Magento\SomeModule\Some\Class4'
|
||||
],
|
||||
[
|
||||
'adminhtml',
|
||||
'virtualType4',
|
||||
'Magento\SomeModule\Some\Class5'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $diFilesPath
|
||||
* @param array $expectedVirtualTypesDependencies
|
||||
* @dataProvider loadConfigurationDataProvider
|
||||
*/
|
||||
public function testLoad(array $diFilesPath, array $expectedVirtualTypesDependencies)
|
||||
{
|
||||
$mapper = new VirtualTypeMapper();
|
||||
self::assertArrayEqualsRecursive(
|
||||
$expectedVirtualTypesDependencies,
|
||||
$mapper->loadMap($diFilesPath)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $diFilesPath
|
||||
* @param array $expectedVirtualTypesDependencies
|
||||
* @dataProvider loadConfigurationDataProvider
|
||||
*/
|
||||
public function testGetTypeComplex(array $diFilesPath, array $expectedVirtualTypesDependencies)
|
||||
{
|
||||
$mapper = new VirtualTypeMapper();
|
||||
$mapper->loadMap($diFilesPath);
|
||||
|
||||
// getType will return input value in case there no virtualType with the same name was found
|
||||
$expectedVirtualTypesDependencies['global']['ConcreteClass'] = 'ConcreteClass';
|
||||
|
||||
foreach ($expectedVirtualTypesDependencies as $scope => $deps) {
|
||||
foreach ($deps as $virtualType => $baseType) {
|
||||
self::assertEquals($baseType, $mapper->getType($virtualType, $scope));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function loadConfigurationDataProvider()
|
||||
{
|
||||
return [
|
||||
// collects two virtual types, defined in module configuration on global area level
|
||||
[
|
||||
'diFilesPath' => [$this->getFilePath('etc/di.xml')],
|
||||
'expectedVirtualTypesDependencies' => [
|
||||
'global' => [
|
||||
'Magento\Internal\Some\Class' => 'ExternalVirtualType',
|
||||
'MyVirtualType' => 'Magento\Internal\Some\Class'
|
||||
]
|
||||
]
|
||||
],
|
||||
|
||||
/**
|
||||
* expectation is the same for global area,
|
||||
* but extended for adminhtml as it contains own virtual types definitions
|
||||
*/
|
||||
[
|
||||
'diFilesPath' => [$this->getFilePath('etc/di.xml'), $this->getFilePath('etc/adminhtml/di.xml')],
|
||||
'expectedVirtualTypesDependencies' => [
|
||||
'global' => [
|
||||
'Magento\Internal\Some\Class' => 'ExternalVirtualType',
|
||||
'MyVirtualType' => 'Magento\Internal\Some\Class'
|
||||
],
|
||||
'adminhtml' => [
|
||||
'MyVirtualType2' => 'ExternalVirtualType'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $expectation
|
||||
* @param array $input
|
||||
*/
|
||||
private static function assertArrayEqualsRecursive(
|
||||
array $expectation,
|
||||
array $input
|
||||
) {
|
||||
static::assertEquals(count($expectation), count($input));
|
||||
foreach ($expectation as $ek => $ev) {
|
||||
self::assertArrayHasKey($ek, $input);
|
||||
if (is_array($ev)) {
|
||||
self::assertArrayEqualsRecursive($ev, $input[$ek]);
|
||||
} else {
|
||||
self::assertEquals($ev, $input[$ek]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileName
|
||||
* @return string
|
||||
*/
|
||||
private function getFilePath($fileName)
|
||||
{
|
||||
return __DIR__
|
||||
. DIRECTORY_SEPARATOR
|
||||
. '_files' . DIRECTORY_SEPARATOR . $fileName;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
|
||||
<type name="ExternalVirtualType">
|
||||
<arguments>
|
||||
<argument name="argument2" xsi:type="object">Magento\External\Virtual</argument>
|
||||
</arguments>
|
||||
</type>
|
||||
<type name="Magento\Internal\Some\Class">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="object">Magento\ExternalModule4\Some\Argument4</argument>
|
||||
</arguments>
|
||||
</type>
|
||||
<type name="MyVirtualType">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="object">Magento\External2\Class2</argument>
|
||||
</arguments>
|
||||
</type>
|
||||
<virtualType name="MyVirtualType2" type="ExternalVirtualType">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="object">Magento\External2\Class2</argument>
|
||||
</arguments>
|
||||
</virtualType>
|
||||
</config>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
|
||||
<virtualType name="Magento\Internal\Some\Class" type="ExternalVirtualType">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="object">Magento\External\Class</argument>
|
||||
<argument name="argument2" xsi:type="object">Magento\External\Virtual</argument>
|
||||
</arguments>
|
||||
</virtualType>
|
||||
<type name="Magento\External\Class">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="object">Magento\ExternalModule4\Some\Argument3</argument>
|
||||
<argument name="argument2" xsi:type="object">ExternalVirtualType</argument>
|
||||
</arguments>
|
||||
</type>
|
||||
<virtualType name="MyVirtualType" type="Magento\Internal\Some\Class">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="object">Magento\External2\Class</argument>
|
||||
<argument name="argument2" xsi:type="object">Magento\External2\Virtual</argument>
|
||||
</arguments>
|
||||
</virtualType>
|
||||
</config>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
|
||||
<type name="Magento\SomeModule\Some\Class">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="object">Magento\ExternalModule1\Some\Argument1</argument>
|
||||
<argument name="argument2" xsi:type="object">Magento\ExternalModule2\Some\Argument2</argument>
|
||||
</arguments>
|
||||
</type>
|
||||
<type name="Magento\ExternalModule3\Some\Another\Class">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="object">Magento\ExternalModule4\Some\Argument3</argument>
|
||||
</arguments>
|
||||
</type>
|
||||
<type name="Magento\ExternalModule5\Some\Another\Class">
|
||||
<plugin name="plugin" type="Magento\ExternalModule6\Some\Plugin\Class" />
|
||||
</type>
|
||||
</config>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
|
||||
<type name="Magento\SomeModule\Some\Class">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="string">Magento\SomeModule\Some\Argument1</argument>
|
||||
<argument name="argument2" xsi:type="string">Magento\SomeModule\Some\Argument2</argument>
|
||||
</arguments>
|
||||
</type>
|
||||
<type name="Magento\SomeModule\Some\Another\Class">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="string">Magento\SomeModule\Some\Argument3</argument>
|
||||
</arguments>
|
||||
</type>
|
||||
</config>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
|
||||
</config>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
|
||||
<type name="Magento\SomeModule\Some\Class">
|
||||
<arguments>
|
||||
<argument name="argument1" xsi:type="object">someVirtualType1</argument>
|
||||
<argument name="argument2" xsi:type="object">someVirtualType2</argument>
|
||||
<argument name="argument3" xsi:type="object">someNotExistsType</argument>
|
||||
</arguments>
|
||||
</type>
|
||||
</config>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_layout.xsd">
|
||||
<any_handle_name />
|
||||
<singlechunk />
|
||||
<router_name_action />
|
||||
</layout>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_layout.xsd">
|
||||
<any_node1 parent="any_handle_name" />
|
||||
<any_node2 parent="singlechunk" />
|
||||
<any_node3 parent="router_name_action" />
|
||||
</layout>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_layout.xsd">
|
||||
<update handle="any_handle_name" />
|
||||
<any_node>
|
||||
<update handle="singlechunk">
|
||||
<update handle="router_name_action" />
|
||||
</update>
|
||||
</any_node>
|
||||
</layout>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_layout.xsd">
|
||||
<reference name="any_handle_name" />
|
||||
<any_node>
|
||||
<reference name="singlechunk" />
|
||||
</any_node>
|
||||
</layout>
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Inspection;
|
||||
|
||||
use Magento\Framework\Component\ComponentRegistrar;
|
||||
|
||||
class WordsFinderTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @param string $configFile
|
||||
* @param string $baseDir
|
||||
* @dataProvider constructorExceptionDataProvider
|
||||
*/
|
||||
public function testConstructorException($configFile, $baseDir)
|
||||
{
|
||||
$this->expectException(\Magento\TestFramework\Inspection\Exception::class);
|
||||
|
||||
new \Magento\TestFramework\Inspection\WordsFinder($configFile, $baseDir, new ComponentRegistrar());
|
||||
}
|
||||
|
||||
public function constructorExceptionDataProvider()
|
||||
{
|
||||
$fixturePath = __DIR__ . '/_files/';
|
||||
return [
|
||||
'non-existing config file' => [$fixturePath . 'non-existing.xml', $fixturePath],
|
||||
'non-existing base dir' => [$fixturePath . 'config.xml', $fixturePath . 'non-existing-dir'],
|
||||
'broken config' => [$fixturePath . 'broken_config.xml', $fixturePath],
|
||||
'empty words config' => [$fixturePath . 'empty_words_config.xml', $fixturePath],
|
||||
'empty whitelisted path' => [$fixturePath . 'empty_whitelisted_path.xml', $fixturePath]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $configFiles
|
||||
* @param string $file
|
||||
* @param array $expected
|
||||
* @dataProvider findWordsDataProvider
|
||||
*/
|
||||
public function testFindWords($configFiles, $file, $expected)
|
||||
{
|
||||
$wordsFinder = new \Magento\TestFramework\Inspection\WordsFinder(
|
||||
$configFiles,
|
||||
__DIR__ . '/_files/words_finder',
|
||||
new ComponentRegistrar()
|
||||
);
|
||||
$actual = $wordsFinder->findWords($file);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function findWordsDataProvider()
|
||||
{
|
||||
$mainConfig = __DIR__ . '/_files/config.xml';
|
||||
$additionalConfig = __DIR__ . '/_files/config_additional.xml';
|
||||
$basePath = __DIR__ . '/_files/words_finder/';
|
||||
return [
|
||||
'usual file' => [$mainConfig, $basePath . 'buffy.php', ['demon', 'vampire']],
|
||||
'whitelisted file' => [$mainConfig, $basePath . 'twilight/eclipse.php', []],
|
||||
'partially whitelisted file' => [$mainConfig, $basePath . 'twilight/newmoon.php', ['demon']],
|
||||
'filename with bad word' => [
|
||||
$mainConfig,
|
||||
$basePath . 'interview_with_the_vampire.php',
|
||||
['vampire'],
|
||||
],
|
||||
'binary file, having name with bad word' => [
|
||||
$mainConfig,
|
||||
$basePath . 'interview_with_the_vampire.zip',
|
||||
['vampire'],
|
||||
],
|
||||
'words in multiple configs' => [
|
||||
[$mainConfig, $additionalConfig],
|
||||
$basePath . 'buffy.php',
|
||||
['demon', 'vampire', 'darkness'],
|
||||
],
|
||||
'whitelisted paths in multiple configs' => [
|
||||
[$mainConfig, $additionalConfig],
|
||||
$basePath . 'twilight/newmoon.php',
|
||||
['demon'],
|
||||
],
|
||||
'config must be whitelisted automatically' => [
|
||||
$basePath . 'self_tested_config.xml',
|
||||
$basePath . 'self_tested_config.xml',
|
||||
[],
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config>
|
||||
<words>
|
||||
<word>alien
|
||||
</words>
|
||||
</config>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config>
|
||||
<words>
|
||||
<word>demon</word>
|
||||
<word>vampire</word>
|
||||
</words>
|
||||
<whitelist>
|
||||
<item>
|
||||
<path>twilight/eclipse.php</path>
|
||||
</item>
|
||||
<item>
|
||||
<word>vampire</word>
|
||||
<path>twilight/newmoon.php</path>
|
||||
</item>
|
||||
</whitelist>
|
||||
</config>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config>
|
||||
<words>
|
||||
<word>darkness</word>
|
||||
<word>paranormal</word>
|
||||
</words>
|
||||
<whitelist>
|
||||
<item>
|
||||
<word>paranormal</word>
|
||||
<path>twilight/newmoon.php</path>
|
||||
</item>
|
||||
</whitelist>
|
||||
</config>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config>
|
||||
<words>
|
||||
<word>any</word>
|
||||
</words>
|
||||
<whitelist>
|
||||
<item></item>
|
||||
</whitelist>
|
||||
</config>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config></config>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
?>
|
||||
<?= 'At the young age of 15, Buffy Summers was chosen to hunt vampires, demons, and the forces of darkness.';
|
||||
+1
@@ -0,0 +1 @@
|
||||
At the young age of 15, Buffy Summers was chosen to hunt vampires, demons, and the forces of darkness.
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
?>
|
||||
<?= 'A man tells his epic life story: love, betrayal, loneliness, and hunger.';
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
-->
|
||||
<config>
|
||||
<words>
|
||||
<word>test</word>
|
||||
</words>
|
||||
</config>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
?>
|
||||
<?= 'As a string of mysterious killings grips Seattle, Bella, whose high school graduation is fast approaching,
|
||||
is forced to choose between her love for vampire Edward and her friendship with demon Jacob.';
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
?>
|
||||
<?= 'Vampire Edward leaves Bella after an attack that nearly claimed her life, and in her depression she falls into
|
||||
yet another paranormal relationship - this time with demon Jacob Black.';
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magento\TestFramework\Integrity\Library;
|
||||
|
||||
use Laminas\Code\Reflection\ClassReflection;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionException;
|
||||
|
||||
require_once __DIR__ . '/_files/DummyInjectableClass.php';
|
||||
|
||||
/**
|
||||
* Test for Magento\TestFramework\Integrity\Library\Injectable
|
||||
*/
|
||||
class InjectableTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Covered getDependencies
|
||||
*
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function testGetDependencies(): void
|
||||
{
|
||||
$injectable = new Injectable();
|
||||
$classReflection = new ClassReflection(DummyInjectableClass::class);
|
||||
|
||||
$actualResult = $injectable->getDependencies($classReflection);
|
||||
$expectedResult = [
|
||||
'Magento\Framework\DataObject',
|
||||
'TestNamespace\Some\SomeTestClass',
|
||||
'TestNamespace\Other\Test',
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedResult, $actualResult);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library\PhpParser;
|
||||
|
||||
/**
|
||||
*/
|
||||
class ParserFactoryTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var \Magento\TestFramework\Integrity\Library\PhpParser\Tokens
|
||||
*/
|
||||
protected $tokens;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->tokens = $this->getMockBuilder(
|
||||
\Magento\TestFramework\Integrity\Library\PhpParser\Tokens::class
|
||||
)->disableOriginalConstructor()->getMock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Covered createParsers method
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function testCreateParsers()
|
||||
{
|
||||
$parseFactory = new ParserFactory();
|
||||
$parseFactory->createParsers($this->tokens);
|
||||
$this->assertInstanceOf(
|
||||
\Magento\TestFramework\Integrity\Library\PhpParser\Uses::class,
|
||||
$parseFactory->getUses()
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
\Magento\TestFramework\Integrity\Library\PhpParser\StaticCalls::class,
|
||||
$parseFactory->getStaticCalls()
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
\Magento\TestFramework\Integrity\Library\PhpParser\Throws::class,
|
||||
$parseFactory->getThrows()
|
||||
);
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright © Magento, Inc. All rights reserved.
|
||||
* See COPYING.txt for license details.
|
||||
*/
|
||||
namespace Magento\TestFramework\Integrity\Library\PhpParser;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Check static calls parsing
|
||||
*/
|
||||
class StaticCallsTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var StaticCalls
|
||||
*/
|
||||
protected $staticCalls;
|
||||
|
||||
/**
|
||||
* @var \Magento\TestFramework\Integrity\Library\PhpParser\Tokens|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $tokens;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->tokens = $this->getMockBuilder(
|
||||
\Magento\TestFramework\Integrity\Library\PhpParser\Tokens::class
|
||||
)->disableOriginalConstructor()->getMock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get static call dependencies
|
||||
*
|
||||
* @dataProvider tokensDataProvider
|
||||
*/
|
||||
public function testGetDependencies(array $tokens)
|
||||
{
|
||||
$this->tokens->method('getPreviousToken')
|
||||
->willReturnCallback(
|
||||
function ($k) use ($tokens) {
|
||||
return $tokens[$k - 1];
|
||||
}
|
||||
);
|
||||
|
||||
$this->tokens->method('getTokenCodeByKey')
|
||||
->willReturnCallback(
|
||||
function ($k) use ($tokens) {
|
||||
return $tokens[$k][0];
|
||||
}
|
||||
);
|
||||
|
||||
$this->tokens->method('getTokenValueByKey')
|
||||
->willReturnCallback(
|
||||
function ($k) use ($tokens) {
|
||||
return $tokens[$k][1];
|
||||
}
|
||||
);
|
||||
|
||||
$staticCalls = new StaticCalls($this->tokens);
|
||||
foreach ($tokens as $k => $token) {
|
||||
$staticCalls->parse($token, $k);
|
||||
}
|
||||
|
||||
$uses = $this->getMockBuilder(
|
||||
\Magento\TestFramework\Integrity\Library\PhpParser\Uses::class
|
||||
)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$this->assertEquals(['\Object'], $staticCalls->getDependencies($uses));
|
||||
}
|
||||
|
||||
/**
|
||||
* different tokens data for php 7 and php 8
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function tokensDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'PHP 7' => [
|
||||
[
|
||||
0 => [T_WHITESPACE, ' '],
|
||||
1 => [T_NS_SEPARATOR, '\\'],
|
||||
2 => [T_STRING, 'Object'],
|
||||
3 => [T_PAAMAYIM_NEKUDOTAYIM, '::'],
|
||||
]
|
||||
],
|
||||
'PHP 8' => [
|
||||
[
|
||||
0 => [T_WHITESPACE, ' '],
|
||||
1 => [T_NAME_FULLY_QUALIFIED, '\\Object'],
|
||||
2 => [T_PAAMAYIM_NEKUDOTAYIM, '::'],
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user