/** * @param \PHP_CodeSniffer_File $phpcsFile * @param integer $openTagPointer */ public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer) { $referencedNames = ReferencedNameHelper::getAllReferencedNames($phpcsFile, $openTagPointer); $useStatements = UseStatementHelper::getUseStatements($phpcsFile, $openTagPointer); foreach ($referencedNames as $referencedName) { $pointer = $referencedName->getPointer(); $name = $referencedName->getNameAsReferencedInFile(); $normalizedName = UseStatement::normalizedNameAsReferencedInFile($name); if (isset($useStatements[$normalizedName]) && $referencedName->hasSameUseStatementType($useStatements[$normalizedName])) { $useStatement = $useStatements[$normalizedName]; if (in_array($useStatement->getFullyQualifiedTypeName(), $this->getIgnoredNames(), true) || !StringHelper::endsWith($useStatement->getFullyQualifiedTypeName(), 'Exception') && $useStatement->getFullyQualifiedTypeName() !== 'Throwable' && (!StringHelper::endsWith($useStatement->getFullyQualifiedTypeName(), 'Error') || NamespaceHelper::hasNamespace($useStatement->getFullyQualifiedTypeName())) && !in_array($useStatement->getFullyQualifiedTypeName(), $this->getSpecialExceptionNames(), true)) { continue; } } else { $fileNamespace = NamespaceHelper::findCurrentNamespaceName($phpcsFile, $pointer); $canonicalName = $name; if (!NamespaceHelper::isFullyQualifiedName($name) && $fileNamespace !== null) { $canonicalName = sprintf('%s%s%s', $fileNamespace, NamespaceHelper::NAMESPACE_SEPARATOR, $name); } if (in_array($canonicalName, $this->getIgnoredNames(), true) || !StringHelper::endsWith($name, 'Exception') && $name !== 'Throwable' && (!StringHelper::endsWith($canonicalName, 'Error') || NamespaceHelper::hasNamespace($canonicalName)) && !in_array($canonicalName, $this->getSpecialExceptionNames(), true)) { continue; } } if (!NamespaceHelper::isFullyQualifiedName($name)) { $phpcsFile->addError(sprintf('Exception %s should be referenced via a fully qualified name', $name), $pointer, self::CODE_NON_FULLY_QUALIFIED_EXCEPTION); } } }
/** * @param string $path * @return string|null */ public function getTypeNameFromProjectPath($path) { if (pathinfo($path, PATHINFO_EXTENSION) !== 'php') { return null; } $pathParts = explode(DIRECTORY_SEPARATOR, $path); $rootNamespace = null; while (count($pathParts) > 0) { array_shift($pathParts); foreach ($this->rootNamespaces as $directory => $namespace) { if (StringHelper::startsWith(implode('/', $pathParts) . '/', $directory . '/')) { for ($i = 0; $i < count(explode('/', $directory)); $i++) { array_shift($pathParts); } $rootNamespace = $namespace; break 2; } } } if ($rootNamespace === null) { return null; } if (count($pathParts) === 0) { return null; } array_unshift($pathParts, $rootNamespace); $typeName = implode('\\', array_filter($pathParts, function ($pathPart) { return !isset($this->skipDirs[$pathPart]); })); return substr($typeName, 0, -strlen('.php')); }
/** * @param \PHP_CodeSniffer_File $phpcsFile * @param integer $usePointer */ public function process(PHP_CodeSniffer_File $phpcsFile, $usePointer) { if (UseStatementHelper::isAnonymousFunctionUse($phpcsFile, $usePointer) || UseStatementHelper::isTraitUse($phpcsFile, $usePointer)) { return; } $namespaceName = NamespaceHelper::findCurrentNamespaceName($phpcsFile, $usePointer); if ($namespaceName === null) { $namespaceName = ''; } $usedTypeName = UseStatementHelper::getFullyQualifiedTypeNameFromUse($phpcsFile, $usePointer); if (!StringHelper::startsWith($usedTypeName, $namespaceName)) { return; } $asPointer = $this->findAsPointer($phpcsFile, $usePointer); if ($asPointer !== null) { return; } $usedTypeNameRest = substr($usedTypeName, strlen($namespaceName)); if (!NamespaceHelper::isFullyQualifiedName($usedTypeNameRest) && $namespaceName !== '') { return; } if (!NamespaceHelper::hasNamespace($usedTypeNameRest)) { $fix = $phpcsFile->addFixableError(sprintf('Use %s is from the same namespace – that is prohibited', $usedTypeName), $usePointer, self::CODE_USE_FROM_SAME_NAMESPACE); if ($fix) { $phpcsFile->fixer->beginChangeset(); $endPointer = $phpcsFile->findNext(T_SEMICOLON, $usePointer) + 1; for ($i = $usePointer; $i <= $endPointer; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->endChangeset(); } } }
/** * @param \PHP_CodeSniffer_File $phpcsFile * @param integer $openTagPointer * @param boolean $searchAnnotations * @return \SlevomatCodingStandard\Helpers\ReferencedName[] referenced names */ private static function createAllReferencedNames(PHP_CodeSniffer_File $phpcsFile, $openTagPointer, $searchAnnotations) { $beginSearchAtPointer = $openTagPointer + 1; $tokens = $phpcsFile->getTokens(); $phpDocTypes = [T_DOC_COMMENT_STRING, T_DOC_COMMENT_TAG]; $searchTypes = array_merge([T_RETURN_TYPE], TokenHelper::$nameTokenCodes); if ($searchAnnotations) { $searchTypes = array_merge($phpDocTypes, $searchTypes); } $types = []; $matchTypesInAnnotation = function ($annotation, $nameStartPointer) use(&$types) { $annotation = trim($annotation, '@ '); if (preg_match('#([a-zA-Z0-9_|\\[\\]\\\\]+)#', $annotation, $matches) > 0) { $referencedNames = array_filter(array_map(function ($name) { return trim($name, '[]'); }, explode('|', $matches[1])), function ($match) { return !in_array($match, ['null', 'self', 'static', 'mixed', 'array', 'string', 'int', 'integer', 'bool', 'boolean'], true); }); foreach ($referencedNames as $name) { $types[] = new ReferencedName($name, $nameStartPointer); } } }; while (true) { $nameStartPointer = $phpcsFile->findNext($searchTypes, $beginSearchAtPointer); if ($nameStartPointer === false) { break; } $nameStartToken = $tokens[$nameStartPointer]; if (in_array($nameStartToken['code'], $phpDocTypes, true)) { if ($nameStartToken['code'] === T_DOC_COMMENT_TAG) { if (!StringHelper::startsWith($nameStartToken['content'], '@var') && !StringHelper::startsWith($nameStartToken['content'], '@param') && !StringHelper::startsWith($nameStartToken['content'], '@return') && !StringHelper::startsWith($nameStartToken['content'], '@throws') && !StringHelper::startsWith($nameStartToken['content'], '@see') && !StringHelper::startsWith($nameStartToken['content'], '@link') && !StringHelper::startsWith($nameStartToken['content'], '@inherit')) { $matchTypesInAnnotation($nameStartToken['content'], $nameStartPointer); } } elseif ($nameStartToken['code'] === T_DOC_COMMENT_STRING) { $matchTypesInAnnotation($nameStartToken['content'], $nameStartPointer); } $beginSearchAtPointer = $nameStartPointer + 1; continue; } $nameEndPointer = self::findReferencedNameEndPointer($phpcsFile, $nameStartPointer); if ($nameEndPointer === null) { $beginSearchAtPointer = TokenHelper::findNextExcluding($phpcsFile, array_merge([T_WHITESPACE, T_RETURN_TYPE], TokenHelper::$nameTokenCodes), $nameStartPointer); continue; } $types[] = new ReferencedName(TokenHelper::getContent($phpcsFile, $nameStartPointer, $nameEndPointer), $nameStartPointer); $beginSearchAtPointer = $nameEndPointer + 1; } return $types; }
/** * @param \PHP_CodeSniffer_File $phpcsFile * @param integer $stackPointer */ public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPointer) { $tokens = $phpcsFile->getTokens(); $namePointer = $phpcsFile->findNext(T_STRING, $stackPointer + 1); $nameToken = $tokens[$namePointer]; $typeName = $nameToken['content']; $namespacePointer = $phpcsFile->findPrevious(T_NAMESPACE, $stackPointer - 1); if ($namespacePointer !== false) { $namespaceName = ''; while (true) { $namespaceNamePartPointer = $phpcsFile->findNext([T_STRING, T_NS_SEPARATOR, T_SEMICOLON], $namespacePointer + 1); if ($namespaceNamePartPointer === false) { break; } $namespaceNamePartToken = $tokens[$namespaceNamePartPointer]; if ($namespaceNamePartToken['code'] === T_SEMICOLON) { break; } $namespaceName .= $namespaceNamePartToken['content']; $namespacePointer = $namespaceNamePartPointer; } $typeName = $namespaceName . '\\' . $typeName; } else { // skip types without a namespace return; } foreach ($this->ignoredNamespaces as $ignoredNamespace) { if (StringHelper::startsWith($typeName, $ignoredNamespace . '\\')) { return; } } $expectedTypeName = $this->getNamespaceExtractor()->getTypeNameFromProjectPath($phpcsFile->getFilename()); if ($typeName !== $expectedTypeName) { $phpcsFile->addError(sprintf('%s name %s does not match filepath %s.', ucfirst($tokens[$stackPointer]['content']), $typeName, $phpcsFile->getFilename()), $namePointer); } }
/** * @param string $typeName * @param string $namespace * @return boolean */ public static function isTypeInNamespace($typeName, $namespace) { return StringHelper::startsWith(self::normalizeToCanonicalName($typeName) . '\\', $namespace . '\\'); }
/** * @param \PHP_CodeSniffer_File $phpcsFile * @param integer $openTagPointer */ public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer) { $tokens = $phpcsFile->getTokens(); $referencedNames = ReferencedNameHelper::getAllReferencedNames($phpcsFile, $openTagPointer); foreach ($referencedNames as $referencedName) { $name = $referencedName->getNameAsReferencedInFile(); $pointer = $referencedName->getPointer(); $canonicalName = NamespaceHelper::normalizeToCanonicalName($name); if (NamespaceHelper::isFullyQualifiedName($name)) { $isExceptionByName = StringHelper::endsWith($name, 'Exception') || $name === '\\Throwable' || StringHelper::endsWith($name, 'Error') && !NamespaceHelper::hasNamespace($name) || in_array($canonicalName, $this->getSpecialExceptionNames(), true); $inIgnoredNames = in_array($canonicalName, $this->getIgnoredNames(), true); if ($this->isClassRequiredToBeUsed($name) && (!$this->allowFullyQualifiedExceptions || !$isExceptionByName || $inIgnoredNames)) { $previousKeywordPointer = TokenHelper::findPreviousExcluding($phpcsFile, array_merge(TokenHelper::$nameTokenCodes, [T_WHITESPACE, T_COMMA]), $pointer - 1); if (!in_array($tokens[$previousKeywordPointer]['code'], $this->getFullyQualifiedKeywords(), true)) { if (!NamespaceHelper::hasNamespace($name) && NamespaceHelper::findCurrentNamespaceName($phpcsFile, $pointer) === null) { $phpcsFile->addError(sprintf('Type %s should not be referenced via a fully qualified name, but via an unqualified name without the leading \\, because the file does not have a namespace and the type cannot be put in a use statement', $name), $pointer, self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME_WITHOUT_NAMESPACE); } else { $phpcsFile->addError(sprintf('Type %s should not be referenced via a fully qualified name, but via a use statement', $name), $pointer, self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME); } } } } elseif (!$this->allowPartialUses) { if (NamespaceHelper::isQualifiedName($name)) { $phpcsFile->addError(sprintf('Partial use statements are not allowed, but referencing %s found', $name), $pointer, self::CODE_PARTIAL_USE); } } } }
/** * @param PHP_CodeSniffer_File $phpcsFile * @param mixed[] $tokens * @param mixed[] $classToken * @return integer[] string(name) => pointer */ private function getProperties(PHP_CodeSniffer_File $phpcsFile, array $tokens, array $classToken) { $reportedProperties = []; $findPropertiesStartTokenPointer = $classToken['scope_opener'] + 1; while (($propertyTokenPointer = $phpcsFile->findNext(T_VARIABLE, $findPropertiesStartTokenPointer, $classToken['scope_closer'])) !== false) { $visibilityModifiedTokenPointer = TokenHelper::findPreviousNonWhitespace($phpcsFile, $propertyTokenPointer - 1); $visibilityModifiedToken = $tokens[$visibilityModifiedTokenPointer]; if (in_array($visibilityModifiedToken['code'], [T_PUBLIC, T_PROTECTED], true)) { $findPropertiesStartTokenPointer = $propertyTokenPointer + 1; continue; } elseif ($visibilityModifiedToken['code'] !== T_PRIVATE) { break; } $findPropertiesStartTokenPointer = $propertyTokenPointer + 1; $phpDocTags = $this->getPhpDocTags($phpcsFile, $tokens, $visibilityModifiedTokenPointer); foreach ($phpDocTags as $tag) { preg_match('#([@a-zA-Z\\\\]+)#', $tag, $matches); if (in_array($matches[1], $this->alwaysUsedPropertiesAnnotations, true)) { continue 2; } } $propertyToken = $tokens[$propertyTokenPointer]; $name = substr($propertyToken['content'], 1); foreach ($this->alwaysUsedPropertiesSuffixes as $prefix) { if (StringHelper::endsWith($name, $prefix)) { continue 2; } } $reportedProperties[$name] = $propertyTokenPointer; } return $reportedProperties; }