You are here

FullyQualifiedNamespaceSniff.php in Coder 8.2

File

coder_sniffer/Drupal/Sniffs/Classes/FullyQualifiedNamespaceSniff.php
View source
<?php

/**
 * \Drupal\Sniffs\Classes\FullyQualifiedNamespaceSniff.
 *
 * @category PHP
 * @package  PHP_CodeSniffer
 * @link     http://pear.php.net/package/PHP_CodeSniffer
 */
namespace Drupal\Sniffs\Classes;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;

/**
 * Checks that class references do not use FQN but use statements.
 *
 * @category PHP
 * @package  PHP_CodeSniffer
 * @link     http://pear.php.net/package/PHP_CodeSniffer
 */
class FullyQualifiedNamespaceSniff implements Sniff {

  /**
   * Returns an array of tokens this test wants to listen for.
   *
   * @return array
   */
  public function register() {
    return array(
      T_NS_SEPARATOR,
    );
  }

  //end register()

  /**
   * Processes this test, when one of its tokens is encountered.
   *
   * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
   *                                               token was found.
   * @param int                         $stackPtr  The position in the PHP_CodeSniffer
   *                                               file's token stack where the token
   *                                               was found.
   *
   * @return void|int Optionally returns a stack pointer. The sniff will not be
   *                  called again on the current file until the returned stack
   *                  pointer is reached. Return $phpcsFile->numTokens + 1 to skip
   *                  the rest of the file.
   */
  public function process(File $phpcsFile, $stackPtr) {
    $tokens = $phpcsFile
      ->getTokens();

    // Skip this sniff in *api.php files because they want to have fully
    // qualified names for documentation purposes.
    if (substr($phpcsFile
      ->getFilename(), -8) === '.api.php') {
      return $phpcsFile->numTokens + 1;
    }

    // We are only interested in a backslash embedded between strings, which
    // means this is a class reference with more than once namespace part.
    if ($tokens[$stackPtr - 1]['code'] !== T_STRING || $tokens[$stackPtr + 1]['code'] !== T_STRING) {
      return;
    }

    // Check if this is a use statement and ignore those.
    $before = $phpcsFile
      ->findPrevious([
      T_STRING,
      T_NS_SEPARATOR,
      T_WHITESPACE,
    ], $stackPtr, null, true);
    if ($tokens[$before]['code'] === T_USE || $tokens[$before]['code'] === T_NAMESPACE) {
      return $phpcsFile
        ->findNext([
        T_STRING,
        T_NS_SEPARATOR,
      ], $stackPtr + 1, null, true);
    }

    // If this is a namespaced function call then ignore this because use
    // statements for functions are not possible in PHP 5.5 and lower.
    $after = $phpcsFile
      ->findNext([
      T_STRING,
      T_NS_SEPARATOR,
      T_WHITESPACE,
    ], $stackPtr, null, true);
    if ($tokens[$after]['code'] === T_OPEN_PARENTHESIS && $tokens[$before]['code'] !== T_NEW) {
      return $after + 1;
    }
    $error = 'Namespaced classes/interfaces/traits should be referenced with use statements';
    $fix = $phpcsFile
      ->addFixableError($error, $stackPtr, 'UseStatementMissing');
    if ($fix === true) {
      $fullName = $phpcsFile
        ->getTokensAsString($before + 1, $after - 1 - $before);
      $fullName = trim($fullName, '\\ ');
      $phpcsFile->fixer
        ->beginChangeset();

      // Replace the fully qualified name with the local name.
      for ($i = $before + 1; $i < $after; $i++) {
        if ($tokens[$i]['code'] !== T_WHITESPACE) {
          $phpcsFile->fixer
            ->replaceToken($i, '');
        }
      }
      $parts = explode('\\', $fullName);
      $className = end($parts);
      $phpcsFile->fixer
        ->addContentBefore($after - 1, $className);

      // Check if there is a use statement already for this class and
      // namespace.
      $alreadyUsed = false;
      $useStatement = $phpcsFile
        ->findNext(T_USE, 0);
      while ($useStatement !== false && empty($tokens[$useStatement]['conditions']) === true) {
        $useEnd = $phpcsFile
          ->findEndOfStatement($useStatement);
        $classRef = trim($phpcsFile
          ->getTokensAsString($useStatement + 1, $useEnd - 1 - $useStatement));
        if (strcasecmp($classRef, $fullName) === 0) {
          $alreadyUsed = true;
          break;
        }
        $useStatement = $phpcsFile
          ->findNext(T_USE, $useEnd + 1);
      }

      // @todo Check if the name is already in use - then we need to alias it.
      // Insert use statement at the beginning of the file if it is not there
      // already. Also check if another sniff (for example
      // UnusedUseStatementSniff) has already deleted the use statement, then
      // we need to add it back.
      if ($alreadyUsed === false || $phpcsFile->fixer
        ->getTokenContent($useStatement) !== $tokens[$useStatement]['content']) {

        // Check if there is a group of use statements and add it there.
        $useStatement = $phpcsFile
          ->findNext(T_USE, 0);
        if ($useStatement !== false && empty($tokens[$useStatement]['conditions']) === true) {
          $phpcsFile->fixer
            ->addContentBefore($useStatement, "use {$fullName};\n");
        }
        else {

          // Check if there is an @file comment.
          $beginning = 0;
          $fileComment = $phpcsFile
            ->findNext(T_WHITESPACE, $beginning + 1, null, true);
          if ($tokens[$fileComment]['code'] === T_DOC_COMMENT_OPEN_TAG) {
            $beginning = $tokens[$fileComment]['comment_closer'];
          }
          $phpcsFile->fixer
            ->addContent($beginning, "use {$fullName};\n");
        }
      }
      $phpcsFile->fixer
        ->endChangeset();
    }

    //end if

    // Continue after this class reference so that errors for this are not
    // flagged multiple times.
    return $phpcsFile
      ->findNext([
      T_STRING,
      T_NS_SEPARATOR,
    ], $stackPtr + 1, null, true);
  }

}

//end class

Classes

Namesort descending Description
FullyQualifiedNamespaceSniff Checks that class references do not use FQN but use statements.