getTokens(); if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_USE ) { throw new RuntimeException('$stackPtr must be of type T_USE'); } $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); if ($next === false) { // Live coding or parse error. return ''; } // More efficient & simpler check for closure use in PHPCS 4.x. if (isset($tokens[$stackPtr]['parenthesis_owner']) && $tokens[$stackPtr]['parenthesis_owner'] === $stackPtr ) { return 'closure'; } $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); if ($prev !== false && $tokens[$prev]['code'] === \T_CLOSE_PARENTHESIS && Parentheses::isOwnerIn($phpcsFile, $prev, \T_CLOSURE) === true ) { return 'closure'; } $lastCondition = Conditions::getLastCondition($phpcsFile, $stackPtr); if ($lastCondition === false || $tokens[$lastCondition]['code'] === \T_NAMESPACE) { // Global or scoped namespace and not a closure use statement. return 'import'; } $traitScopes = Tokens::$ooScopeTokens; // Only classes, traits and enums can import traits. unset($traitScopes[\T_INTERFACE]); if (isset($traitScopes[$tokens[$lastCondition]['code']]) === true) { return 'trait'; } return ''; } /** * Determine whether a T_USE token represents a closure use statement. * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position of the `T_USE` token. * * @return bool `TRUE` if the token passed is a closure use statement. * `FALSE` if it's not. * * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a * `T_USE` token. */ public static function isClosureUse(File $phpcsFile, $stackPtr) { return (self::getType($phpcsFile, $stackPtr) === 'closure'); } /** * Determine whether a T_USE token represents a class/function/constant import use statement. * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position of the `T_USE` token. * * @return bool `TRUE` if the token passed is an import use statement. * `FALSE` if it's not. * * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a * `T_USE` token. */ public static function isImportUse(File $phpcsFile, $stackPtr) { return (self::getType($phpcsFile, $stackPtr) === 'import'); } /** * Determine whether a T_USE token represents a trait use statement. * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position of the `T_USE` token. * * @return bool `TRUE` if the token passed is a trait use statement. * `FALSE` if it's not. * * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a * `T_USE` token. */ public static function isTraitUse(File $phpcsFile, $stackPtr) { return (self::getType($phpcsFile, $stackPtr) === 'trait'); } /** * Split an import use statement into individual imports. * * Handles single import, multi-import and group-import use statements. * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. * @param int $stackPtr The position in the stack of the `T_USE` token. * * @return array A multi-level array containing information about the use statement. * The first level is `'name'`, `'function'` and `'const'`. These keys will always exist. * If any statements are found for any of these categories, the second level * will contain the alias/name as the key and the full original use name as the * value for each of the found imports or an empty array if no imports were found * in this use statement for a particular category. * * For example, for this function group use statement: * ```php * use function Vendor\Package\{ * LevelA\Name as Alias, * LevelB\Another_Name, * }; * ``` * the return value would look like this: * ```php * array( * 'name' => array(), * 'function' => array( * 'Alias' => 'Vendor\Package\LevelA\Name', * 'Another_Name' => 'Vendor\Package\LevelB\Another_Name', * ), * 'const' => array(), * ) * ``` * * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a * `T_USE` token. * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the `T_USE` token is not for an import * use statement. */ public static function splitImportUseStatement(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_USE) { throw new RuntimeException('$stackPtr must be of type T_USE'); } if (self::isImportUse($phpcsFile, $stackPtr) === false) { throw new RuntimeException('$stackPtr must be an import use statement'); } if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) { return Cache::get($phpcsFile, __METHOD__, $stackPtr); } $statements = [ 'name' => [], 'function' => [], 'const' => [], ]; $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); if ($endOfStatement === false) { // Live coding or parse error. Cache::set($phpcsFile, __METHOD__, $stackPtr, $statements); return $statements; } ++$endOfStatement; $start = true; $useGroup = false; $hasAlias = false; $baseName = ''; $name = ''; $type = ''; $fixedType = false; $alias = ''; for ($i = ($stackPtr + 1); $i < $endOfStatement; $i++) { if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { continue; } $tokenCode = $tokens[$i]['code']; switch ($tokenCode) { case \T_STRING: // Only when either at the start of the statement or at the start of a new sub within a group. if ($start === true && $fixedType === false) { $content = \strtolower($tokens[$i]['content']); if ($content === 'function' || $content === 'const' ) { $type = $content; $start = false; if ($useGroup === false) { $fixedType = true; } break; } else { $type = 'name'; } } $start = false; if ($hasAlias === false) { $name .= $tokens[$i]['content']; } $alias = $tokens[$i]['content']; break; case \T_NAME_QUALIFIED: case \T_NAME_FULLY_QUALIFIED: // This would be a parse error, but handle it anyway. /* * PHPCS 4.x. * * These tokens can only be encountered when either at the start of the statement * or at the start of a new sub within a group. */ if ($start === true && $fixedType === false) { $type = 'name'; } $start = false; if ($hasAlias === false) { $name .= $tokens[$i]['content']; } $alias = \substr($tokens[$i]['content'], (\strrpos($tokens[$i]['content'], '\\') + 1)); break; case \T_AS: $hasAlias = true; break; case \T_OPEN_USE_GROUP: $start = true; $useGroup = true; $baseName = $name; $name = ''; break; case \T_SEMICOLON: case \T_CLOSE_TAG: case \T_CLOSE_USE_GROUP: case \T_COMMA: if ($name !== '') { if ($useGroup === true) { $statements[$type][$alias] = $baseName . $name; } else { $statements[$type][$alias] = $name; } } if ($tokenCode !== \T_COMMA) { break 2; } // Reset. $start = true; $name = ''; $hasAlias = false; if ($fixedType === false) { $type = ''; } break; case \T_NS_SEPARATOR: $name .= $tokens[$i]['content']; break; /* * Fall back in case reserved keyword is (illegally) used in name. * Parse error, but not our concern. */ default: if ($hasAlias === false) { // Defensive coding, just in case. Should no longer be possible since PHPCS 3.7.0. $name .= $tokens[$i]['content']; // @codeCoverageIgnore } $alias = $tokens[$i]['content']; break; } } Cache::set($phpcsFile, __METHOD__, $stackPtr, $statements); return $statements; } /** * Split an import use statement into individual imports and merge it with an array of previously * seen import use statements. * * Beware: this method should only be used to combine the import use statements found in *one* file. * Do NOT combine the statements of multiple files as the result will be inaccurate and unreliable. * * In most cases when tempted to use this method, the {@see \PHPCSUtils\AbstractSniffs\AbstractFileContextSniff} * (upcoming) should be used instead. * * @see \PHPCSUtils\AbstractSniffs\AbstractFileContextSniff * @see \PHPCSUtils\Utils\UseStatements::splitImportUseStatement() * @see \PHPCSUtils\Utils\UseStatements::mergeImportUseStatements() * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. * @param int $stackPtr The position in the stack of the `T_USE` token. * @param array $previousUseStatements The import `use` statements collected so far. * This should be either the output of a * previous call to this method or the output of * an earlier call to the * {@see UseStatements::splitImportUseStatement()} * method. * * @return array A multi-level array containing information about the current `use` statement combined with * the previously collected `use` statement information. * See {@see UseStatements::splitImportUseStatement()} for more details about the array format. */ public static function splitAndMergeImportUseStatement(File $phpcsFile, $stackPtr, array $previousUseStatements) { try { $useStatements = self::splitImportUseStatement($phpcsFile, $stackPtr); $previousUseStatements = self::mergeImportUseStatements($previousUseStatements, $useStatements); } catch (RuntimeException $e) { // Not an import use statement. } return $previousUseStatements; } /** * Merge two import use statement arrays. * * Beware: this method should only be used to combine the import use statements found in *one* file. * Do NOT combine the statements of multiple files as the result will be inaccurate and unreliable. * * @see \PHPCSUtils\Utils\UseStatements::splitImportUseStatement() * * @since 1.0.0 * * @param array $previousUseStatements The import `use` statements collected so far. * This should be either the output of a * previous call to this method or the output of * an earlier call to the * {@see UseStatements::splitImportUseStatement()} * method. * @param array $currentUseStatement The parsed import `use` statements to merge with * the previously collected use statements. * This should be the output of a call to the * {@see UseStatements::splitImportUseStatement()} * method. * * @return array A multi-level array containing information about the current `use` statement combined with * the previously collected `use` statement information. * See {@see UseStatements::splitImportUseStatement()} for more details about the array format. */ public static function mergeImportUseStatements(array $previousUseStatements, array $currentUseStatement) { if (isset($previousUseStatements['name']) === false) { $previousUseStatements['name'] = $currentUseStatement['name']; } else { $previousUseStatements['name'] += $currentUseStatement['name']; } if (isset($previousUseStatements['function']) === false) { $previousUseStatements['function'] = $currentUseStatement['function']; } else { $previousUseStatements['function'] += $currentUseStatement['function']; } if (isset($previousUseStatements['const']) === false) { $previousUseStatements['const'] = $currentUseStatement['const']; } else { $previousUseStatements['const'] += $currentUseStatement['const']; } return $previousUseStatements; } }