<?php
/**
 * PHPCompatibility, an external standard for PHP_CodeSniffer.
 *
 * @package   PHPCompatibility
 * @copyright 2012-2019 PHPCompatibility Contributors
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
 * @link      https://github.com/PHPCompatibility/PHPCompatibility
 */

namespace PHPCompatibility\Sniffs\Interfaces;

use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;

/**
 * Detect classes which implement PHP native interfaces intended only for PHP internal use.
 *
 * PHP version 5.0+
 *
 * @link https://www.php.net/manual/en/class.traversable.php
 * @link https://www.php.net/manual/en/class.throwable.php
 * @link https://www.php.net/manual/en/class.datetimeinterface.php
 *
 * @since 7.0.3
 */
class InternalInterfacesSniff extends Sniff {


	/**
	 * A list of PHP internal interfaces, not intended to be implemented by userland classes.
	 *
	 * The array lists : the error message to use.
	 *
	 * @since 7.0.3
	 *
	 * @var array(string => string)
	 */
	protected $internalInterfaces = array(
		'Traversable'       => 'shouldn\'t be implemented directly, implement the Iterator or IteratorAggregate interface instead.',
		'DateTimeInterface' => 'is intended for type hints only and is not implementable.',
		'Throwable'         => 'cannot be implemented directly, extend the Exception class instead.',
	);


	/**
	 * Returns an array of tokens this test wants to listen for.
	 *
	 * @since 7.0.3
	 *
	 * @return array
	 */
	public function register() {
		// Handle case-insensitivity of interface names.
		$this->internalInterfaces = $this->arrayKeysToLowercase( $this->internalInterfaces );

		$targets = array( \T_CLASS );

		if ( \defined( 'T_ANON_CLASS' ) ) {
			$targets[] = \T_ANON_CLASS;
		}

		return $targets;
	}


	/**
	 * Processes this test, when one of its tokens is encountered.
	 *
	 * @since 7.0.3
	 *
	 * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
	 * @param int                   $stackPtr  The position of the current token in
	 *                                         the stack passed in $tokens.
	 *
	 * @return void
	 */
	public function process( File $phpcsFile, $stackPtr ) {
		$interfaces = PHPCSHelper::findImplementedInterfaceNames( $phpcsFile, $stackPtr );

		if ( \is_array( $interfaces ) === false || $interfaces === array() ) {
			return;
		}

		foreach ( $interfaces as $interface ) {
			$interface   = ltrim( $interface, '\\' );
			$interfaceLc = strtolower( $interface );
			if ( isset( $this->internalInterfaces[ $interfaceLc ] ) === true ) {
				$error     = 'The interface %s %s';
				$errorCode = $this->stringToErrorCode( $interfaceLc ) . 'Found';
				$data      = array(
					$interface,
					$this->internalInterfaces[ $interfaceLc ],
				);

				$phpcsFile->addError( $error, $stackPtr, $errorCode, $data );
			}
		}
	}
}
