<?php
/**
 * Check for merge conflict artefacts.
 *
 * @author    Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
 * @copyright 2017 Juliette Reinders Folmer. All rights reserved.
 * @copyright 2023 PHPCSStandards and contributors
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/HEAD/licence.txt BSD Licence
 */

namespace PHP_CodeSniffer\Standards\Generic\Sniffs\VersionControl;

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

class GitMergeConflictSniff implements Sniff
{


    /**
     * Returns an array of tokens this test wants to listen for.
     *
     * @return array<int|string>
     */
    public function register()
    {
        return [
            T_OPEN_TAG,
            T_OPEN_TAG_WITH_ECHO,
        ];
    }


    /**
     * Processes this test, when one of its tokens is encountered.
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
     * @param int                         $stackPtr  The position of the current token in the
     *                                               stack passed in $tokens.
     *
     * @return int
     */
    public function process(File $phpcsFile, int $stackPtr)
    {
        $tokens = $phpcsFile->getTokens();
        $error  = 'Merge conflict boundary found; type: %s';

        $checkTokens = [
            T_SL                      => true,
            T_SR                      => true,
            T_IS_IDENTICAL            => true,
            T_COMMENT                 => true,
            T_DOC_COMMENT_STRING      => true,
            T_ENCAPSED_AND_WHITESPACE => true,
            T_INLINE_HTML             => true,
            T_HEREDOC                 => true,
            T_NOWDOC                  => true,
        ];

        for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
            if ($tokens[$i]['column'] !== 1 || isset($checkTokens[$tokens[$i]['code']]) === false) {
                continue;
            }

            switch ($tokens[$i]['code']) {
                // Check for first non-comment, non-heredoc/nowdoc, non-inline HTML merge conflict opener.
                case T_SL:
                    if (isset($tokens[($i + 1)], $tokens[($i + 2)]) !== false
                        && $tokens[($i + 1)]['code'] === T_SL
                        && $tokens[($i + 2)]['code'] === T_STRING
                        && trim($tokens[($i + 2)]['content']) === '<<< HEAD'
                    ) {
                        $phpcsFile->addError($error, $i, 'OpenerFound', ['opener']);
                        $i += 2;
                    }
                    break;

                // Check for merge conflict closer which was opened in a heredoc/nowdoc.
                case T_SR:
                    if (isset($tokens[($i + 1)], $tokens[($i + 2)], $tokens[($i + 3)], $tokens[($i + 4)]) !== false
                        && $tokens[($i + 1)]['code'] === T_SR
                        && $tokens[($i + 2)]['code'] === T_SR
                        && $tokens[($i + 3)]['code'] === T_GREATER_THAN
                        && $tokens[($i + 4)]['code'] === T_WHITESPACE
                        && $tokens[($i + 4)]['content'] === ' '
                    ) {
                        $phpcsFile->addError($error, $i, 'CloserFound', ['closer']);
                        $i += 4;
                    }
                    break;

                // - Check for delimiters and closers.
                // - Inspect heredoc/nowdoc content, comments and inline HTML.
                // - Check for subsequent merge conflict openers after the first broke the tokenizer.
                case T_ENCAPSED_AND_WHITESPACE:
                case T_COMMENT:
                case T_DOC_COMMENT_STRING:
                case T_INLINE_HTML:
                case T_HEREDOC:
                case T_NOWDOC:
                    if (substr($tokens[$i]['content'], 0, 12) === '<<<<<<< HEAD') {
                        $phpcsFile->addError($error, $i, 'OpenerFound', ['opener']);
                        break;
                    } elseif (substr($tokens[$i]['content'], 0, 8) === '>>>>>>> ') {
                        $phpcsFile->addError($error, $i, 'CloserFound', ['closer']);
                        break;
                    }

                    if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
                        if ($tokens[$i]['content'] === '======='
                            && $tokens[($i + 1)]['code'] === T_DOC_COMMENT_WHITESPACE
                        ) {
                            $phpcsFile->addError($error, $i, 'DelimiterFound', ['delimiter']);
                            break;
                        }
                    } else {
                        if ($tokens[$i]['content'] === "=======\n") {
                            $phpcsFile->addError($error, $i, 'DelimiterFound', ['delimiter']);
                        }
                    }
                    break;
            }
        }

        // Ignore the rest of the file.
        return $phpcsFile->numTokens;
    }
}
