diff --git a/Magento2/Sniffs/Commenting/FunctionPHPDocBlockParser.php b/Magento2/Sniffs/Commenting/FunctionPHPDocBlockParser.php
new file mode 100644
index 00000000..c23975e9
--- /dev/null
+++ b/Magento2/Sniffs/Commenting/FunctionPHPDocBlockParser.php
@@ -0,0 +1,147 @@
+ $is_empty,
+ 'description' => $description,
+ 'tags' => [],
+ 'return' => '',
+ 'parameters' => [],
+ 'throws' => [],
+ ];
+
+ $lastDocLine = 0;
+ $docType = false;
+ for ($i = $docStart; $i <= $docEnd; $i++) {
+ $token = $tokens[$i];
+ $code = $token['code'];
+ $line = $token['line'];
+ $content = $token['content'];
+
+
+ if ($code === T_DOC_COMMENT_TAG) {
+ // add php tokens also without comment to tag list
+ if (preg_match('/@[a-z]++/', $content, $output_array)) {
+ $functionDeclarations['tags'][] = $content;
+ }
+
+ $lastDocLine = $line;
+ $docType = $token['content'];
+ continue;
+ }
+
+ if ($lastDocLine !== $line) {
+ $lastDocLine = 0;
+ continue;
+ }
+
+ if ($content === ' ' || $content === "\n") {
+ continue;
+ }
+
+ preg_match_all('/[A-Za-z0-9$]*/', $content, $docTokens);
+ $docTokens = array_values(array_filter($docTokens[0]));
+
+ if (count($docTokens) === 0) {
+ // ignore empty parameter declaration
+ continue;
+ }
+
+ switch ($docType) {
+ case '@param':
+ $functionDeclarations = $this->addParamTagValue($docTokens, $functionDeclarations);
+ break;
+
+ case '@return':
+ $functionDeclarations = $this->addReturnTagValue($docTokens, $functionDeclarations);
+ break;
+
+ case '@throws':
+ $functionDeclarations = $this->addThrowsTagValue($docTokens, $functionDeclarations);
+ break;
+ }
+ }
+
+ return $functionDeclarations;
+ }
+
+ /**
+ * @param array $tokens
+ * @param array $functionDeclarations
+ * @return array
+ */
+ private function addParamTagValue(array $tokens, array $functionDeclarations)
+ {
+ $type = false;
+ $content = false;
+
+ foreach ($tokens as $token) {
+ if (strpos($token, '$') !== false) {
+ $content = $token;
+ continue;
+ }
+ $type = $token;
+ }
+
+ $functionDeclarations['parameters'][] = ['content' => $content, 'type' => $type,];
+ $functionDeclarations['is_empty'] = false;
+ return $functionDeclarations;
+ }
+
+ /**
+ * @param array $docTokens
+ * @param array $functionDeclarations
+ * @return array
+ */
+ private function addReturnTagValue(array $tokens, array $functionDeclarations)
+ {
+ $functionDeclarations['return'] = $tokens[0];
+ $functionDeclarations['is_empty'] = false;
+ return $functionDeclarations;
+ }
+
+ /**
+ * @param array $docTokens
+ * @param array $functionDeclarations
+ * @return array
+ */
+ private function addThrowsTagValue(array $tokens, array $functionDeclarations)
+ {
+ $functionDeclarations['throws'][] = $tokens[0];
+ $functionDeclarations['is_empty'] = false;
+ return $functionDeclarations;
+ }
+}
diff --git a/Magento2/Sniffs/Commenting/FunctionsPHPDocFormattingSniff.php b/Magento2/Sniffs/Commenting/FunctionsPHPDocFormattingSniff.php
new file mode 100644
index 00000000..099c2e88
--- /dev/null
+++ b/Magento2/Sniffs/Commenting/FunctionsPHPDocFormattingSniff.php
@@ -0,0 +1,285 @@
+functionPHPDocBlock = new FunctionPHPDocBlockParser();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function register()
+ {
+ return [T_FUNCTION];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function process(File $phpcsFile, $stackPtr)
+ {
+ $funcReturnType = $this->analysePhp7ReturnDeclaration($phpcsFile, $stackPtr);
+ $funcParamTypeList = $this->analysePhp7ParamDeclarations($phpcsFile, $stackPtr);
+ $phpDocTokens = $this->getPhpDocTokens($phpcsFile, $stackPtr);
+ $hasPhp7TypeDeclarations = $funcReturnType !== false && count($funcParamTypeList['missing_type']) === 0;
+
+ if ($phpDocTokens === false && $hasPhp7TypeDeclarations === true) {
+ // NO check it use all php 7 type declarations and no php doc docblock
+ return;
+ }
+
+ if ($phpDocTokens === false && $hasPhp7TypeDeclarations === false) {
+ $phpcsFile->addWarning('Use php 7 type declarations or an php doc block', $stackPtr, $this->warningCode);
+
+ return;
+ }
+ $tokens = $phpcsFile->getTokens();
+ $phpDocTokensList = $this->functionPHPDocBlock->execute(
+ $tokens,
+ $phpDocTokens[0],
+ $phpDocTokens[1]
+ );
+
+ if ($phpDocTokensList['is_empty']) {
+ $phpcsFile->addWarning('Empty Docblock SHOULD NOT be used', $stackPtr, $this->warningCode);
+ return;
+ }
+
+ $description = $phpDocTokensList['description'];
+
+ if ($description !== false) {
+ $functionNameToken = $phpcsFile->findNext(T_STRING, $stackPtr, $tokens[$stackPtr]['parenthesis_opener']);
+ $functionName = str_replace(['_', ' ', '.', ','], '', strtolower($tokens[$functionNameToken]['content']));
+ $description = str_replace(['_', ' ', '.', ','], '', strtolower($description));
+
+ if ($functionName === $description) {
+ $phpcsFile->addWarning(
+ sprintf(
+ '%s description should contain additional information beyond the name already supplies.',
+ ucfirst($phpDocTokensList['description'])
+ ),
+ $stackPtr,
+ 'InvalidDescription'
+ );
+ }
+ }
+
+ if (array_key_exists('@inheritdoc', array_flip($phpDocTokensList['tags']))) {
+ $phpcsFile->addWarning('The @inheritdoc tag SHOULD NOT be used', $stackPtr, $this->warningCode);
+ return;
+ }
+
+ if (count($phpDocTokensList['parameters']) > 0) {
+ $this->comparePhp7WithDocBlock(
+ $funcParamTypeList,
+ $phpDocTokensList['parameters'],
+ $phpcsFile,
+ $stackPtr
+ );
+ }
+
+
+ }
+
+ /**
+ * Search for php7 return type declarations like func() : string {}
+ * @param File $phpcsFile
+ * @param $stackPtr
+ * @return bool|string
+ */
+ private function analysePhp7ReturnDeclaration(File $phpcsFile, $stackPtr)
+ {
+ $tokens = $phpcsFile->getTokens();
+ $functionNameToken = $phpcsFile->findNext(T_STRING, $stackPtr, $tokens[$stackPtr]['parenthesis_opener']);
+ if (strpos($tokens[$functionNameToken]['content'], '__construct') === 0) {
+ // magic functions start with __construct dont have php7 return type
+ return 'void';
+ }
+
+ $funcParamCloser = $tokens[$stackPtr]['parenthesis_closer'];
+ $funcReturnTypePos = $phpcsFile->findNext([T_STRING], $funcParamCloser, $tokens[$stackPtr]['scope_opener']);
+ $funcReturnType = false;
+ if ($funcReturnTypePos !== false) {
+ $funcReturnType = $tokens[$funcReturnTypePos]['content'];
+ }
+
+ return $funcReturnType;
+ }
+
+ /**
+ * Search for php7 return type declarations like func(bool $arg1) {}
+ * @param File $phpcsFile
+ * @param $stackPtr
+ * @return array
+ */
+ private function analysePhp7ParamDeclarations(File $phpcsFile, $stackPtr)
+ {
+ $tokens = $phpcsFile->getTokens();
+
+ $funcParamStart = $tokens[$stackPtr]['parenthesis_opener'];
+ $funcParamCloser = $tokens[$stackPtr]['parenthesis_closer'];
+
+ $functionParameterTokens = array_slice(
+ $tokens,
+ $funcParamStart + 1,
+ $funcParamCloser - ($funcParamStart + 1)
+ );
+
+ return $this->parseFunctionTokens($functionParameterTokens);
+ }
+
+ /**
+ * Returns all parameter as list with there php 7 type declarations like
+ * func(string $arg1, int $arg2)
+ *
+ * @param array $tokens
+ * @return array
+ */
+ private function parseFunctionTokens(array $tokens)
+ {
+ $paramType = null;
+ $functionParameterList = [
+ 'count' => 0,
+ 'missing_type' => [],
+ 'has_type' => [],
+ ];
+
+ foreach ($tokens as $token) {
+ $type = $token['code'];
+ $content = $token['content'];
+
+ if ($type === T_COMMA) {
+ $paramType = null;
+ continue;
+ }
+
+ if ($type === T_STRING) {
+ $paramType = $content;
+ continue;
+ }
+ if ($content === ' ') {
+ continue;
+ }
+
+ $functionParameterList['count']++;
+ $key = $paramType !== null ? 'has_type' : 'missing_type';
+ $functionParameterList[$key][$content] =
+ [
+ 'content' => $content,
+ 'type' => $paramType,
+ ];
+ }
+
+ return $functionParameterList;
+ }
+
+ /**
+ * Parse the doc block for type and return declarations
+ * @param File $phpcsFile
+ * @param $stackPtr
+ * @return array|bool
+ */
+ private function getPhpDocTokens(File $phpcsFile, $stackPtr)
+ {
+ $tokens = $phpcsFile->getTokens();
+
+ // search for php doc block
+ $find = Tokens::$methodPrefixes;
+ $find[] = T_WHITESPACE;
+
+ $commentEnd = $phpcsFile->findPrevious($find, $stackPtr - 1, null, true);
+ $commentStart = false;
+
+ if ($commentEnd !== false) {
+ $endToken = $tokens[$commentEnd];
+ if ($endToken['code'] === T_DOC_COMMENT_CLOSE_TAG) {
+ $commentStart = $tokens[$commentEnd]['comment_opener'];
+ }
+ }
+
+ if ($commentStart === false) {
+ return false;
+ }
+
+ return [$commentStart + 1, $commentEnd - 1];
+ }
+
+ private function comparePhp7WithDocBlock(array $php7Tokens, array $docBlockTokens, File $phpcsFile, $stackPtr)
+ {
+
+ $parsedDocToken = [];
+ foreach ($docBlockTokens as $token) {
+ $parameterName = $token['content'];
+ if (isset($parsedDocToken[$parameterName])) {
+ $phpcsFile->addWarning(
+ sprintf('Parameter %s is definitely multiple', $parameterName),
+ $stackPtr,
+ $this->warningCode
+ );
+ return;
+ }
+
+ $parsedDocToken[$parameterName] = $token['type'];
+ }
+
+ if (count($parsedDocToken) > $php7Tokens['count']) {
+ $phpcsFile->addWarning(
+ 'More documented parameter than real function parameter',
+ $stackPtr,
+ $this->warningCode
+ );
+ return;
+ }
+
+ $hasMissingTypes = count($php7Tokens['missing_type']) > 0;
+ if ($hasMissingTypes === false) {
+ return;
+ }
+
+ $php7ParamKey = array_keys($php7Tokens['missing_type']);
+ $parsedDocTokenKeys = array_keys($parsedDocToken);
+ if ($php7ParamKey !== $parsedDocTokenKeys) {
+ $phpcsFile->addWarning(
+ 'Documented parameter and real function parameter dont match',
+ $stackPtr,
+ $this->warningCode
+ );
+ }
+
+ foreach ($php7ParamKey as $parameter) {
+ if (!isset($parsedDocToken[$parameter]) || $parsedDocToken[$parameter] === false) {
+ $phpcsFile->addWarning(
+ sprintf('Type for parameter %s is missing', $parameter),
+ $stackPtr,
+ $this->warningCode
+ );
+ }
+ }
+ }
+}
diff --git a/Magento2/Tests/Commenting/FunctionsPHPDocFormattingUnitTest.1.inc b/Magento2/Tests/Commenting/FunctionsPHPDocFormattingUnitTest.1.inc
new file mode 100644
index 00000000..f3f34273
--- /dev/null
+++ b/Magento2/Tests/Commenting/FunctionsPHPDocFormattingUnitTest.1.inc
@@ -0,0 +1,61 @@
+ 1,
+ 19 => 1,
+ 26 => 2,
+ 30 => 1,
+ 40 => 1,
+ 47 => 1,
+ 55 => 2,
+ 62 => 1,
+ 69 => 1,
+ ];
+ }
+}
diff --git a/Magento2/ruleset.xml b/Magento2/ruleset.xml
index 9d65e166..9b23bcdd 100644
--- a/Magento2/ruleset.xml
+++ b/Magento2/ruleset.xml
@@ -556,6 +556,10 @@
5
warning
+
+ 5
+ warning
+
5
warning