<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd" name="Ramsey">

    <arg name="extensions" value="php"/>
    <arg name="colors"/>
    <arg value="sp"/>

    <file>./src</file>
    <file>./tests</file>

    <description>A common coding standard for Ramsey's PHP libraries.</description>

    <!-- Rules -->
    <rule ref="PSR12">
        <!-- Already checked with the sniff Squiz.WhiteSpace.SemicolonSpacing.Incorrect -->
        <exclude name="PSR12.Files.DeclareStatement.SpaceFoundBeforeSemicolon"/>
    </rule>

    <!-- Warn about TODOs and FIXMEs in code. -->
    <rule ref="Generic.Commenting.Fixme"/>
    <rule ref="Generic.Commenting.Todo"/>

    <!-- Constants must be all uppercase. -->
    <rule ref="Generic.NamingConventions.UpperCaseConstantName"/>

    <!-- Disallow 'else if' in favor of 'elseif' (increase PSR12 severity level from warning to error) -->
    <rule ref="PSR2.ControlStructures.ElseIfDeclaration.NotAllowed">
        <type>error</type>
    </rule>

    <!-- Forbid comments starting with # -->
    <rule ref="PEAR.Commenting.InlineComment"/>

    <!-- Force array element indentation with one tab stop -->
    <rule ref="Generic.Arrays.ArrayIndent"/>

    <!-- Forbid 'array(...)' -->
    <rule ref="Generic.Arrays.DisallowLongArraySyntax"/>

    <!-- Forbid empty statements -->
    <rule ref="Generic.CodeAnalysis.EmptyStatement">
        <!-- But allow empty catch -->
        <exclude name="Generic.CodeAnalysis.EmptyStatement.DetectedCatch"/>
    </rule>

    <!-- Forbid final methods in final classes -->
    <rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/>

    <!-- Force whitespace after a type cast -->
    <rule ref="Generic.Formatting.SpaceAfterCast"/>

    <!-- Forbid PHP 4 constructors -->
    <rule ref="Generic.NamingConventions.ConstructorName"/>

    <!-- Forbid any content before opening tag -->
    <rule ref="Generic.PHP.CharacterBeforePHPOpeningTag"/>

    <!-- Forbid backtick operator -->
    <rule ref="Generic.PHP.BacktickOperator"/>

    <!-- Forbid 'php_sapi_name()' function, use PHP_SAPI -->
    <rule ref="Generic.PHP.SAPIUsage"/>

    <!-- Forbid deprecated functions -->
    <rule ref="Generic.PHP.DeprecatedFunctions"/>

    <!-- Allow comments to run longer than 120 characters -->
    <rule ref="Generic.Files.LineLength">
        <properties>
            <property name="ignoreComments" value="true"/>
        </properties>
    </rule>

    <!-- Forbid a bunch of functions -->
    <rule ref="Generic.PHP.ForbiddenFunctions">
        <properties>
            <property name="forbiddenFunctions" type="array">
                <!-- Forbid 'empty()'; use more strict comparison instead -->
                <element key="empty" value="null"/>
                <!-- Forbid some alias functions for scalars -->
                <element key="doubleval" value="floatval"/>
                <element key="is_double" value="is_float"/>
                <element key="is_integer" value="is_int"/>
                <element key="is_long" value="is_int"/>
                <element key="is_real" value="is_float"/>
                <!-- Forbid remaining debug functions in the code -->
                <element key="d" value="null"/>
                <element key="dd" value="null"/>
                <element key="dump" value="null"/>
                <element key="dump_d" value="null"/>
                <element key="var_dump" value="null"/>
            </property>
        </properties>
    </rule>

    <!-- Forbid goto instruction -->
    <rule ref="Generic.PHP.DiscourageGoto"/>

    <!-- Forbid merge conflict artifacts left in files -->
    <rule ref="Generic.VersionControl.GitMergeConflict"/>

    <!-- Require comma after last element in multi-line array -->
    <rule ref="SlevomatCodingStandard.Arrays.TrailingArrayComma"/>

    <!-- Rules for single-line arrays. -->
    <rule ref="SlevomatCodingStandard.Arrays.SingleLineArrayWhitespace"/>

    <!-- Require presence of constant visibility -->
    <rule ref="SlevomatCodingStandard.Classes.ClassConstantVisibility">
        <properties>
            <property name="fixable" value="true"/>
        </properties>
    </rule>

    <!-- Forbid empty lines around type declarations -->
    <rule ref="SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces">
        <properties>
            <property name="linesCountAfterOpeningBrace" value="0"/>
            <property name="linesCountBeforeClosingBrace" value="0"/>
        </properties>
        <!-- Already checked with the sniff PSR2.Classes.ClassDeclaration.CloseBraceAfterBody -->
        <exclude name="SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces.IncorrectEmptyLinesBeforeClosingBrace"/>
    </rule>

    <!-- Require usage of ::class instead of __CLASS__, get_class(), get_class($this), get_called_class() and get_parent_class() -->
    <rule ref="SlevomatCodingStandard.Classes.ModernClassNameReference"/>

    <!-- Forbid uses of multiple traits separated by comma -->
    <rule ref="SlevomatCodingStandard.Classes.TraitUseDeclaration"/>

    <!-- Require no spaces before trait use, between trait uses and one space after trait uses -->
    <rule ref="SlevomatCodingStandard.Classes.TraitUseSpacing">
        <properties>
            <property name="linesCountAfterLastUse" value="1"/>
            <property name="linesCountAfterLastUseWhenLastInClass" value="0"/>
            <property name="linesCountBeforeFirstUse" value="0"/>
            <property name="linesCountBetweenUses" value="0"/>
        </properties>
    </rule>

    <rule ref="SlevomatCodingStandard.Commenting.DocCommentSpacing">
        <properties>
            <property name="linesCountBeforeFirstContent" value="0"/>
            <property name="linesCountAfterLastContent" value="0"/>
            <property name="linesCountBetweenDescriptionAndAnnotations" value="1"/>
            <property name="linesCountBetweenAnnotationsGroups" value="1"/>
            <property name="annotationsGroups" type="array">
                <element value="@internal,@deprecated,"/>
                <element value="@link,@see,@uses,"/>
                <element value="@param"/>
                <element value="@return"/>
                <element value="@throws"/>
            </property>
        </properties>
    </rule>

    <!-- Forbid useless annotations -->
    <rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations">
        <properties>
            <property name="forbiddenAnnotations" type="array">
                <element value="@api"/>
                <element value="@author"/>
                <element value="@category"/>
                <element value="@created"/>
                <element value="@package"/>
                <element value="@since"/>
                <element value="@subpackage"/>
                <element value="@version"/>
            </property>
        </properties>
    </rule>

    <!-- Forbid empty comments -->
    <rule ref="SlevomatCodingStandard.Commenting.EmptyComment"/>

    <!-- Report invalid format of inline phpDocs with @var -->
    <rule ref="SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration">
        <properties>
            <property name="allowDocCommentAboveReturn" value="true"/>
        </properties>
    </rule>

    <!-- Report use of the useless @inheritDoc annotation -->
    <rule ref="SlevomatCodingStandard.Commenting.UselessInheritDocComment"/>

    <!-- Require consistent spacing for control structures (add a line break) -->
    <rule ref="SlevomatCodingStandard.ControlStructures.JumpStatementsSpacing">
        <properties>
            <property name="jumpStatements" type="array">
                <element value="break"/>
                <element value="continue"/>
                <element value="return"/>
                <element value="throw"/>
            </property>
        </properties>
    </rule>

    <!-- Forbid fancy yoda conditions -->
    <rule ref="SlevomatCodingStandard.ControlStructures.DisallowYodaComparison"/>

    <!-- Require language constructs without parentheses -->
    <rule ref="SlevomatCodingStandard.ControlStructures.LanguageConstructWithParentheses"/>

    <!-- Require new instances with parentheses -->
    <rule ref="SlevomatCodingStandard.ControlStructures.NewWithParentheses"/>

    <!-- Require usage of null coalesce operator when possible -->
    <rule ref="SlevomatCodingStandard.ControlStructures.RequireNullCoalesceOperator"/>

    <!-- Forbid usage of conditions when a simple return can be used -->
    <rule ref="SlevomatCodingStandard.ControlStructures.UselessIfConditionWithReturn"/>

    <!-- Forbid usage of boolean-only ternary operator usage (e.g. $foo ? true : false) -->
    <rule ref="SlevomatCodingStandard.ControlStructures.UselessTernaryOperator"/>

    <!-- Forbid useless unreachable catch blocks -->
    <rule ref="SlevomatCodingStandard.Exceptions.DeadCatch"/>

    <!-- Require using Throwable instead of Exception -->
    <rule ref="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly"/>

    <!-- Forbid unused variables passed to closures via 'use' -->
    <rule ref="SlevomatCodingStandard.Functions.UnusedInheritedVariablePassedToClosure"/>

    <!-- Require use statements to be alphabetically sorted -->
    <rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
        <properties>
            <property name="psr12Compatible" value="true"/>
            <property name="caseSensitive" value="true"/>
        </properties>
    </rule>

    <!-- Forbid fancy group uses -->
    <rule ref="SlevomatCodingStandard.Namespaces.DisallowGroupUse"/>

    <!-- Forbid multiple use statements on same line -->
    <rule ref="SlevomatCodingStandard.Namespaces.MultipleUsesPerLine"/>

    <!-- Forbid using absolute class name references (except global ones) -->
    <rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly">
        <properties>
            <property name="allowFallbackGlobalConstants" value="false"/>
            <property name="allowFallbackGlobalFunctions" value="true"/>
            <property name="allowFullyQualifiedGlobalClasses" value="false"/>
            <property name="allowFullyQualifiedGlobalConstants" value="false"/>
            <property name="allowFullyQualifiedGlobalFunctions" value="false"/>
            <property name="allowFullyQualifiedNameForCollidingClasses" value="true"/>
            <property name="allowFullyQualifiedNameForCollidingConstants" value="true"/>
            <property name="allowFullyQualifiedNameForCollidingFunctions" value="true"/>
            <property name="allowPartialUses" value="true"/>
            <property name="searchAnnotations" value="true"/>
        </properties>
    </rule>

    <!-- Forbid unused use statements -->
    <rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
        <properties>
            <property name="searchAnnotations" value="true"/>
        </properties>
    </rule>

    <!-- Forbid useless alias for classes, constants and functions -->
    <rule ref="SlevomatCodingStandard.Namespaces.UselessAlias"/>

    <!-- Forbid superfluous leading backslash in use statements -->
    <rule ref="SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash"/>

    <!-- Forbid useless uses of the same namespace -->
    <rule ref="SlevomatCodingStandard.Namespaces.UseFromSameNamespace"/>

    <!-- Require one space after the namespace, and no space before the semicolon -->
    <rule ref="SlevomatCodingStandard.Namespaces.NamespaceDeclaration"/>

    <!-- Require only one namespace in a file -->
    <rule ref="SlevomatCodingStandard.Namespaces.RequireOneNamespaceInFile"/>

    <!-- Forbid weak comparisons -->
    <rule ref="SlevomatCodingStandard.Operators.DisallowEqualOperators"/>

    <!-- Require NOWDOC if string interpolation not used -->
    <rule ref="SlevomatCodingStandard.PHP.RequireNowdoc"/>

    <!-- Forbid 'list(...)' syntax -->
    <rule ref="SlevomatCodingStandard.PHP.ShortList"/>

    <!-- Forbid use of longhand cast operators -->
    <rule ref="SlevomatCodingStandard.PHP.TypeCast"/>

    <!-- Forbid useless parentheses -->
    <rule ref="SlevomatCodingStandard.PHP.UselessParentheses"/>

    <!-- Forbid useless semicolon `;` -->
    <rule ref="SlevomatCodingStandard.PHP.UselessSemicolon"/>

    <!-- There must be no space after the spread operator. -->
    <rule ref="SlevomatCodingStandard.Operators.SpreadOperatorSpacing">
        <properties>
            <property name="spacesCountAfterOperator" value="0"/>
        </properties>
    </rule>

    <!-- Require 0 spaces after the reference '&' operator -->
    <rule ref="SlevomatCodingStandard.PHP.ReferenceSpacing">
        <properties>
            <property name="spacesCountAfterReference" value="0"/>
        </properties>
    </rule>

    <!-- Require presence of declare(strict_types=1) -->
    <rule ref="SlevomatCodingStandard.TypeHints.DeclareStrictTypes">
        <!-- Ignore whitespace requirements for lines occurring before declare(strict_types=1).
             In general, we have two lines before this statement, one before the
             file header comment and one after. The sniff does not count any
             newlines from the comment block. However, in tests, we don't use
             file header comments, so we have only one line before this
             statement. This causes problems, so we just ignore this error. -->
        <exclude name="SlevomatCodingStandard.TypeHints.DeclareStrictTypes.IncorrectWhitespaceBeforeDeclare"/>
        <properties>
            <property name="linesCountAfterDeclare" value="1"/>
            <property name="spacesCountAroundEqualsSign" value="0"/>
        </properties>
    </rule>

    <!-- Require use of short versions of scalar types (i.e. int instead of integer) -->
    <rule ref="SlevomatCodingStandard.TypeHints.LongTypeHints"/>

    <!-- Require the 'null' type hint to be in the last position of annotations -->
    <rule ref="SlevomatCodingStandard.TypeHints.NullTypeHintOnLastPosition"/>

    <!-- Require ? when default value is null -->
    <rule ref="SlevomatCodingStandard.TypeHints.NullableTypeForNullDefaultValue"/>

    <!-- Require one space between typehint and variable, require no space between nullability sign and typehint -->
    <rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHintSpacing"/>

    <!-- Require types to be written as natively if possible;
         require iterable types to specify phpDoc with their content;
         forbid useless/duplicated information in phpDoc -->
    <rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint">
        <properties>
            <property name="enableObjectTypeHint" value="true"/>
        </properties>

        <!-- We don't want to check for this just yet. -->
        <exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint"/>
    </rule>
    <rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint">
        <properties>
            <property name="enableNativeTypeHint" value="false"/>
        </properties>
    </rule>
    <rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint">
        <properties>
            <property name="enableObjectTypeHint" value="true"/>
        </properties>

        <!-- some native type hints cannot be added, because declaring them on pre-existing interfaces leads
             to a major downstream BC break -->
        <exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint"/>
    </rule>

    <rule ref="SlevomatCodingStandard.Commenting.UselessFunctionDocComment"/>

    <!-- Require the return type hint to have one space (i.e. function foo(): ?int) -->
    <rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHintSpacing"/>

    <!-- For arrow functions, require 1 space after the keyword and before/after the arrow -->
    <rule ref="SlevomatCodingStandard.Functions.ArrowFunctionDeclaration"/>

    <!-- Forbid useless @var for constants -->
    <rule ref="SlevomatCodingStandard.TypeHints.UselessConstantTypeHint"/>

    <!-- Disallow use of super-global variables -->
    <rule ref="SlevomatCodingStandard.Variables.DisallowSuperGlobalVariable"/>

    <!-- Clean up unnecessary variables -->
    <rule ref="SlevomatCodingStandard.Variables.UselessVariable"/>
    <rule ref="SlevomatCodingStandard.Variables.UnusedVariable"/>

    <!-- Forbid spaces around square brackets -->
    <rule ref="Squiz.Arrays.ArrayBracketSpacing"/>

    <!-- Forbid class being in a file with different name -->
    <rule ref="Squiz.Classes.ClassFileName"/>

    <!-- Force 'self::' for self-reference, force lower-case self, forbid spaces around '::' -->
    <rule ref="Squiz.Classes.SelfMemberReference"/>

    <!-- Forbid 'AND' and 'OR', require '&&' and '||' -->
    <rule ref="Squiz.Operators.ValidLogicalOperators"/>

    <!-- Forbid global functions -->
    <rule ref="Squiz.Functions.GlobalFunction"/>

    <!-- Forbid 'global' -->
    <rule ref="Squiz.PHP.GlobalKeyword"/>

    <!-- Forbid functions inside functions -->
    <rule ref="Squiz.PHP.InnerFunctions"/>

    <!-- Require PHP function calls in lowercase -->
    <rule ref="Squiz.PHP.LowercasePHPFunctions"/>

    <!-- Forbid dead code -->
    <rule ref="Squiz.PHP.NonExecutableCode"/>

    <!-- Forbid `$this` inside static function -->
    <rule ref="Squiz.Scope.StaticThisUsage"/>

    <!-- Force whitespace before and after concatenation -->
    <rule ref="Squiz.Strings.ConcatenationSpacing">
        <properties>
            <property name="spacing" value="1"/>
            <property name="ignoreNewlines" value="true"/>
        </properties>
    </rule>

    <!-- Forbid blank line after function opening brace -->
    <rule ref="Squiz.WhiteSpace.FunctionOpeningBraceSpace"/>

    <!-- Require 1 line before and after function, except at the top and bottom -->
    <rule ref="Squiz.WhiteSpace.FunctionSpacing">
        <properties>
            <property name="spacing" value="1"/>
            <property name="spacingBeforeFirst" value="0"/>
            <property name="spacingAfterLast" value="0"/>
        </properties>
    </rule>

    <!-- Require space after language constructs -->
    <rule ref="Squiz.WhiteSpace.LanguageConstructSpacing"/>

    <!-- Require space around logical operators -->
    <rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/>

    <rule ref="SlevomatCodingStandard.TypeHints.UnionTypeHintFormat">
        <properties>
            <property name="withSpaces" value="yes"/>
            <property name="nullPosition" value="last"/>
        </properties>
    </rule>

    <!-- Forbid spaces around '->' operator -->
    <rule ref="Squiz.WhiteSpace.ObjectOperatorSpacing">
        <properties>
            <property name="ignoreNewlines" value="true"/>
        </properties>
    </rule>

    <!-- Forbid spaces before semicolon ';' -->
    <rule ref="Squiz.WhiteSpace.SemicolonSpacing"/>

    <!-- Forbid superfluous whitespaces -->
    <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
        <properties>
            <!-- turned on by PSR12 -> turning back off -->
            <property name="ignoreBlankLines" value="false"/>
        </properties>
    </rule>
    <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace.EmptyLines">
        <!-- turned off by PSR12 -> turning back on -->
        <severity>5</severity>
    </rule>

    <!-- Does not allow double quotes for plain strings but does allow them if they contain a variable. -->
    <rule ref="Squiz.Strings.DoubleQuoteUsage.NotRequired"/>

    <!-- Force camelCase variables -->
    <rule ref="Squiz.NamingConventions.ValidVariableName">
        <exclude name="Squiz.NamingConventions.ValidVariableName.PrivateNoUnderscore"/>
    </rule>

    <!-- Checks duplicate spaces anywhere because there aren't sniffs for every part of code to check formatting. -->
    <rule ref="SlevomatCodingStandard.Whitespaces.DuplicateSpaces"/>

</ruleset>
