├── .github └── workflows │ └── testing.yml ├── .gitignore ├── coder_sniffer ├── Drupal │ ├── Sniffs │ │ ├── Arrays │ │ │ ├── ArraySniff.php │ │ │ └── DisallowLongArraySyntaxSniff.php │ │ ├── Attributes │ │ │ └── ValidHookNameSniff.php │ │ ├── CSS │ │ │ ├── ClassDefinitionNameSpacingSniff.php │ │ │ └── ColourDefinitionSniff.php │ │ ├── Classes │ │ │ ├── ClassCreateInstanceSniff.php │ │ │ ├── ClassDeclarationSniff.php │ │ │ ├── ClassFileNameSniff.php │ │ │ ├── FullyQualifiedNamespaceSniff.php │ │ │ ├── InterfaceNameSniff.php │ │ │ ├── PropertyDeclarationSniff.php │ │ │ ├── UnusedUseStatementSniff.php │ │ │ ├── UseGlobalClassSniff.php │ │ │ └── UseLeadingBackslashSniff.php │ │ ├── Commenting │ │ │ ├── ClassCommentSniff.php │ │ │ ├── DataTypeNamespaceSniff.php │ │ │ ├── DeprecatedSniff.php │ │ │ ├── DocCommentAlignmentSniff.php │ │ │ ├── DocCommentLongArraySyntaxSniff.php │ │ │ ├── DocCommentSniff.php │ │ │ ├── DocCommentStarSniff.php │ │ │ ├── FileCommentSniff.php │ │ │ ├── FunctionCommentSniff.php │ │ │ ├── GenderNeutralCommentSniff.php │ │ │ ├── HookCommentSniff.php │ │ │ ├── InlineCommentSniff.php │ │ │ ├── InlineVariableCommentSniff.php │ │ │ ├── PostStatementCommentSniff.php │ │ │ ├── TodoCommentSniff.php │ │ │ └── VariableCommentSniff.php │ │ ├── ControlStructures │ │ │ ├── CaseSemicolonSniff.php │ │ │ ├── ControlSignatureSniff.php │ │ │ ├── ElseIfSniff.php │ │ │ └── InlineControlStructureSniff.php │ │ ├── Files │ │ │ ├── EndFileNewlineSniff.php │ │ │ ├── FileEncodingSniff.php │ │ │ ├── LineLengthSniff.php │ │ │ └── TxtFileLineLengthSniff.php │ │ ├── Formatting │ │ │ ├── MultiLineAssignmentSniff.php │ │ │ ├── MultipleStatementAlignmentSniff.php │ │ │ ├── SpaceInlineIfSniff.php │ │ │ └── SpaceUnaryOperatorSniff.php │ │ ├── Functions │ │ │ ├── DiscouragedFunctionsSniff.php │ │ │ ├── FunctionDeclarationSniff.php │ │ │ └── MultiLineFunctionDeclarationSniff.php │ │ ├── InfoFiles │ │ │ ├── AutoAddedKeysSniff.php │ │ │ ├── ClassFilesSniff.php │ │ │ ├── DependenciesArraySniff.php │ │ │ ├── DuplicateEntrySniff.php │ │ │ └── RequiredSniff.php │ │ ├── Methods │ │ │ └── MethodDeclarationSniff.php │ │ ├── NamingConventions │ │ │ ├── ValidClassNameSniff.php │ │ │ ├── ValidEnumCaseSniff.php │ │ │ ├── ValidFunctionNameSniff.php │ │ │ ├── ValidGlobalSniff.php │ │ │ └── ValidVariableNameSniff.php │ │ ├── Scope │ │ │ └── MethodScopeSniff.php │ │ ├── Semantics │ │ │ ├── ConstantNameSniff.php │ │ │ ├── EmptyInstallSniff.php │ │ │ ├── FunctionAliasSniff.php │ │ │ ├── FunctionCall.php │ │ │ ├── FunctionDefinition.php │ │ │ ├── FunctionTSniff.php │ │ │ ├── FunctionTriggerErrorSniff.php │ │ │ ├── FunctionWatchdogSniff.php │ │ │ ├── InstallHooksSniff.php │ │ │ ├── LStringTranslatableSniff.php │ │ │ ├── PregSecuritySniff.php │ │ │ ├── RemoteAddressSniff.php │ │ │ ├── TInHookMenuSniff.php │ │ │ ├── TInHookSchemaSniff.php │ │ │ └── UnsilencedDeprecationSniff.php │ │ ├── Strings │ │ │ └── UnnecessaryStringConcatSniff.php │ │ └── WhiteSpace │ │ │ ├── CloseBracketSpacingSniff.php │ │ │ ├── CommaSniff.php │ │ │ ├── EmptyLinesSniff.php │ │ │ ├── NamespaceSniff.php │ │ │ ├── ObjectOperatorIndentSniff.php │ │ │ ├── ObjectOperatorSpacingSniff.php │ │ │ ├── OpenBracketSpacingSniff.php │ │ │ ├── OpenTagNewlineSniff.php │ │ │ ├── ScopeClosingBraceSniff.php │ │ │ └── ScopeIndentSniff.php │ └── ruleset.xml └── DrupalPractice │ ├── Project.php │ ├── Sniffs │ ├── CodeAnalysis │ │ └── VariableAnalysisSniff.php │ ├── Commenting │ │ ├── AuthorTagSniff.php │ │ ├── CommentEmptyLineSniff.php │ │ └── ExpectedExceptionSniff.php │ ├── Constants │ │ ├── GlobalConstantSniff.php │ │ └── GlobalDefineSniff.php │ ├── FunctionCalls │ │ ├── CheckPlainSniff.php │ │ ├── CurlSslVerifierSniff.php │ │ ├── DbQuerySniff.php │ │ ├── DbSelectBracesSniff.php │ │ ├── DefaultValueSanitizeSniff.php │ │ ├── FormErrorTSniff.php │ │ ├── InsecureUnserializeSniff.php │ │ ├── LCheckPlainSniff.php │ │ ├── MessageTSniff.php │ │ ├── TCheckPlainSniff.php │ │ ├── ThemeSniff.php │ │ └── VariableSetSanitizeSniff.php │ ├── FunctionDefinitions │ │ ├── AccessHookMenuSniff.php │ │ ├── FormAlterDocSniff.php │ │ ├── HookInitCssSniff.php │ │ └── InstallTSniff.php │ ├── General │ │ ├── AccessAdminPagesSniff.php │ │ ├── ClassNameSniff.php │ │ ├── DescriptionTSniff.php │ │ ├── ExceptionTSniff.php │ │ ├── FormStateInputSniff.php │ │ ├── LanguageNoneSniff.php │ │ ├── OptionsTSniff.php │ │ └── VariableNameSniff.php │ ├── InfoFiles │ │ ├── CoreVersionRequirementSniff.php │ │ ├── DescriptionSniff.php │ │ └── NamespacedDependencySniff.php │ ├── Objects │ │ ├── GlobalClassSniff.php │ │ ├── GlobalDrupalSniff.php │ │ ├── GlobalFunctionSniff.php │ │ ├── StrictSchemaDisabledSniff.php │ │ └── UnusedPrivateMethodSniff.php │ ├── Variables │ │ └── GetRequestDataSniff.php │ └── Yaml │ │ └── RoutingAccessSniff.php │ └── ruleset.xml ├── composer.json └── phpstan.neon /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request] 3 | jobs: 4 | testing: 5 | name: PHP ${{ matrix.php-versions }} 6 | runs-on: ubuntu-latest 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] 11 | extra-tests: ['0'] 12 | # We only need to run PHPStan and Drupal core regression tests once on 13 | # the latest PHP version. 14 | include: 15 | - php-versions: '8.4' 16 | extra-tests: '1' 17 | steps: 18 | - name: Checkout Coder 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup PHP, with composer and extensions 22 | uses: shivammathur/setup-php@v2 23 | with: 24 | php-version: ${{ matrix.php-versions }} 25 | extensions: mbstring 26 | # Disable Xdebug for better performance. 27 | coverage: none 28 | 29 | - name: Get composer cache directory 30 | id: composercache 31 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 32 | 33 | - name: Cache composer dependencies 34 | uses: actions/cache@v4 35 | with: 36 | path: ${{ steps.composercache.outputs.dir }} 37 | # Use composer.json for key, if composer.lock is not committed. 38 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 39 | restore-keys: ${{ runner.os }}-composer- 40 | 41 | - name: Install Composer dependencies 42 | # Running composer install without a lock file will also update cached 43 | # dependencies in vendor. 44 | run: composer install --no-progress --prefer-dist --optimize-autoloader 45 | 46 | - name: Run PHPUnit 47 | run: ./vendor/bin/phpunit 48 | 49 | - name: Run PHPCS 50 | run: ./vendor/bin/phpcs 51 | 52 | - name: Check custom standard autoloading 53 | # Ensure that a custom standard can be invoked and the auto-loading of 54 | # abstract classes works. 55 | # Ensure that the DrupalPractice standard can be invoked standalone and the 56 | # auto-loading of abstract classes works. 57 | run: | 58 | ./vendor/bin/phpcs -p --standard=tests/Drupal/phpcs-ruleset.xml tests/Drupal/good/ --ignore=tests/Drupal/good/GoodUnitTest.php 59 | ./vendor/bin/phpcs -p --standard=coder_sniffer/DrupalPractice tests/DrupalPractice/good/ --ignore=tests/DrupalPractice/good/GoodUnitTest.php 60 | 61 | - name: Run PHPStan 62 | if: ${{ matrix.extra-tests == '1' }} 63 | run: ./vendor/bin/phpstan analyse 64 | 65 | - name: Run Cspell 66 | if: ${{ matrix.extra-tests == '1' }} 67 | uses: streetsidesoftware/cspell-action@v7 68 | with: 69 | incremental_files_only: false 70 | 71 | - name: Checkout Drupal core 72 | if: ${{ matrix.extra-tests == '1' }} 73 | uses: actions/checkout@v3 74 | with: 75 | repository: drupal/drupal 76 | ref: "11.x" 77 | path: drupal 78 | 79 | - name: Run PHPCS on Drupal core for regressions 80 | if: ${{ matrix.extra-tests == '1' }} 81 | # In case Drupal core files have known problems that should be 82 | # ignored temporarily, add them with the --ignore option. 83 | # @todo Remove ignore option once Coder 8.3.27 is released and Drupal 84 | # core is updated to that version. 85 | run: | 86 | cd drupal/core 87 | ../../vendor/bin/phpcs -p -s --exclude=Drupal.Commenting.FunctionComment --ignore=lib/Drupal/Core/Entity/EntityType.php,lib/Drupal/Core/Recipe/RecipeInputFormTrait.php,lib/Drupal/Core/Form/FormState.php,modules/migrate/src/Plugin/Migration.php,modules/views/src/ViewExecutable.php,modules/views/src/Plugin/views/style/StylePluginBase.php,core/lib/Drupal/Core/FileTransfer/FTP.php,core/lib/Drupal/Core/FileTransfer/SSH.php,modules/system/tests/modules/theme_test/src/EventSubscriber/ThemeTestSubscriber.php,modules/views/src/Plugin/views/pager/PagerPluginBase.php,lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /composer.lock 3 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Arrays/DisallowLongArraySyntaxSniff.php: -------------------------------------------------------------------------------- 1 | numTokens + 1); 42 | } 43 | 44 | parent::process($phpcsFile, $stackPtr); 45 | 46 | }//end process() 47 | 48 | 49 | }//end class 50 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Attributes/ValidHookNameSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_ATTRIBUTE]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the 42 | * token was found. 43 | * @param int $stackPtr The position in the PHP_CodeSniffer 44 | * file's token stack where the token 45 | * was found. 46 | * 47 | * @return void|int Optionally returns a stack pointer. The sniff will not be 48 | * called again on the current file until the returned stack 49 | * pointer is reached. Return $phpcsFile->numTokens + 1 to skip 50 | * the rest of the file. 51 | */ 52 | public function process(File $phpcsFile, $stackPtr) 53 | { 54 | $tokens = $phpcsFile->getTokens(); 55 | $attributeName = $phpcsFile->findNext(T_STRING, ($stackPtr + 1)); 56 | if ($attributeName !== false 57 | && $tokens[$attributeName]['content'] === 'Hook' 58 | ) { 59 | $hookName = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($attributeName + 2)); 60 | if ($hookName !== false) { 61 | // Remove outer quotes. 62 | $hookNameValue = trim($tokens[$hookName]['content'], '"\''); 63 | 64 | if (strpos($hookNameValue, 'hook_') === 0 && $hookNameValue !== 'hook_') { 65 | // Remove "hook_" prefix. 66 | $hookNameValueFixed = substr($hookNameValue, 5); 67 | $message = sprintf("The hook name should not start with 'hook_', expected '%s' but found '%s'", $hookNameValueFixed, $hookNameValue); 68 | 69 | $fix = $phpcsFile->addFixableWarning($message, $hookName, 'HookPrefix'); 70 | if ($fix === true) { 71 | // Return outer quotes. 72 | $hookNameValueFixed = str_replace($hookNameValue, $hookNameValueFixed, $tokens[$hookName]['content']); 73 | $phpcsFile->fixer->replaceToken($hookName, $hookNameValueFixed); 74 | } 75 | } 76 | } 77 | }//end if 78 | 79 | }//end process() 80 | 81 | 82 | }//end class 83 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/CSS/ClassDefinitionNameSpacingSniff.php: -------------------------------------------------------------------------------- 1 | 39 | */ 40 | public function register() 41 | { 42 | return [T_OPEN_TAG]; 43 | 44 | }//end register() 45 | 46 | 47 | /** 48 | * Processes the tokens that this sniff is interested in. 49 | * 50 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where the token was found. 51 | * @param int $stackPtr The position in the stack where 52 | * the token was found. 53 | * 54 | * @return int 55 | */ 56 | public function process(File $phpcsFile, $stackPtr) 57 | { 58 | // This sniff is deprecated and disabled - do nothing. 59 | return ($phpcsFile->numTokens + 1); 60 | 61 | }//end process() 62 | 63 | 64 | /** 65 | * {@inheritdoc} 66 | * 67 | * @return string 68 | */ 69 | public function getDeprecationVersion(): string 70 | { 71 | return 'Coder 8.3.30'; 72 | 73 | }//end getDeprecationVersion() 74 | 75 | 76 | /** 77 | * {@inheritdoc} 78 | * 79 | * @return string 80 | */ 81 | public function getRemovalVersion(): string 82 | { 83 | return 'Coder 9.0.0'; 84 | 85 | }//end getRemovalVersion() 86 | 87 | 88 | /** 89 | * {@inheritdoc} 90 | * 91 | * @return string 92 | */ 93 | public function getDeprecationMessage(): string 94 | { 95 | return 'Checking CSS coding standards is not supported anymore, use Stylelint instead with the Drupal core .stylelintrc.json configuration file. https://git.drupalcode.org/project/drupal/-/blob/11.x/core/.stylelintrc.json'; 96 | 97 | }//end getDeprecationMessage() 98 | 99 | 100 | }//end class 101 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/CSS/ColourDefinitionSniff.php: -------------------------------------------------------------------------------- 1 | 38 | */ 39 | public function register() 40 | { 41 | return [T_OPEN_TAG]; 42 | 43 | }//end register() 44 | 45 | 46 | /** 47 | * Processes the tokens that this sniff is interested in. 48 | * 49 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where the token was found. 50 | * @param int $stackPtr The position in the stack where 51 | * the token was found. 52 | * 53 | * @return int 54 | */ 55 | public function process(File $phpcsFile, $stackPtr) 56 | { 57 | // This sniff is deprecated and disabled - do nothing. 58 | return ($phpcsFile->numTokens + 1); 59 | 60 | }//end process() 61 | 62 | 63 | /** 64 | * {@inheritdoc} 65 | * 66 | * @return string 67 | */ 68 | public function getDeprecationVersion(): string 69 | { 70 | return 'Coder 8.3.30'; 71 | 72 | }//end getDeprecationVersion() 73 | 74 | 75 | /** 76 | * {@inheritdoc} 77 | * 78 | * @return string 79 | */ 80 | public function getRemovalVersion(): string 81 | { 82 | return 'Coder 9.0.0'; 83 | 84 | }//end getRemovalVersion() 85 | 86 | 87 | /** 88 | * {@inheritdoc} 89 | * 90 | * @return string 91 | */ 92 | public function getDeprecationMessage(): string 93 | { 94 | return 'Checking CSS coding standards is not supported anymore, use Stylelint instead with the Drupal core .stylelintrc.json configuration file. https://git.drupalcode.org/project/drupal/-/blob/11.x/core/.stylelintrc.json'; 95 | 96 | }//end getDeprecationMessage() 97 | 98 | 99 | }//end class 100 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Classes/ClassFileNameSniff.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | public function register() 26 | { 27 | return [ 28 | T_CLASS, 29 | T_INTERFACE, 30 | T_TRAIT, 31 | T_ENUM, 32 | ]; 33 | 34 | }//end register() 35 | 36 | 37 | /** 38 | * Processes this test, when one of its tokens is encountered. 39 | * 40 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 41 | * @param int $stackPtr The position of the current token in 42 | * the stack passed in $tokens. 43 | * 44 | * @return int 45 | */ 46 | public function process(File $phpcsFile, $stackPtr) 47 | { 48 | // This check only applies to Drupal 8+, in Drupal 7 we can have classes 49 | // in all kinds of files. 50 | if (Project::getCoreVersion($phpcsFile) < 8) { 51 | return ($phpcsFile->numTokens + 1); 52 | } 53 | 54 | $fullPath = basename($phpcsFile->getFilename()); 55 | $fileName = substr($fullPath, 0, strrpos($fullPath, '.')); 56 | if ($fileName === '') { 57 | // No filename probably means STDIN, so we can't do this check. 58 | return ($phpcsFile->numTokens + 1); 59 | } 60 | 61 | // If the file is not a php file, we do not care about how it looks, 62 | // since we care about psr-4. 63 | $extension = pathinfo($fullPath, PATHINFO_EXTENSION); 64 | if ($extension !== 'php') { 65 | return ($phpcsFile->numTokens + 1); 66 | } 67 | 68 | $tokens = $phpcsFile->getTokens(); 69 | $decName = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 70 | 71 | if ($tokens[$decName]['code'] === T_STRING 72 | && $tokens[$decName]['content'] !== $fileName 73 | ) { 74 | $error = '%s name doesn\'t match filename; expected "%s %s"'; 75 | $data = [ 76 | ucfirst($tokens[$stackPtr]['content']), 77 | $tokens[$stackPtr]['content'], 78 | $fileName, 79 | ]; 80 | $phpcsFile->addError($error, $stackPtr, 'NoMatch', $data); 81 | } 82 | 83 | // Only check the first class in a file, we don't care about helper 84 | // classes in tests for example. 85 | return ($phpcsFile->numTokens + 1); 86 | 87 | }//end process() 88 | 89 | 90 | }//end class 91 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Classes/InterfaceNameSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_INTERFACE]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the current token in 43 | * the stack passed in $tokens. 44 | * 45 | * @return void 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | $tokens = $phpcsFile->getTokens(); 50 | $namePtr = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 51 | $name = $tokens[$namePtr]['content']; 52 | if (substr($name, -9) !== 'Interface') { 53 | $warn = 'Interface names should always have the suffix "Interface"'; 54 | $phpcsFile->addWarning($warn, $namePtr, 'InterfaceSuffix'); 55 | } 56 | 57 | }//end process() 58 | 59 | 60 | }//end class 61 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Classes/PropertyDeclarationSniff.php: -------------------------------------------------------------------------------- 1 | 34 | */ 35 | public function register() 36 | { 37 | return [T_VAR]; 38 | 39 | }//end register() 40 | 41 | 42 | /** 43 | * Processes this test, when one of its tokens is encountered. 44 | * 45 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 46 | * @param int $stackPtr The position of the current token in 47 | * the stack passed in $tokens. 48 | * 49 | * @return void 50 | */ 51 | public function process(File $phpcsFile, $stackPtr) 52 | { 53 | $error = 'The var keyword must not be used to declare a property'; 54 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'VarUsed'); 55 | if ($fix === true) { 56 | $phpcsFile->fixer->replaceToken($stackPtr, 'public'); 57 | } 58 | 59 | }//end process() 60 | 61 | 62 | }//end class 63 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Classes/UseLeadingBackslashSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [T_USE]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the current token in 44 | * the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $tokens = $phpcsFile->getTokens(); 51 | 52 | // Only check use statements in the global scope. 53 | if (empty($tokens[$stackPtr]['conditions']) === false) { 54 | return; 55 | } 56 | 57 | $startPtr = $phpcsFile->findNext( 58 | Tokens::$emptyTokens, 59 | ($stackPtr + 1), 60 | null, 61 | true 62 | ); 63 | 64 | if ($startPtr !== false && $tokens[$startPtr]['code'] === T_NS_SEPARATOR) { 65 | $error = 'When importing a class with "use", do not include a leading \\'; 66 | $fix = $phpcsFile->addFixableError($error, $startPtr, 'SeparatorStart'); 67 | if ($fix === true) { 68 | $phpcsFile->fixer->replaceToken($startPtr, ''); 69 | } 70 | } 71 | 72 | }//end process() 73 | 74 | 75 | }//end class 76 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Commenting/DocCommentLongArraySyntaxSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_DOC_COMMENT_OPEN_TAG]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the current token 43 | * in the stack passed in $tokens. 44 | * 45 | * @return void 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | $tokens = $phpcsFile->getTokens(); 50 | $commentEnd = $phpcsFile->findNext(T_DOC_COMMENT_CLOSE_TAG, ($stackPtr + 1)); 51 | 52 | // Look for @code annotations. 53 | $codeEnd = $stackPtr; 54 | do { 55 | $codeStart = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($codeEnd + 1), $commentEnd, false, '@code'); 56 | if ($codeStart !== false) { 57 | $codeEnd = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($codeStart + 1), $commentEnd, false, '@endcode'); 58 | // If the code block never ends then simply ignore this 59 | // docblock, it is probably malformed. 60 | if ($codeEnd === false) { 61 | break; 62 | } else { 63 | // Check for long array syntax use inside this @code annotation. 64 | for ($i = ($codeStart + 1); $i < $codeEnd; $i++) { 65 | if (preg_match('/\barray\s*\(/', $tokens[$i]['content']) === 1) { 66 | $error = 'Long array syntax must not be used in doc comment code annotations'; 67 | $phpcsFile->addError($error, $i, 'DocLongArray'); 68 | } 69 | } 70 | } 71 | } 72 | } while ($codeStart !== false); 73 | 74 | }//end process() 75 | 76 | 77 | }//end class 78 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Commenting/DocCommentStarSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_DOC_COMMENT_OPEN_TAG]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the current token 43 | * in the stack passed in $tokens. 44 | * 45 | * @return void 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | $tokens = $phpcsFile->getTokens(); 50 | 51 | $lastLineChecked = $tokens[$stackPtr]['line']; 52 | for ($i = ($stackPtr + 1); $i < ($tokens[$stackPtr]['comment_closer'] - 1); $i++) { 53 | // We are only interested in the beginning of the line. 54 | if ($tokens[$i]['line'] === $lastLineChecked) { 55 | continue; 56 | } 57 | 58 | // The first token on the line must be a whitespace followed by a star. 59 | if ($tokens[$i]['code'] === T_DOC_COMMENT_WHITESPACE) { 60 | if ($tokens[($i + 1)]['code'] !== T_DOC_COMMENT_STAR) { 61 | $error = 'Doc comment star missing'; 62 | $fix = $phpcsFile->addFixableError($error, $i, 'StarMissing'); 63 | if ($fix === true) { 64 | if (strpos($tokens[$i]['content'], $phpcsFile->eolChar) !== false) { 65 | $phpcsFile->fixer->replaceToken($i, str_repeat(' ', $tokens[$stackPtr]['column'])."* \n"); 66 | } else { 67 | $phpcsFile->fixer->replaceToken($i, str_repeat(' ', $tokens[$stackPtr]['column']).'* '); 68 | } 69 | 70 | // Ordering of lines might have changed - stop here. The 71 | // fixer will restart the sniff if there are remaining fixes. 72 | return; 73 | } 74 | } 75 | } else if ($tokens[$i]['code'] !== T_DOC_COMMENT_STAR) { 76 | $error = 'Doc comment star missing'; 77 | $fix = $phpcsFile->addFixableError($error, $i, 'StarMissing'); 78 | if ($fix === true) { 79 | $phpcsFile->fixer->addContentBefore($i, str_repeat(' ', $tokens[$stackPtr]['column']).'* '); 80 | } 81 | }//end if 82 | 83 | $lastLineChecked = $tokens[$i]['line']; 84 | }//end for 85 | 86 | }//end process() 87 | 88 | 89 | }//end class 90 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Commenting/GenderNeutralCommentSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [ 34 | T_COMMENT, 35 | T_DOC_COMMENT_STRING, 36 | ]; 37 | 38 | }//end register() 39 | 40 | 41 | /** 42 | * Processes this test, when one of its tokens is encountered. 43 | * 44 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 45 | * @param int $stackPtr The position of the current token 46 | * in the stack passed in $tokens. 47 | * 48 | * @return void 49 | */ 50 | public function process(File $phpcsFile, $stackPtr) 51 | { 52 | $tokens = $phpcsFile->getTokens(); 53 | if ((bool) preg_match('/(^|\W)(he|her|hers|him|his|she)($|\W)/i', $tokens[$stackPtr]['content']) === true) { 54 | $phpcsFile->addError('Unnecessarily gendered language in a comment', $stackPtr, 'GenderNeutral'); 55 | } 56 | 57 | }//end process() 58 | 59 | 60 | }//end class 61 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Commenting/PostStatementCommentSniff.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function register() 34 | { 35 | return [T_COMMENT]; 36 | 37 | }//end register() 38 | 39 | 40 | /** 41 | * Processes this sniff, when one of its tokens is encountered. 42 | * 43 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 44 | * @param int $stackPtr The position of the current token in the 45 | * stack passed in $tokens. 46 | * 47 | * @return void 48 | */ 49 | public function process(File $phpcsFile, $stackPtr) 50 | { 51 | $tokens = $phpcsFile->getTokens(); 52 | 53 | if (substr($tokens[$stackPtr]['content'], 0, 2) !== '//') { 54 | return; 55 | } 56 | 57 | $commentLine = $tokens[$stackPtr]['line']; 58 | $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 59 | 60 | if ($tokens[$lastContent]['line'] !== $commentLine) { 61 | return; 62 | } 63 | 64 | if ($tokens[$lastContent]['code'] === T_CLOSE_CURLY_BRACKET) { 65 | return; 66 | } 67 | 68 | $error = 'Comments may not appear after statements'; 69 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found'); 70 | if ($fix === true) { 71 | if ($tokens[$lastContent]['code'] === T_OPEN_TAG) { 72 | $phpcsFile->fixer->addNewlineBefore($stackPtr); 73 | return; 74 | } 75 | 76 | $lineStart = $stackPtr; 77 | while ($tokens[$lineStart]['line'] === $tokens[$stackPtr]['line'] 78 | && $tokens[$lineStart]['code'] !== T_OPEN_TAG 79 | ) { 80 | $lineStart--; 81 | } 82 | 83 | $phpcsFile->fixer->beginChangeset(); 84 | $phpcsFile->fixer->addContent($lineStart, $tokens[$stackPtr]['content']); 85 | $phpcsFile->fixer->replaceToken($stackPtr, $phpcsFile->eolChar); 86 | $phpcsFile->fixer->endChangeset(); 87 | } 88 | 89 | }//end process() 90 | 91 | 92 | }//end class 93 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/ControlStructures/CaseSemicolonSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_CASE]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the current token in the 43 | * stack passed in $tokens. 44 | * 45 | * @return void 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | $tokens = $phpcsFile->getTokens(); 50 | if (isset($tokens[$stackPtr]['scope_opener']) === true 51 | && $tokens[$tokens[$stackPtr]['scope_opener']]['code'] === T_SEMICOLON 52 | ) { 53 | $error = 'A colon ":" must be used to open a case statement, found ";"'; 54 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SemicolonNotAllowed'); 55 | if ($fix === true) { 56 | $phpcsFile->fixer->replaceToken($tokens[$stackPtr]['scope_opener'], ':'); 57 | } 58 | } 59 | 60 | }//end process() 61 | 62 | 63 | }//end class 64 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/ControlStructures/ElseIfSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_ELSE]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the current token in the 43 | * stack passed in $tokens. 44 | * 45 | * @return void 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | 50 | $tokens = $phpcsFile->getTokens(); 51 | 52 | $nextNonWhiteSpace = $phpcsFile->findNext( 53 | T_WHITESPACE, 54 | ($stackPtr + 1), 55 | null, 56 | true, 57 | null, 58 | true 59 | ); 60 | 61 | if ($tokens[$nextNonWhiteSpace]['code'] === T_IF) { 62 | $fix = $phpcsFile->addFixableError('Use "elseif" in place of "else if"', $nextNonWhiteSpace, 'ElseIfDeclaration'); 63 | if ($fix === true) { 64 | $phpcsFile->fixer->beginChangeset(); 65 | $phpcsFile->fixer->replaceToken($stackPtr, 'elseif'); 66 | for ($i = ($stackPtr + 1); $i < $nextNonWhiteSpace; $i++) { 67 | if ($tokens[$i]['code'] === T_WHITESPACE) { 68 | $phpcsFile->fixer->replaceToken($i, ''); 69 | } 70 | } 71 | 72 | $phpcsFile->fixer->replaceToken($nextNonWhiteSpace, ''); 73 | $phpcsFile->fixer->endChangeset(); 74 | } 75 | } 76 | 77 | }//end process() 78 | 79 | 80 | }//end class 81 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/ControlStructures/InlineControlStructureSniff.php: -------------------------------------------------------------------------------- 1 | getTokens(); 43 | 44 | // Check for the alternate syntax for control structures with colons (:). 45 | if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) { 46 | $start = $tokens[$stackPtr]['parenthesis_closer']; 47 | } else { 48 | $start = $stackPtr; 49 | } 50 | 51 | $scopeOpener = $phpcsFile->findNext(T_WHITESPACE, ($start + 1), null, true); 52 | if ($tokens[$scopeOpener]['code'] === T_COLON) { 53 | return; 54 | } 55 | 56 | parent::process($phpcsFile, $stackPtr); 57 | 58 | }//end process() 59 | 60 | 61 | }//end class 62 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Files/FileEncodingSniff.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | public $allowedEncodings = ['UTF-8']; 35 | 36 | 37 | /** 38 | * Returns an array of tokens this test wants to listen for. 39 | * 40 | * @return array 41 | */ 42 | public function register() 43 | { 44 | return [ 45 | T_INLINE_HTML, 46 | T_OPEN_TAG, 47 | ]; 48 | 49 | }//end register() 50 | 51 | 52 | /** 53 | * Processes this sniff, when one of its tokens is encountered. 54 | * 55 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 56 | * @param int $stackPtr The position of the current token in 57 | * the stack passed in $tokens. 58 | * 59 | * @return int|void 60 | */ 61 | public function process(File $phpcsFile, $stackPtr) 62 | { 63 | // Not all PHP installs have the multi byte extension - nothing we can do. 64 | if (function_exists('mb_check_encoding') === false) { 65 | return $phpcsFile->numTokens; 66 | } 67 | 68 | $fileContent = $phpcsFile->getTokensAsString(0, $phpcsFile->numTokens); 69 | 70 | $validEncodingFound = false; 71 | foreach ($this->allowedEncodings as $encoding) { 72 | if (mb_check_encoding($fileContent, $encoding) === true) { 73 | $validEncodingFound = true; 74 | } 75 | } 76 | 77 | if ($validEncodingFound === false) { 78 | $warning = 'File encoding is invalid, expected %s'; 79 | $data = [implode(' or ', $this->allowedEncodings)]; 80 | $phpcsFile->addWarning($warning, $stackPtr, 'InvalidEncoding', $data); 81 | } 82 | 83 | return $phpcsFile->numTokens; 84 | 85 | }//end process() 86 | 87 | 88 | }//end class 89 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Files/TxtFileLineLengthSniff.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | public function register() 35 | { 36 | return [T_INLINE_HTML]; 37 | 38 | }//end register() 39 | 40 | 41 | /** 42 | * Processes this test, when one of its tokens is encountered. 43 | * 44 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 45 | * @param int $stackPtr The position of the current token in the 46 | * stack passed in $tokens. 47 | * 48 | * @return void 49 | */ 50 | public function process(File $phpcsFile, $stackPtr) 51 | { 52 | $fileExtension = strtolower(substr($phpcsFile->getFilename(), -3)); 53 | if ($fileExtension === 'txt' || $fileExtension === '.md') { 54 | $tokens = $phpcsFile->getTokens(); 55 | 56 | $content = rtrim($tokens[$stackPtr]['content']); 57 | $lineLength = mb_strlen($content, 'UTF-8'); 58 | if ($lineLength > 80) { 59 | // Often text files contain long URLs that need to be preceded 60 | // with certain textual elements that are significant for 61 | // preserving the formatting of the document - e.g. a long link 62 | // in a bulleted list. If we find that the line does not contain 63 | // any spaces after the 40th character we'll allow it. 64 | if (preg_match('/\s+/', mb_substr($content, 40)) === 0) { 65 | return; 66 | } 67 | 68 | // Lines without spaces are allowed to be longer. 69 | // Markdown allowed to be longer for lines 70 | // - without spaces 71 | // - starting with # 72 | // - starting with | (tables) 73 | // - containing a link. 74 | if (preg_match('/^([^ ]+$|#|\||.*\[.+\]\(.+\))/', $content) === 0) { 75 | $data = [ 76 | 80, 77 | $lineLength, 78 | ]; 79 | $warning = 'Line exceeds %s characters; contains %s characters'; 80 | $phpcsFile->addWarning($warning, $stackPtr, 'TooLong', $data); 81 | } 82 | }//end if 83 | }//end if 84 | 85 | }//end process() 86 | 87 | 88 | }//end class 89 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Formatting/MultiLineAssignmentSniff.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function register() 34 | { 35 | return [T_EQUAL]; 36 | 37 | }//end register() 38 | 39 | 40 | /** 41 | * Processes this test, when one of its tokens is encountered. 42 | * 43 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 44 | * @param int $stackPtr The position of the current token 45 | * in the stack passed in $tokens. 46 | * 47 | * @return void 48 | */ 49 | public function process(File $phpcsFile, $stackPtr) 50 | { 51 | $tokens = $phpcsFile->getTokens(); 52 | 53 | // Equal sign can't be the last thing on the line. 54 | $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 55 | if ($next === false) { 56 | // Bad assignment. 57 | return; 58 | } 59 | 60 | // Make sure it is the first thing on the line, otherwise we ignore it. 61 | $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 62 | if ($prev === false) { 63 | // Bad assignment. 64 | return; 65 | } 66 | 67 | if ($tokens[$prev]['line'] === $tokens[$stackPtr]['line']) { 68 | return; 69 | } 70 | 71 | // Find the required indent based on the ident of the previous line. 72 | $assignmentIndent = 0; 73 | $prevLine = $tokens[$prev]['line']; 74 | for ($i = ($prev - 1); $i >= 0; $i--) { 75 | if ($tokens[$i]['line'] !== $prevLine) { 76 | $i++; 77 | break; 78 | } 79 | } 80 | 81 | if ($tokens[$i]['code'] === T_WHITESPACE) { 82 | $assignmentIndent = strlen($tokens[$i]['content']); 83 | } 84 | 85 | // Find the actual indent. 86 | $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1)); 87 | 88 | $expectedIndent = ($assignmentIndent + 2); 89 | $foundIndent = strlen($tokens[$prev]['content']); 90 | if ($foundIndent !== $expectedIndent) { 91 | $error = "Multi-line assignment not indented correctly; expected $expectedIndent spaces but found $foundIndent"; 92 | $phpcsFile->addError($error, $stackPtr, 'MultiLineAssignmentIndent'); 93 | } 94 | 95 | }//end process() 96 | 97 | 98 | }//end class 99 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Formatting/SpaceInlineIfSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_INLINE_ELSE]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the current token in 43 | * the stack passed in $tokens. 44 | * 45 | * @return void 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | $tokens = $phpcsFile->getTokens(); 50 | 51 | // Handle the short ternary operator (?:) introduced in PHP 5.3. 52 | $previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 53 | if ($tokens[$previous]['code'] === T_INLINE_THEN) { 54 | if ($previous !== ($stackPtr - 1)) { 55 | $error = 'There must be no space between ? and :'; 56 | $phpcsFile->addError($error, $stackPtr, 'SpaceInlineElse'); 57 | } 58 | }//end if 59 | 60 | }//end process() 61 | 62 | 63 | }//end class 64 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Functions/DiscouragedFunctionsSniff.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | public $forbiddenFunctions = [ 35 | // Devel module debugging functions. 36 | 'dargs' => null, 37 | 'dcp' => null, 38 | 'dd' => null, 39 | 'ddebug_backtrace' => null, 40 | 'ddm' => null, 41 | 'dfb' => null, 42 | 'dfbt' => null, 43 | 'dpm' => null, 44 | 'dpq' => null, 45 | 'dpr' => null, 46 | 'dprint_r' => null, 47 | 'drupal_debug' => null, 48 | 'dsm' => null, 49 | 'dvm' => null, 50 | 'dvr' => null, 51 | 'kdevel_print_object' => null, 52 | 'kint' => null, 53 | 'ksm' => null, 54 | 'kpr' => null, 55 | 'kprint_r' => null, 56 | 'sdpm' => null, 57 | // Functions which are not available on all 58 | // PHP builds. 59 | 'fnmatch' => null, 60 | // Functions which are a security risk. 61 | 'eval' => null, 62 | // cspell:enable 63 | ]; 64 | 65 | /** 66 | * If true, an error will be thrown; otherwise a warning. 67 | * 68 | * @var boolean 69 | */ 70 | public $error = false; 71 | 72 | }//end class 73 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Functions/FunctionDeclarationSniff.php: -------------------------------------------------------------------------------- 1 | 34 | */ 35 | public function register() 36 | { 37 | return [T_FUNCTION]; 38 | 39 | }//end register() 40 | 41 | 42 | /** 43 | * Processes this test, when one of its tokens is encountered. 44 | * 45 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 46 | * @param int $stackPtr The position of the current token 47 | * in the stack passed in $tokens. 48 | * 49 | * @return void 50 | */ 51 | public function process(File $phpcsFile, $stackPtr) 52 | { 53 | $tokens = $phpcsFile->getTokens(); 54 | 55 | if ($tokens[($stackPtr + 1)]['content'] !== ' ') { 56 | $error = 'Expected exactly one space after the function keyword'; 57 | $phpcsFile->addError($error, ($stackPtr + 1), 'SpaceAfter'); 58 | } 59 | 60 | if (isset($tokens[($stackPtr + 3)]) === true 61 | && $tokens[($stackPtr + 3)]['code'] === T_WHITESPACE 62 | ) { 63 | $error = 'Space before opening parenthesis of function definition prohibited'; 64 | $phpcsFile->addError($error, ($stackPtr + 3), 'SpaceBeforeParenthesis'); 65 | } 66 | 67 | }//end process() 68 | 69 | 70 | }//end class 71 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/InfoFiles/AutoAddedKeysSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [T_INLINE_HTML]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the current token in the 44 | * stack passed in $tokens. 45 | * 46 | * @return int 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | // Only run this sniff once per info file. 51 | if (preg_match('/\.info$/', $phpcsFile->getFilename()) === 1) { 52 | // Drupal 7 style info file. 53 | $contents = file_get_contents($phpcsFile->getFilename()); 54 | $info = ClassFilesSniff::drupalParseInfoFormat($contents); 55 | } else if (preg_match('/\.info\.yml$/', $phpcsFile->getFilename()) === 1) { 56 | // Drupal 8 style info.yml file. 57 | $contents = file_get_contents($phpcsFile->getFilename()); 58 | try { 59 | $info = \Symfony\Component\Yaml\Yaml::parse($contents); 60 | } catch (\Symfony\Component\Yaml\Exception\ParseException $e) { 61 | // If the YAML is invalid we ignore this file. 62 | return ($phpcsFile->numTokens + 1); 63 | } 64 | } else { 65 | return ($phpcsFile->numTokens + 1); 66 | } 67 | 68 | if (isset($info['project']) === true) { 69 | $warning = 'Remove "project" from the info file, it will be added by drupal.org packaging automatically'; 70 | $phpcsFile->addWarning($warning, $stackPtr, 'Project'); 71 | } 72 | 73 | if (isset($info['datestamp']) === true) { 74 | $warning = 'Remove "datestamp" from the info file, it will be added by drupal.org packaging automatically'; 75 | $phpcsFile->addWarning($warning, $stackPtr, 'Timestamp'); 76 | } 77 | 78 | // "version" is special: we want to allow it in core, but not anywhere else. 79 | if (isset($info['version']) === true && strpos($phpcsFile->getFilename(), '/core/') === false) { 80 | $warning = 'Remove "version" from the info file, it will be added by drupal.org packaging automatically'; 81 | $phpcsFile->addWarning($warning, $stackPtr, 'Version'); 82 | } 83 | 84 | return ($phpcsFile->numTokens + 1); 85 | 86 | }//end process() 87 | 88 | 89 | }//end class 90 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/InfoFiles/DependenciesArraySniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [T_INLINE_HTML]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the current token in the stack passed in $tokens. 44 | * 45 | * @return int 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | // Only run this sniff on .info.yml files. 50 | if (strtolower(substr($phpcsFile->getFilename(), -9)) !== '.info.yml') { 51 | return ($phpcsFile->numTokens + 1); 52 | } 53 | 54 | try { 55 | $info = Yaml::parse(file_get_contents($phpcsFile->getFilename())); 56 | } catch (ParseException $e) { 57 | // If the YAML is invalid we ignore this file. 58 | return ($phpcsFile->numTokens + 1); 59 | } 60 | 61 | if (isset($info['dependencies']) === true && is_array($info['dependencies']) === false) { 62 | // The $stackPtr will always indicate line 1, but we can get the actual line by 63 | // searching $tokens to find the dependencies item. 64 | $tokens = $phpcsFile->getTokens(); 65 | // $tokens cannot be empty at this point, but PHPStan 10.1.4 does not know this and gives the error 66 | // "Variable $key might not be defined". So initialize it here. 67 | $key = $stackPtr; 68 | foreach ($tokens as $key => $token) { 69 | if (preg_match('/dependencies\s*\:/', $token['content']) === 1) { 70 | break; 71 | } 72 | } 73 | 74 | $error = '"dependencies" should be an array in the info yaml file'; 75 | $phpcsFile->addError($error, $key, 'Dependencies'); 76 | } 77 | 78 | return ($phpcsFile->numTokens + 1); 79 | 80 | }//end process() 81 | 82 | 83 | }//end class 84 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/InfoFiles/RequiredSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [T_INLINE_HTML]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the current token in the 44 | * stack passed in $tokens. 45 | * 46 | * @return int 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | // Only run this sniff once per info file. 51 | $fileExtension = strtolower(substr($phpcsFile->getFilename(), -4)); 52 | if ($fileExtension !== 'info') { 53 | return ($phpcsFile->numTokens + 1); 54 | } 55 | 56 | $contents = file_get_contents($phpcsFile->getFilename()); 57 | $info = ClassFilesSniff::drupalParseInfoFormat($contents); 58 | if (isset($info['name']) === false) { 59 | $error = '"name" property is missing in the info file'; 60 | $phpcsFile->addError($error, $stackPtr, 'Name'); 61 | } 62 | 63 | if (isset($info['description']) === false) { 64 | $error = '"description" property is missing in the info file'; 65 | $phpcsFile->addError($error, $stackPtr, 'Description'); 66 | } 67 | 68 | if (isset($info['core']) === false) { 69 | $error = '"core" property is missing in the info file'; 70 | $phpcsFile->addError($error, $stackPtr, 'Core'); 71 | } else if ($info['core'] === '7.x' && isset($info['php']) === true 72 | && $info['php'] <= '5.2' 73 | ) { 74 | $error = 'Drupal 7 core already requires PHP 5.2'; 75 | $ptr = ClassFilesSniff::getPtr('php', $info['php'], $phpcsFile); 76 | $phpcsFile->addError($error, $ptr, 'D7PHPVersion'); 77 | } 78 | 79 | return ($phpcsFile->numTokens + 1); 80 | 81 | }//end process() 82 | 83 | 84 | }//end class 85 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Methods/MethodDeclarationSniff.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | public function register() 35 | { 36 | return [ 37 | T_CLASS, 38 | T_ENUM, 39 | T_INTERFACE, 40 | T_TRAIT, 41 | ]; 42 | 43 | }//end register() 44 | 45 | 46 | /** 47 | * Processes this test, when one of its tokens is encountered. 48 | * 49 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed. 50 | * @param int $stackPtr The position of the current token 51 | * in the stack passed in $tokens. 52 | * 53 | * @return void 54 | */ 55 | public function process(File $phpcsFile, $stackPtr) 56 | { 57 | $tokens = $phpcsFile->getTokens(); 58 | 59 | $className = $phpcsFile->findNext(T_STRING, $stackPtr); 60 | $name = trim($tokens[$className]['content']); 61 | $errorData = [ucfirst($tokens[$stackPtr]['content'])]; 62 | 63 | // Make sure the first letter is a capital. 64 | if (preg_match('|^[A-Z]|', $name) === 0) { 65 | $error = '%s name must use UpperCamel naming and begin with a capital letter'; 66 | $phpcsFile->addError($error, $stackPtr, 'StartWithCapital', $errorData); 67 | } 68 | 69 | // Search for underscores. 70 | if (strpos($name, '_') !== false) { 71 | $error = '%s name must use UpperCamel naming without underscores'; 72 | $phpcsFile->addError($error, $stackPtr, 'NoUnderscores', $errorData); 73 | } 74 | 75 | // Ensure the name is not all uppercase. 76 | // @todo We could make this more strict to check if there are more than 77 | // 2 upper case characters in a row anywhere, but not decided yet. 78 | // See https://www.drupal.org/project/coder/issues/3497433 79 | if (preg_match('|^[A-Z]{3}[^a-z]*$|', $name) === 1) { 80 | $error = '%s name must use UpperCamel naming and not contain multiple upper case letters in a row'; 81 | $phpcsFile->addError($error, $stackPtr, 'NoUpperAcronyms', $errorData); 82 | } 83 | 84 | }//end process() 85 | 86 | 87 | }//end class 88 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/NamingConventions/ValidEnumCaseSniff.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | public function register() 29 | { 30 | return [T_ENUM_CASE]; 31 | 32 | }//end register() 33 | 34 | 35 | }//end class 36 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/NamingConventions/ValidGlobalSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public $coreGlobals = [ 32 | '$argc', 33 | '$argv', 34 | '$base_insecure_url', 35 | '$base_path', 36 | '$base_root', 37 | '$base_secure_url', 38 | '$base_theme_info', 39 | '$base_url', 40 | '$channel', 41 | '$conf', 42 | '$config', 43 | '$config_directories', 44 | '$cookie_domain', 45 | '$databases', 46 | '$db_prefix', 47 | '$db_type', 48 | '$db_url', 49 | '$drupal_hash_salt', 50 | '$drupal_test_info', 51 | '$element', 52 | '$forum_topic_list_header', 53 | '$image', 54 | '$install_state', 55 | '$installed_profile', 56 | '$is_https', 57 | '$is_https_mock', 58 | '$item', 59 | '$items', 60 | '$language', 61 | '$language_content', 62 | '$language_url', 63 | '$locks', 64 | '$menu_admin', 65 | '$multibyte', 66 | '$pager_limits', 67 | '$pager_page_array', 68 | '$pager_total', 69 | '$pager_total_items', 70 | '$tag', 71 | '$theme', 72 | '$theme_engine', 73 | '$theme_info', 74 | '$theme_key', 75 | '$theme_path', 76 | '$timers', 77 | '$update_free_access', 78 | '$update_rewrite_settings', 79 | '$user', 80 | ]; 81 | 82 | 83 | /** 84 | * Returns an array of tokens this test wants to listen for. 85 | * 86 | * @return array 87 | */ 88 | public function register() 89 | { 90 | return [T_GLOBAL]; 91 | 92 | }//end register() 93 | 94 | 95 | /** 96 | * Processes this test, when one of its tokens is encountered. 97 | * 98 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed. 99 | * @param int $stackPtr The position of the current token 100 | * in the stack passed in $tokens. 101 | * 102 | * @return void 103 | */ 104 | public function process(File $phpcsFile, $stackPtr) 105 | { 106 | $tokens = $phpcsFile->getTokens(); 107 | 108 | $varToken = $stackPtr; 109 | // Find variable names until we hit a semicolon. 110 | $ignore = Tokens::$emptyTokens; 111 | $ignore[] = T_SEMICOLON; 112 | while (($varToken = $phpcsFile->findNext($ignore, ($varToken + 1), null, true, null, true)) !== false) { 113 | if ($tokens[$varToken]['code'] === T_VARIABLE 114 | && in_array($tokens[$varToken]['content'], $this->coreGlobals) === false 115 | && $tokens[$varToken]['content'][1] !== '_' 116 | ) { 117 | $error = 'global variables should start with a single underscore followed by the module and another underscore'; 118 | $phpcsFile->addError($error, $varToken, 'GlobalUnderScore'); 119 | } 120 | } 121 | 122 | }//end process() 123 | 124 | 125 | }//end class 126 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Scope/MethodScopeSniff.php: -------------------------------------------------------------------------------- 1 | getTokens(); 54 | 55 | $methodName = $phpcsFile->getDeclarationName($stackPtr); 56 | if ($methodName === null) { 57 | // Ignore closures. 58 | return; 59 | } 60 | 61 | if ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true) { 62 | // Ignore nested functions. 63 | return; 64 | } 65 | 66 | $modifier = null; 67 | for ($i = ($stackPtr - 1); $i > 0; $i--) { 68 | if ($tokens[$i]['line'] < $tokens[$stackPtr]['line']) { 69 | break; 70 | } else if (isset(Tokens::$scopeModifiers[$tokens[$i]['code']]) === true) { 71 | $modifier = $i; 72 | break; 73 | } 74 | } 75 | 76 | if ($modifier === null) { 77 | $error = 'Visibility must be declared on method "%s"'; 78 | $data = [$methodName]; 79 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Missing', $data); 80 | 81 | if ($fix === true) { 82 | // No scope modifier means the method is public in PHP, fix that 83 | // to be explicitly public. 84 | $phpcsFile->fixer->addContentBefore($stackPtr, 'public '); 85 | } 86 | } 87 | 88 | }//end processTokenWithinScope() 89 | 90 | 91 | /** 92 | * Processes a token that is found outside the scope that this test is 93 | * listening to. 94 | * 95 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. 96 | * @param int $stackPtr The position in the stack where this 97 | * token was found. 98 | * 99 | * @return void 100 | */ 101 | protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) 102 | { 103 | 104 | }//end processTokenOutsideScope() 105 | 106 | 107 | }//end class 108 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/EmptyInstallSniff.php: -------------------------------------------------------------------------------- 1 | getFilename(), -7)); 40 | // Only check in *.install files. 41 | if ($fileExtension !== 'install') { 42 | return; 43 | } 44 | 45 | $fileName = substr(basename($phpcsFile->getFilename()), 0, -8); 46 | $tokens = $phpcsFile->getTokens(); 47 | if ($tokens[$stackPtr]['content'] === ($fileName.'_install') 48 | || $tokens[$stackPtr]['content'] === ($fileName.'_uninstall') 49 | ) { 50 | // Check if there is a function body. 51 | $bodyPtr = $phpcsFile->findNext( 52 | Tokens::$emptyTokens, 53 | ($tokens[$functionPtr]['scope_opener'] + 1), 54 | $tokens[$functionPtr]['scope_closer'], 55 | true 56 | ); 57 | if ($bodyPtr === false) { 58 | $error = 'Empty installation hooks are not necessary'; 59 | $phpcsFile->addError($error, $stackPtr, 'EmptyInstall'); 60 | } 61 | } 62 | 63 | }//end processFunction() 64 | 65 | 66 | }//end class 67 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/FunctionDefinition.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [T_STRING]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $tokens = $phpcsFile->getTokens(); 51 | // Check if this is a function definition. 52 | $functionPtr = $phpcsFile->findPrevious( 53 | Tokens::$emptyTokens, 54 | ($stackPtr - 1), 55 | null, 56 | true 57 | ); 58 | if ($tokens[$functionPtr]['code'] === T_FUNCTION) { 59 | $this->processFunction($phpcsFile, $stackPtr, $functionPtr); 60 | } 61 | 62 | }//end process() 63 | 64 | 65 | /** 66 | * Process this function definition. 67 | * 68 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 69 | * @param int $stackPtr The position of the function name in the stack. 70 | * name in the stack. 71 | * @param int $functionPtr The position of the function keyword in the stack. 72 | * keyword in the stack. 73 | * 74 | * @return void 75 | */ 76 | abstract public function processFunction(File $phpcsFile, $stackPtr, $functionPtr); 77 | 78 | 79 | }//end class 80 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/FunctionWatchdogSniff.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | public function registerFunctionNames() 31 | { 32 | return ['watchdog']; 33 | 34 | }//end registerFunctionNames() 35 | 36 | 37 | /** 38 | * Processes this function call. 39 | * 40 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 41 | * @param int $stackPtr The position of the function call in 42 | * the stack. 43 | * @param int $openBracket The position of the opening 44 | * parenthesis in the stack. 45 | * @param int $closeBracket The position of the closing 46 | * parenthesis in the stack. 47 | * 48 | * @return void 49 | */ 50 | public function processFunctionCall( 51 | File $phpcsFile, 52 | $stackPtr, 53 | $openBracket, 54 | $closeBracket 55 | ) { 56 | $tokens = $phpcsFile->getTokens(); 57 | // Get the second argument passed to watchdog(). 58 | $argument = $this->getArgument(2); 59 | if ($argument === false) { 60 | $error = 'The second argument to watchdog() is missing'; 61 | $phpcsFile->addError($error, $stackPtr, 'WatchdogArgument'); 62 | return; 63 | } 64 | 65 | if ($tokens[$argument['start']]['code'] === T_STRING 66 | && $tokens[$argument['start']]['content'] === 't' 67 | ) { 68 | $error = 'The second argument to watchdog() should not be enclosed with t()'; 69 | $phpcsFile->addError($error, $argument['start'], 'WatchdogT'); 70 | } 71 | 72 | $concatFound = $phpcsFile->findNext(T_STRING_CONCAT, $argument['start'], $argument['end']); 73 | if ($concatFound !== false) { 74 | $error = 'Concatenating translatable strings is not allowed, use placeholders instead and only one string literal'; 75 | $phpcsFile->addError($error, $concatFound, 'Concat'); 76 | } 77 | 78 | }//end processFunctionCall() 79 | 80 | 81 | }//end class 82 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/InstallHooksSniff.php: -------------------------------------------------------------------------------- 1 | getFilename(), -6)); 40 | // Only check in *.module files. 41 | if ($fileExtension !== 'module') { 42 | return; 43 | } 44 | 45 | $tokens = $phpcsFile->getTokens(); 46 | 47 | $fileName = substr(basename($phpcsFile->getFilename()), 0, -7); 48 | if ($tokens[$stackPtr]['content'] === ($fileName.'_install') 49 | || $tokens[$stackPtr]['content'] === ($fileName.'_uninstall') 50 | || $tokens[$stackPtr]['content'] === ($fileName.'_requirements') 51 | || $tokens[$stackPtr]['content'] === ($fileName.'_schema') 52 | || $tokens[$stackPtr]['content'] === ($fileName.'_enable') 53 | || $tokens[$stackPtr]['content'] === ($fileName.'_disable') 54 | ) { 55 | $error = '%s() is an installation hook and must be declared in an install file'; 56 | $data = [$tokens[$stackPtr]['content']]; 57 | $phpcsFile->addError($error, $stackPtr, 'InstallHook', $data); 58 | } 59 | 60 | }//end processFunction() 61 | 62 | 63 | }//end class 64 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/LStringTranslatableSniff.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | public function registerFunctionNames() 31 | { 32 | return ['l']; 33 | 34 | }//end registerFunctionNames() 35 | 36 | 37 | /** 38 | * Processes this function call. 39 | * 40 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 41 | * @param int $stackPtr The position of the function call in 42 | * the stack. 43 | * @param int $openBracket The position of the opening 44 | * parenthesis in the stack. 45 | * @param int $closeBracket The position of the closing 46 | * parenthesis in the stack. 47 | * 48 | * @return void 49 | */ 50 | public function processFunctionCall( 51 | File $phpcsFile, 52 | $stackPtr, 53 | $openBracket, 54 | $closeBracket 55 | ) { 56 | $tokens = $phpcsFile->getTokens(); 57 | // Get the first argument passed to l(). 58 | $argument = $this->getArgument(1); 59 | if ($tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING 60 | // If the string starts with a HTML tag we don't complain. 61 | && $tokens[$argument['start']]['content'][1] !== '<' 62 | ) { 63 | $error = 'The $text argument to l() should be enclosed within t() so that it is translatable'; 64 | $phpcsFile->addError($error, $stackPtr, 'LArg'); 65 | } 66 | 67 | }//end processFunctionCall() 68 | 69 | 70 | }//end class 71 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/PregSecuritySniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function registerFunctionNames() 32 | { 33 | return [ 34 | 'preg_filter', 35 | 'preg_grep', 36 | 'preg_match', 37 | 'preg_match_all', 38 | 'preg_replace', 39 | 'preg_replace_callback', 40 | 'preg_split', 41 | ]; 42 | 43 | }//end registerFunctionNames() 44 | 45 | 46 | /** 47 | * Processes this function call. 48 | * 49 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 50 | * @param int $stackPtr The position of the function call in 51 | * the stack. 52 | * @param int $openBracket The position of the opening 53 | * parenthesis in the stack. 54 | * @param int $closeBracket The position of the closing 55 | * parenthesis in the stack. 56 | * 57 | * @return void 58 | */ 59 | public function processFunctionCall( 60 | File $phpcsFile, 61 | $stackPtr, 62 | $openBracket, 63 | $closeBracket 64 | ) { 65 | $tokens = $phpcsFile->getTokens(); 66 | $argument = $this->getArgument(1); 67 | 68 | if ($argument === false) { 69 | return; 70 | } 71 | 72 | if ($tokens[$argument['start']]['code'] !== T_CONSTANT_ENCAPSED_STRING) { 73 | // Not a string literal. 74 | // @TODO: Extend code to recognize patterns in variables. 75 | return; 76 | } 77 | 78 | $pattern = $tokens[$argument['start']]['content']; 79 | $quote = substr($pattern, 0, 1); 80 | // Check that the pattern is a string. 81 | if ($quote === '"' || $quote === "'") { 82 | // Get the delimiter - first char after the enclosing quotes. 83 | $delimiter = preg_quote(substr($pattern, 1, 1), '/'); 84 | // Check if there is the evil e flag. 85 | if (preg_match('/'.$delimiter.'[\w]{0,}e[\w]{0,}$/', substr($pattern, 0, -1)) === 1) { 86 | $warn = 'Using the e flag in %s is a possible security risk. For details see https://www.drupal.org/node/750148'; 87 | $phpcsFile->addError( 88 | $warn, 89 | $argument['start'], 90 | 'PregEFlag', 91 | [$tokens[$stackPtr]['content']] 92 | ); 93 | return; 94 | } 95 | } 96 | 97 | }//end processFunctionCall() 98 | 99 | 100 | }//end class 101 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/RemoteAddressSniff.php: -------------------------------------------------------------------------------- 1 | getClientIp() is used instead of 17 | * $_SERVER['REMOTE_ADDR']. 18 | * 19 | * @category PHP 20 | * @package PHP_CodeSniffer 21 | * @link http://pear.php.net/package/PHP_CodeSniffer 22 | */ 23 | class RemoteAddressSniff implements Sniff 24 | { 25 | 26 | 27 | /** 28 | * Returns an array of tokens this test wants to listen for. 29 | * 30 | * @return array 31 | */ 32 | public function register() 33 | { 34 | return [T_VARIABLE]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $string = $phpcsFile->getTokensAsString($stackPtr, 4); 51 | $startOfStatement = $phpcsFile->findStartOfStatement($stackPtr); 52 | if (($string === '$_SERVER["REMOTE_ADDR"]' || $string === '$_SERVER[\'REMOTE_ADDR\']') && $stackPtr !== $startOfStatement) { 53 | $error = 'Use ip_address() or Drupal::request()->getClientIp() instead of $_SERVER[\'REMOTE_ADDR\']'; 54 | $phpcsFile->addError($error, $stackPtr, 'RemoteAddress'); 55 | } 56 | 57 | }//end process() 58 | 59 | 60 | }//end class 61 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/TInHookMenuSniff.php: -------------------------------------------------------------------------------- 1 | getFilename(), -6)); 40 | // Only check in *.module files. 41 | if ($fileExtension !== 'module') { 42 | return; 43 | } 44 | 45 | $fileName = substr(basename($phpcsFile->getFilename()), 0, -7); 46 | $tokens = $phpcsFile->getTokens(); 47 | if ($tokens[$stackPtr]['content'] !== ($fileName.'_menu')) { 48 | return; 49 | } 50 | 51 | // Search in the function body for t() calls. 52 | $string = $phpcsFile->findNext( 53 | T_STRING, 54 | $tokens[$functionPtr]['scope_opener'], 55 | $tokens[$functionPtr]['scope_closer'] 56 | ); 57 | while ($string !== false) { 58 | if ($tokens[$string]['content'] === 't') { 59 | $opener = $phpcsFile->findNext( 60 | Tokens::$emptyTokens, 61 | ($string + 1), 62 | null, 63 | true 64 | ); 65 | if ($opener !== false 66 | && $tokens[$opener]['code'] === T_OPEN_PARENTHESIS 67 | ) { 68 | $error = 'Do not use t() in hook_menu()'; 69 | $phpcsFile->addError($error, $string, 'TFound'); 70 | } 71 | } 72 | 73 | $string = $phpcsFile->findNext( 74 | T_STRING, 75 | ($string + 1), 76 | $tokens[$functionPtr]['scope_closer'] 77 | ); 78 | }//end while 79 | 80 | }//end processFunction() 81 | 82 | 83 | }//end class 84 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/TInHookSchemaSniff.php: -------------------------------------------------------------------------------- 1 | getFilename(), -7)); 40 | // Only check in *.install files. 41 | if ($fileExtension !== 'install') { 42 | return; 43 | } 44 | 45 | $fileName = substr(basename($phpcsFile->getFilename()), 0, -8); 46 | $tokens = $phpcsFile->getTokens(); 47 | if ($tokens[$stackPtr]['content'] !== ($fileName.'_schema')) { 48 | return; 49 | } 50 | 51 | // Search in the function body for t() calls. 52 | $string = $phpcsFile->findNext( 53 | T_STRING, 54 | $tokens[$functionPtr]['scope_opener'], 55 | $tokens[$functionPtr]['scope_closer'] 56 | ); 57 | while ($string !== false) { 58 | if ($tokens[$string]['content'] === 't') { 59 | $opener = $phpcsFile->findNext( 60 | Tokens::$emptyTokens, 61 | ($string + 1), 62 | null, 63 | true 64 | ); 65 | if ($opener !== false 66 | && $tokens[$opener]['code'] === T_OPEN_PARENTHESIS 67 | ) { 68 | $error = 'Do not use t() in hook_schema(), this will only generate overhead for translators'; 69 | $phpcsFile->addError($error, $string, 'TFound'); 70 | } 71 | } 72 | 73 | $string = $phpcsFile->findNext( 74 | T_STRING, 75 | ($string + 1), 76 | $tokens[$functionPtr]['scope_closer'] 77 | ); 78 | }//end while 79 | 80 | }//end processFunction() 81 | 82 | 83 | }//end class 84 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Semantics/UnsilencedDeprecationSniff.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | public function registerFunctionNames() 31 | { 32 | return ['trigger_error']; 33 | 34 | }//end registerFunctionNames() 35 | 36 | 37 | /** 38 | * Processes this function call. 39 | * 40 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 41 | * @param int $stackPtr The position of the function call in 42 | * the stack. 43 | * @param int $openBracket The position of the opening 44 | * parenthesis in the stack. 45 | * @param int $closeBracket The position of the closing 46 | * parenthesis in the stack. 47 | * 48 | * @return void 49 | */ 50 | public function processFunctionCall( 51 | file $phpcsFile, 52 | $stackPtr, 53 | $openBracket, 54 | $closeBracket 55 | ) { 56 | 57 | $tokens = $phpcsFile->getTokens(); 58 | $argument = $this->getArgument(2); 59 | 60 | // If no second argument then quit. 61 | if ($argument === false) { 62 | return; 63 | } 64 | 65 | // Only check deprecation messages. 66 | if (strcasecmp($tokens[$argument['start']]['content'], 'E_USER_DEPRECATED') !== 0) { 67 | return; 68 | } 69 | 70 | if ($tokens[($stackPtr - 1)]['type'] !== 'T_ASPERAND') { 71 | $error = 'All trigger_error calls used for deprecation must be prefixed by an "@"'; 72 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'UnsilencedDeprecation'); 73 | if ($fix === true) { 74 | $phpcsFile->fixer->addContentBefore($stackPtr, '@'); 75 | } 76 | } 77 | 78 | }//end processFunctionCall() 79 | 80 | 81 | }//end class 82 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/Strings/UnnecessaryStringConcatSniff.php: -------------------------------------------------------------------------------- 1 | 36 | */ 37 | public function register() 38 | { 39 | return [T_OPEN_TAG]; 40 | 41 | }//end register() 42 | 43 | 44 | /** 45 | * Processes the tokens that this sniff is interested in. 46 | * 47 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where the token was found. 48 | * @param int $stackPtr The position in the stack where 49 | * the token was found. 50 | * 51 | * @return int 52 | */ 53 | public function process(File $phpcsFile, $stackPtr) 54 | { 55 | // This sniff is deprecated and disabled - do nothing. 56 | return ($phpcsFile->numTokens + 1); 57 | 58 | }//end process() 59 | 60 | 61 | /** 62 | * {@inheritdoc} 63 | * 64 | * @return string 65 | */ 66 | public function getDeprecationVersion(): string 67 | { 68 | return 'Coder 8.3.30'; 69 | 70 | }//end getDeprecationVersion() 71 | 72 | 73 | /** 74 | * {@inheritdoc} 75 | * 76 | * @return string 77 | */ 78 | public function getRemovalVersion(): string 79 | { 80 | return 'Coder 9.0.0'; 81 | 82 | }//end getRemovalVersion() 83 | 84 | 85 | /** 86 | * {@inheritdoc} 87 | * 88 | * @return string 89 | */ 90 | public function getDeprecationMessage(): string 91 | { 92 | return 'The custom UnnecessaryStringConcatSniff is deprecated and will be removed in Coder 9.0.0. Use Generic.Strings.UnnecessaryStringConcat instead.'; 93 | 94 | }//end getDeprecationMessage() 95 | 96 | 97 | }//end class 98 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/WhiteSpace/CloseBracketSpacingSniff.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | public function register() 35 | { 36 | return [ 37 | T_CLOSE_CURLY_BRACKET, 38 | T_CLOSE_PARENTHESIS, 39 | T_CLOSE_SHORT_ARRAY, 40 | ]; 41 | 42 | }//end register() 43 | 44 | 45 | /** 46 | * Processes this test, when one of its tokens is encountered. 47 | * 48 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 49 | * @param int $stackPtr The position of the current token 50 | * in the stack passed in $tokens. 51 | * 52 | * @return void 53 | */ 54 | public function process(File $phpcsFile, $stackPtr) 55 | { 56 | $tokens = $phpcsFile->getTokens(); 57 | 58 | if (isset($tokens[($stackPtr - 1)]) === true 59 | && $tokens[($stackPtr - 1)]['code'] === T_WHITESPACE 60 | ) { 61 | $before = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); 62 | if ($before !== false && $tokens[$stackPtr]['line'] === $tokens[$before]['line']) { 63 | $error = 'There should be no white space before a closing "%s"'; 64 | $fix = $phpcsFile->addFixableError( 65 | $error, 66 | ($stackPtr - 1), 67 | 'ClosingWhitespace', 68 | [$tokens[$stackPtr]['content']] 69 | ); 70 | if ($fix === true) { 71 | $phpcsFile->fixer->replaceToken(($stackPtr - 1), ''); 72 | } 73 | } 74 | } 75 | 76 | }//end process() 77 | 78 | 79 | }//end class 80 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/WhiteSpace/CommaSniff.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function register() 34 | { 35 | return [T_COMMA]; 36 | 37 | }//end register() 38 | 39 | 40 | /** 41 | * Processes this test, when one of its tokens is encountered. 42 | * 43 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 44 | * @param int $stackPtr The position of the current token 45 | * in the stack passed in $tokens. 46 | * 47 | * @return void 48 | */ 49 | public function process(File $phpcsFile, $stackPtr) 50 | { 51 | $tokens = $phpcsFile->getTokens(); 52 | 53 | if (isset($tokens[($stackPtr + 1)]) === false) { 54 | return; 55 | } 56 | 57 | if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE 58 | && $tokens[($stackPtr + 1)]['code'] !== T_COMMA 59 | && $tokens[($stackPtr + 1)]['code'] !== T_CLOSE_PARENTHESIS 60 | ) { 61 | $error = 'Expected one space after the comma, 0 found'; 62 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpace'); 63 | if ($fix === true) { 64 | $phpcsFile->fixer->addContent($stackPtr, ' '); 65 | } 66 | 67 | return; 68 | } 69 | 70 | if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE 71 | && isset($tokens[($stackPtr + 2)]) === true 72 | && $tokens[($stackPtr + 2)]['line'] === $tokens[($stackPtr + 1)]['line'] 73 | && $tokens[($stackPtr + 1)]['content'] !== ' ' 74 | ) { 75 | $error = 'Expected one space after the comma, %s found'; 76 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'TooManySpaces', [strlen($tokens[($stackPtr + 1)]['content'])]); 77 | if ($fix === true) { 78 | $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); 79 | } 80 | } 81 | 82 | }//end process() 83 | 84 | 85 | }//end class 86 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/WhiteSpace/EmptyLinesSniff.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function register() 34 | { 35 | return [T_WHITESPACE]; 36 | 37 | }//end register() 38 | 39 | 40 | /** 41 | * Processes this test, when one of its tokens is encountered. 42 | * 43 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 44 | * @param int $stackPtr The position of the current token 45 | * in the stack passed in $tokens. 46 | * 47 | * @return void 48 | */ 49 | public function process(File $phpcsFile, $stackPtr) 50 | { 51 | $tokens = $phpcsFile->getTokens(); 52 | if ($tokens[$stackPtr]['content'] === $phpcsFile->eolChar 53 | && isset($tokens[($stackPtr + 1)]) === true 54 | && $tokens[($stackPtr + 1)]['content'] === $phpcsFile->eolChar 55 | && isset($tokens[($stackPtr + 2)]) === true 56 | && $tokens[($stackPtr + 2)]['content'] === $phpcsFile->eolChar 57 | && isset($tokens[($stackPtr + 3)]) === true 58 | && $tokens[($stackPtr + 3)]['content'] === $phpcsFile->eolChar 59 | ) { 60 | $error = 'More than 2 empty lines are not allowed'; 61 | $phpcsFile->addError($error, ($stackPtr + 3), 'EmptyLines'); 62 | } 63 | 64 | }//end process() 65 | 66 | 67 | }//end class 68 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/WhiteSpace/NamespaceSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_NAMESPACE]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the current token 43 | * in the stack passed in $tokens. 44 | * 45 | * @return void 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | $tokens = $phpcsFile->getTokens(); 50 | 51 | if ($tokens[($stackPtr + 1)]['content'] !== ' ') { 52 | $error = 'There must be exactly one space after the namespace keyword'; 53 | $fix = $phpcsFile->addFixableError($error, ($stackPtr + 1), 'OneSpace'); 54 | if ($fix === true) { 55 | $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); 56 | } 57 | } 58 | 59 | }//end process() 60 | 61 | 62 | }//end class 63 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/WhiteSpace/OpenBracketSpacingSniff.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function register() 34 | { 35 | return [ 36 | T_OPEN_CURLY_BRACKET, 37 | T_OPEN_PARENTHESIS, 38 | T_OPEN_SHORT_ARRAY, 39 | ]; 40 | 41 | }//end register() 42 | 43 | 44 | /** 45 | * Processes this test, when one of its tokens is encountered. 46 | * 47 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 48 | * @param int $stackPtr The position of the current token 49 | * in the stack passed in $tokens. 50 | * 51 | * @return void 52 | */ 53 | public function process(File $phpcsFile, $stackPtr) 54 | { 55 | $tokens = $phpcsFile->getTokens(); 56 | 57 | if (isset($tokens[($stackPtr + 1)]) === true 58 | && $tokens[($stackPtr + 1)]['code'] === T_WHITESPACE 59 | && strpos($tokens[($stackPtr + 1)]['content'], $phpcsFile->eolChar) === false 60 | // Allow spaces in template files where the PHP close tag is used. 61 | && isset($tokens[($stackPtr + 2)]) === true 62 | && $tokens[($stackPtr + 2)]['code'] !== T_CLOSE_TAG 63 | ) { 64 | $error = 'There should be no white space after an opening "%s"'; 65 | $fix = $phpcsFile->addFixableError( 66 | $error, 67 | ($stackPtr + 1), 68 | 'OpeningWhitespace', 69 | [$tokens[$stackPtr]['content']] 70 | ); 71 | if ($fix === true) { 72 | $phpcsFile->fixer->replaceToken(($stackPtr + 1), ''); 73 | } 74 | } 75 | 76 | }//end process() 77 | 78 | 79 | }//end class 80 | -------------------------------------------------------------------------------- /coder_sniffer/Drupal/Sniffs/WhiteSpace/OpenTagNewlineSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_OPEN_TAG]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the 42 | * token was found. 43 | * @param int $stackPtr The position in the PHP_CodeSniffer 44 | * file's token stack where the token 45 | * was found. 46 | * 47 | * @return int End of the stack to skip the rest of the file. 48 | */ 49 | public function process(File $phpcsFile, $stackPtr) 50 | { 51 | $tokens = $phpcsFile->getTokens(); 52 | 53 | // Only check the very first PHP open tag in a file, ignore any others. 54 | if ($stackPtr !== 0) { 55 | return ($phpcsFile->numTokens + 1); 56 | } 57 | 58 | $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 59 | 60 | // If there is no further content in this file ignore it. 61 | if ($next === false) { 62 | return ($phpcsFile->numTokens + 1); 63 | } 64 | 65 | if ($tokens[$next]['line'] === 3) { 66 | return ($phpcsFile->numTokens + 1); 67 | } 68 | 69 | $error = 'The PHP open tag must be followed by exactly one blank line'; 70 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'BlankLine'); 71 | 72 | if ($fix === true) { 73 | $phpcsFile->fixer->beginChangeset(); 74 | 75 | if ($tokens[$next]['line'] === 1) { 76 | $phpcsFile->fixer->addNewline($stackPtr); 77 | $phpcsFile->fixer->addNewline($stackPtr); 78 | } else if ($tokens[$next]['line'] === 2) { 79 | $phpcsFile->fixer->addNewline($stackPtr); 80 | } else { 81 | for ($i = ($stackPtr + 1); $i < $next; $i++) { 82 | if ($tokens[$i]['line'] > 2) { 83 | $phpcsFile->fixer->replaceToken($i, ''); 84 | } 85 | } 86 | } 87 | 88 | $phpcsFile->fixer->endChangeset(); 89 | } 90 | 91 | return ($phpcsFile->numTokens + 1); 92 | 93 | }//end process() 94 | 95 | 96 | }//end class 97 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/CodeAnalysis/VariableAnalysisSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_DOC_COMMENT_TAG]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the current token 43 | * in the stack passed in $tokens. 44 | * 45 | * @return void 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | $tokens = $phpcsFile->getTokens(); 50 | 51 | $content = $tokens[$stackPtr]['content']; 52 | if ($content === '@author' || $content === '@author:') { 53 | $warning = '@author tags are not usually used in Drupal, because over time multiple contributors will touch the code anyway'; 54 | $phpcsFile->addWarning($warning, $stackPtr, 'AuthorFound'); 55 | } 56 | 57 | }//end process() 58 | 59 | 60 | }//end class 61 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/Commenting/CommentEmptyLineSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [T_COMMENT]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $tokens = $phpcsFile->getTokens(); 51 | 52 | $comment = rtrim($tokens[$stackPtr]['content']); 53 | 54 | // Only want inline comments. 55 | if (substr($comment, 0, 2) !== '//') { 56 | return; 57 | } 58 | 59 | // The line below the last comment cannot be empty. 60 | for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) { 61 | if ($tokens[$i]['line'] === ($tokens[$stackPtr]['line'] + 1)) { 62 | if ($tokens[$i]['code'] !== T_WHITESPACE) { 63 | return; 64 | } 65 | } else if ($tokens[$i]['line'] > ($tokens[$stackPtr]['line'] + 1)) { 66 | break; 67 | } 68 | } 69 | 70 | $warning = 'There must be no blank line following an inline comment'; 71 | $phpcsFile->addWarning($warning, $stackPtr, 'SpacingAfter'); 72 | 73 | }//end process() 74 | 75 | 76 | }//end class 77 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/Commenting/ExpectedExceptionSniff.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function register() 34 | { 35 | return [T_DOC_COMMENT_TAG]; 36 | 37 | }//end register() 38 | 39 | 40 | /** 41 | * Processes this test, when one of its tokens is encountered. 42 | * 43 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 44 | * @param int $stackPtr The position of the current token 45 | * in the stack passed in $tokens. 46 | * 47 | * @return void 48 | */ 49 | public function process(File $phpcsFile, $stackPtr) 50 | { 51 | $tokens = $phpcsFile->getTokens(); 52 | 53 | $content = $tokens[$stackPtr]['content']; 54 | if ($content === '@expectedException' || $content === '@expectedExceptionCode' 55 | || $content === '@expectedExceptionMessage' 56 | || $content === '@expectedExceptionMessageRegExp' 57 | ) { 58 | $warning = '%s tags should not be used, use $this->setExpectedException() or $this->expectException() instead'; 59 | $phpcsFile->addWarning($warning, $stackPtr, 'TagFound', [$content]); 60 | } 61 | 62 | }//end process() 63 | 64 | 65 | }//end class 66 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/Constants/GlobalConstantSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [T_CONST]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void|int 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $tokens = $phpcsFile->getTokens(); 51 | 52 | // Only check constants in the global scope. 53 | if (empty($tokens[$stackPtr]['conditions']) === false) { 54 | return; 55 | } 56 | 57 | $coreVersion = Project::getCoreVersion($phpcsFile); 58 | if ($coreVersion < 8) { 59 | // No need to check this file again, mark it as done. 60 | return ($phpcsFile->numTokens + 1); 61 | } 62 | 63 | // Allow constants if they are deprecated. 64 | $commentEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 65 | if ($commentEnd !== null && $tokens[$commentEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) { 66 | // Go through all comment tags and check if one is @deprecated. 67 | $commentTag = $commentEnd; 68 | while ($commentTag !== null && $commentTag > $tokens[$commentEnd]['comment_opener']) { 69 | if ($tokens[$commentTag]['content'] === '@deprecated') { 70 | return; 71 | } 72 | 73 | $commentTag = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, ($commentTag - 1), $tokens[$commentEnd]['comment_opener']); 74 | } 75 | } 76 | 77 | $warning = 'Global constants should not be used, move it to a class or interface'; 78 | $phpcsFile->addWarning($warning, $stackPtr, 'GlobalConstant'); 79 | 80 | }//end process() 81 | 82 | 83 | }//end class 84 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/Constants/GlobalDefineSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function registerFunctionNames() 33 | { 34 | return ['define']; 35 | 36 | }//end registerFunctionNames() 37 | 38 | 39 | /** 40 | * Processes this function call. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the function call in 44 | * the stack. 45 | * @param int $openBracket The position of the opening 46 | * parenthesis in the stack. 47 | * @param int $closeBracket The position of the closing 48 | * parenthesis in the stack. 49 | * 50 | * @return void|int 51 | */ 52 | public function processFunctionCall( 53 | File $phpcsFile, 54 | $stackPtr, 55 | $openBracket, 56 | $closeBracket 57 | ) { 58 | $tokens = $phpcsFile->getTokens(); 59 | 60 | // Only check constants in the global scope in module files. 61 | if (empty($tokens[$stackPtr]['conditions']) === false || substr($phpcsFile->getFilename(), -7) !== '.module') { 62 | return; 63 | } 64 | 65 | $coreVersion = Project::getCoreVersion($phpcsFile); 66 | if ($coreVersion < 8) { 67 | // No need to check this file again, mark it as done. 68 | return ($phpcsFile->numTokens + 1); 69 | } 70 | 71 | // Allow constants if they are deprecated. 72 | $commentEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 73 | if ($commentEnd !== null && $tokens[$commentEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) { 74 | // Go through all comment tags and check if one is @deprecated. 75 | $commentTag = $commentEnd; 76 | while ($commentTag !== null && $commentTag > $tokens[$commentEnd]['comment_opener']) { 77 | if ($tokens[$commentTag]['content'] === '@deprecated') { 78 | return; 79 | } 80 | 81 | $commentTag = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, ($commentTag - 1), $tokens[$commentEnd]['comment_opener']); 82 | } 83 | } 84 | 85 | $warning = 'Global constants should not be used, move it to a class or interface'; 86 | $phpcsFile->addWarning($warning, $stackPtr, 'GlobalConstant'); 87 | 88 | }//end processFunctionCall() 89 | 90 | 91 | }//end class 92 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/CheckPlainSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function registerFunctionNames() 32 | { 33 | return ['check_plain']; 34 | 35 | }//end registerFunctionNames() 36 | 37 | 38 | /** 39 | * Processes this function call. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the function call in 43 | * the stack. 44 | * @param int $openBracket The position of the opening 45 | * parenthesis in the stack. 46 | * @param int $closeBracket The position of the closing 47 | * parenthesis in the stack. 48 | * 49 | * @return void 50 | */ 51 | public function processFunctionCall( 52 | File $phpcsFile, 53 | $stackPtr, 54 | $openBracket, 55 | $closeBracket 56 | ) { 57 | $tokens = $phpcsFile->getTokens(); 58 | $argument = $this->getArgument(1); 59 | if ($argument['start'] === $argument['end'] && $tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING) { 60 | $warning = 'Do not use check_plain() on string literals, because they cannot contain user provided text'; 61 | $phpcsFile->addWarning($warning, $argument['start'], 'CheckPlainLiteral'); 62 | } 63 | 64 | }//end processFunctionCall() 65 | 66 | 67 | }//end class 68 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/CurlSslVerifierSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function registerFunctionNames() 33 | { 34 | return ['curl_setopt']; 35 | 36 | }//end registerFunctionNames() 37 | 38 | 39 | /** 40 | * Processes this function call. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the function call in 44 | * the stack. 45 | * @param int $openBracket The position of the opening 46 | * parenthesis in the stack. 47 | * @param int $closeBracket The position of the closing 48 | * parenthesis in the stack. 49 | * 50 | * @return void 51 | */ 52 | public function processFunctionCall( 53 | File $phpcsFile, 54 | $stackPtr, 55 | $openBracket, 56 | $closeBracket 57 | ) { 58 | $tokens = $phpcsFile->getTokens(); 59 | $option = $this->getArgument(2); 60 | if ($tokens[$option['start']]['content'] !== 'CURLOPT_SSL_VERIFYPEER') { 61 | return; 62 | } 63 | 64 | $value = $this->getArgument(3); 65 | if ($tokens[$value['start']]['content'] === 'FALSE' || $tokens[$value['start']]['content'] === '0') { 66 | $warning = 'Potential security problem: SSL peer verification must not be disabled'; 67 | $phpcsFile->addWarning($warning, $value['start'], 'SslPeerVerificationDisabled'); 68 | } 69 | 70 | }//end processFunctionCall() 71 | 72 | 73 | }//end class 74 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/DbQuerySniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function registerFunctionNames() 33 | { 34 | return ['db_query']; 35 | 36 | }//end registerFunctionNames() 37 | 38 | 39 | /** 40 | * Processes this function call. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the function call in 44 | * the stack. 45 | * @param int $openBracket The position of the opening 46 | * parenthesis in the stack. 47 | * @param int $closeBracket The position of the closing 48 | * parenthesis in the stack. 49 | * 50 | * @return void|int 51 | */ 52 | public function processFunctionCall( 53 | File $phpcsFile, 54 | $stackPtr, 55 | $openBracket, 56 | $closeBracket 57 | ) { 58 | // This check only applies to Drupal 7, not Drupal 6. 59 | if (Project::getCoreVersion($phpcsFile) !== 7) { 60 | return ($phpcsFile->numTokens + 1); 61 | } 62 | 63 | $tokens = $phpcsFile->getTokens(); 64 | $argument = $this->getArgument(1); 65 | 66 | $queryStart = ''; 67 | for ($start = $argument['start']; $tokens[$start]['code'] === T_CONSTANT_ENCAPSED_STRING && empty($queryStart) === true; $start++) { 68 | // Remove quote and white space from the beginning. 69 | $queryStart = trim(substr($tokens[$start]['content'], 1)); 70 | // Just look at the first word. 71 | $parts = explode(' ', $queryStart); 72 | $queryStart = $parts[0]; 73 | 74 | if (in_array(strtoupper($queryStart), ['INSERT', 'UPDATE', 'DELETE', 'TRUNCATE']) === true) { 75 | $warning = 'Do not use %s queries with db_query(), use %s instead'; 76 | $phpcsFile->addWarning($warning, $start, 'DbQuery', [$queryStart, 'db_'.strtolower($queryStart).'()']); 77 | } 78 | } 79 | 80 | }//end processFunctionCall() 81 | 82 | 83 | }//end class 84 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/DbSelectBracesSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function registerFunctionNames() 32 | { 33 | return ['db_select']; 34 | 35 | }//end registerFunctionNames() 36 | 37 | 38 | /** 39 | * Processes this function call. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the function call in 43 | * the stack. 44 | * @param int $openBracket The position of the opening 45 | * parenthesis in the stack. 46 | * @param int $closeBracket The position of the closing 47 | * parenthesis in the stack. 48 | * 49 | * @return void 50 | */ 51 | public function processFunctionCall( 52 | File $phpcsFile, 53 | $stackPtr, 54 | $openBracket, 55 | $closeBracket 56 | ) { 57 | $tokens = $phpcsFile->getTokens(); 58 | $argument = $this->getArgument(1); 59 | 60 | if ($argument !== false && $tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING 61 | && strpos($tokens[$argument['start']]['content'], '{') !== false 62 | ) { 63 | $warning = 'Do not use {} curly brackets in db_select() table names'; 64 | $phpcsFile->addWarning($warning, $argument['start'], 'DbSelectBrace'); 65 | } 66 | 67 | }//end processFunctionCall() 68 | 69 | 70 | }//end class 71 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/DefaultValueSanitizeSniff.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function registerFunctionNames() 34 | { 35 | return [ 36 | 'check_markup', 37 | 'check_plain', 38 | 'check_url', 39 | 'filter_xss', 40 | 'filter_xss_admin', 41 | ]; 42 | 43 | }//end registerFunctionNames() 44 | 45 | 46 | /** 47 | * Processes this function call. 48 | * 49 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 50 | * @param int $stackPtr The position of the function call in 51 | * the stack. 52 | * @param int $openBracket The position of the opening 53 | * parenthesis in the stack. 54 | * @param int $closeBracket The position of the closing 55 | * parenthesis in the stack. 56 | * 57 | * @return void 58 | */ 59 | public function processFunctionCall( 60 | File $phpcsFile, 61 | $stackPtr, 62 | $openBracket, 63 | $closeBracket 64 | ) { 65 | $tokens = $phpcsFile->getTokens(); 66 | 67 | // We assume that the sequence '#default_value' => check_plain(...) is 68 | // wrong because the Form API already sanitizes #default_value. 69 | $arrow = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); 70 | if ($arrow === false || $tokens[$arrow]['code'] !== T_DOUBLE_ARROW) { 71 | return; 72 | } 73 | 74 | $arrayKey = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($arrow - 1), null, true); 75 | if ($arrayKey === false 76 | || $tokens[$arrayKey]['code'] !== T_CONSTANT_ENCAPSED_STRING 77 | || substr($tokens[$arrayKey]['content'], 1, -1) !== '#default_value' 78 | ) { 79 | return; 80 | } 81 | 82 | $warning = 'Do not use the %s() sanitization function on Form API #default_value elements, they get escaped automatically'; 83 | $data = [$tokens[$stackPtr]['content']]; 84 | $phpcsFile->addWarning($warning, $stackPtr, 'DefaultValue', $data); 85 | 86 | }//end processFunctionCall() 87 | 88 | 89 | }//end class 90 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/FormErrorTSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function registerFunctionNames() 32 | { 33 | return [ 34 | 'form_set_error', 35 | 'form_error', 36 | ]; 37 | 38 | }//end registerFunctionNames() 39 | 40 | 41 | /** 42 | * Processes this function call. 43 | * 44 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 45 | * @param int $stackPtr The position of the function call in 46 | * the stack. 47 | * @param int $openBracket The position of the opening 48 | * parenthesis in the stack. 49 | * @param int $closeBracket The position of the closing 50 | * parenthesis in the stack. 51 | * 52 | * @return void 53 | */ 54 | public function processFunctionCall( 55 | File $phpcsFile, 56 | $stackPtr, 57 | $openBracket, 58 | $closeBracket 59 | ) { 60 | $tokens = $phpcsFile->getTokens(); 61 | $argument = $this->getArgument(2); 62 | if ($argument !== false && $tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING) { 63 | $warning = 'Form error messages are user facing text and must run through t() for translation'; 64 | $phpcsFile->addWarning($warning, $argument['start'], 'ErrorMessage'); 65 | } 66 | 67 | }//end processFunctionCall() 68 | 69 | 70 | }//end class 71 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/InsecureUnserializeSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function registerFunctionNames() 32 | { 33 | return ['unserialize']; 34 | 35 | }//end registerFunctionNames() 36 | 37 | 38 | /** 39 | * Processes this function call. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the function call in 43 | * the stack. 44 | * @param int $openBracket The position of the opening 45 | * parenthesis in the stack. 46 | * @param int $closeBracket The position of the closing 47 | * parenthesis in the stack. 48 | * 49 | * @return void 50 | */ 51 | public function processFunctionCall( 52 | File $phpcsFile, 53 | $stackPtr, 54 | $openBracket, 55 | $closeBracket 56 | ) { 57 | $tokens = $phpcsFile->getTokens(); 58 | $argument = $this->getArgument(2); 59 | if ($argument === false) { 60 | $this->fail($phpcsFile, $closeBracket); 61 | return; 62 | } 63 | 64 | $allowedClassesKeyStart = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $argument['start'], $argument['end'], false, '\'allowed_classes\''); 65 | if ($allowedClassesKeyStart === false) { 66 | $allowedClassesKeyStart = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $argument['start'], $argument['end'], false, '"allowed_classes"'); 67 | } 68 | 69 | if ($allowedClassesKeyStart === false) { 70 | $this->fail($phpcsFile, $argument['end']); 71 | return; 72 | } 73 | 74 | $allowedClassesArrow = $phpcsFile->findNext(T_DOUBLE_ARROW, $allowedClassesKeyStart, $argument['end'], false); 75 | if ($allowedClassesArrow === false) { 76 | $this->fail($phpcsFile, $argument['end']); 77 | return; 78 | } 79 | 80 | $allowedClassesValue = $phpcsFile->findNext(T_WHITESPACE, ($allowedClassesArrow + 1), $argument['end'], true); 81 | if ($tokens[$allowedClassesValue]['code'] === T_TRUE) { 82 | $this->fail($phpcsFile, $allowedClassesValue); 83 | } 84 | 85 | }//end processFunctionCall() 86 | 87 | 88 | /** 89 | * Record a violation of the standard. 90 | * 91 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 92 | * @param int $position The stack position of the violation. 93 | * 94 | * @return void 95 | */ 96 | protected function fail(File $phpcsFile, int $position) 97 | { 98 | $phpcsFile->addError('unserialize() is insecure unless allowed classes are limited. Use a safe format like JSON or use the allowed_classes option.', $position, 'InsecureUnserialize'); 99 | 100 | }//end fail() 101 | 102 | 103 | }//end class 104 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/LCheckPlainSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function registerFunctionNames() 32 | { 33 | return ['l']; 34 | 35 | }//end registerFunctionNames() 36 | 37 | 38 | /** 39 | * Processes this function call. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the function call in 43 | * the stack. 44 | * @param int $openBracket The position of the opening 45 | * parenthesis in the stack. 46 | * @param int $closeBracket The position of the closing 47 | * parenthesis in the stack. 48 | * 49 | * @return void 50 | */ 51 | public function processFunctionCall( 52 | File $phpcsFile, 53 | $stackPtr, 54 | $openBracket, 55 | $closeBracket 56 | ) { 57 | $tokens = $phpcsFile->getTokens(); 58 | $argument = $this->getArgument(1); 59 | if ($tokens[$argument['start']]['content'] === 'check_plain') { 60 | $warning = 'Do not use check_plain() on the first argument of l(), because l() will sanitize it for you by default'; 61 | $phpcsFile->addWarning($warning, $argument['start'], 'LCheckPlain'); 62 | } 63 | 64 | }//end processFunctionCall() 65 | 66 | 67 | }//end class 68 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/MessageTSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function registerFunctionNames() 32 | { 33 | return ['drupal_set_message']; 34 | 35 | }//end registerFunctionNames() 36 | 37 | 38 | /** 39 | * Processes this function call. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the function call in 43 | * the stack. 44 | * @param int $openBracket The position of the opening 45 | * parenthesis in the stack. 46 | * @param int $closeBracket The position of the closing 47 | * parenthesis in the stack. 48 | * 49 | * @return void 50 | */ 51 | public function processFunctionCall( 52 | File $phpcsFile, 53 | $stackPtr, 54 | $openBracket, 55 | $closeBracket 56 | ) { 57 | $tokens = $phpcsFile->getTokens(); 58 | $argument = $this->getArgument(1); 59 | if ($argument !== false && $tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING) { 60 | $warning = 'Messages are user facing text and must run through t() for translation'; 61 | $phpcsFile->addWarning($warning, $argument['start'], 'ErrorMessage'); 62 | } 63 | 64 | }//end processFunctionCall() 65 | 66 | 67 | }//end class 68 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/TCheckPlainSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function registerFunctionNames() 33 | { 34 | return [ 35 | 't', 36 | 'watchdog', 37 | ]; 38 | 39 | }//end registerFunctionNames() 40 | 41 | 42 | /** 43 | * Processes this function call. 44 | * 45 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 46 | * @param int $stackPtr The position of the function call in 47 | * the stack. 48 | * @param int $openBracket The position of the opening 49 | * parenthesis in the stack. 50 | * @param int $closeBracket The position of the closing 51 | * parenthesis in the stack. 52 | * 53 | * @return void 54 | */ 55 | public function processFunctionCall( 56 | File $phpcsFile, 57 | $stackPtr, 58 | $openBracket, 59 | $closeBracket 60 | ) { 61 | $tokens = $phpcsFile->getTokens(); 62 | if ($tokens[$stackPtr]['content'] === 't') { 63 | $argument = $this->getArgument(2); 64 | } else { 65 | // For watchdog() the placeholders are in the third argument. 66 | $argument = $this->getArgument(3); 67 | } 68 | 69 | if ($argument === false) { 70 | return; 71 | } 72 | 73 | if ($tokens[$argument['start']]['code'] !== T_ARRAY) { 74 | return; 75 | } 76 | 77 | $checkPlain = $argument['start']; 78 | while (($checkPlain = $phpcsFile->findNext(T_STRING, ($checkPlain + 1), $tokens[$argument['start']]['parenthesis_closer'])) !== false) { 79 | if ($tokens[$checkPlain]['content'] === 'check_plain') { 80 | // The check_plain() could be embedded with string concatenation, 81 | // which we want to allow. 82 | $previous = $phpcsFile->findPrevious(T_WHITESPACE, ($checkPlain - 1), $argument['start'], true); 83 | if ($previous === false || $tokens[$previous]['code'] !== T_STRING_CONCAT) { 84 | $warning = 'The extra check_plain() is not necessary for placeholders, "@" and "%" will automatically run check_plain()'; 85 | $phpcsFile->addWarning($warning, $checkPlain, 'CheckPlain'); 86 | } 87 | } 88 | } 89 | 90 | }//end processFunctionCall() 91 | 92 | 93 | }//end class 94 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/ThemeSniff.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | protected $reservedFunctions = [ 31 | 'theme_get_registry', 32 | 'theme_get_setting', 33 | 'theme_render_template', 34 | 'theme_enable', 35 | 'theme_disable', 36 | 'theme_get_suggestions', 37 | ]; 38 | 39 | 40 | /** 41 | * Processes this function call. 42 | * 43 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 44 | * @param int $stackPtr The position of the function call in 45 | * the stack. 46 | * 47 | * @return void 48 | */ 49 | public function process(File $phpcsFile, $stackPtr) 50 | { 51 | $tokens = $phpcsFile->getTokens(); 52 | $functionName = $tokens[$stackPtr]['content']; 53 | if (strpos($functionName, 'theme_') !== 0 54 | || in_array($functionName, $this->reservedFunctions) === true 55 | || $this->isFunctionCall($phpcsFile, $stackPtr) === false 56 | ) { 57 | return; 58 | } 59 | 60 | $themeName = substr($functionName, 6); 61 | $warning = "Do not call theme functions directly, use theme('%s', ...) instead"; 62 | $phpcsFile->addWarning($warning, $stackPtr, 'ThemeFunctionDirect', [$themeName]); 63 | 64 | }//end process() 65 | 66 | 67 | }//end class 68 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionCalls/VariableSetSanitizeSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function registerFunctionNames() 33 | { 34 | return ['variable_set']; 35 | 36 | }//end registerFunctionNames() 37 | 38 | 39 | /** 40 | * Processes this function call. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the function call in 44 | * the stack. 45 | * @param int $openBracket The position of the opening 46 | * parenthesis in the stack. 47 | * @param int $closeBracket The position of the closing 48 | * parenthesis in the stack. 49 | * 50 | * @return void 51 | */ 52 | public function processFunctionCall( 53 | File $phpcsFile, 54 | $stackPtr, 55 | $openBracket, 56 | $closeBracket 57 | ) { 58 | $tokens = $phpcsFile->getTokens(); 59 | 60 | $argument = $this->getArgument(2); 61 | if ($argument !== false && in_array( 62 | $tokens[$argument['start']]['content'], 63 | [ 64 | 'check_markup', 65 | 'check_plain', 66 | 'check_url', 67 | 'filter_xss', 68 | 'filter_xss_admin', 69 | ] 70 | ) === true 71 | ) { 72 | $warning = 'Do not use the %s() sanitization function when writing values to the database, use it on output to HTML instead'; 73 | $data = [$tokens[$argument['start']]['content']]; 74 | $phpcsFile->addWarning($warning, $argument['start'], 'VariableSet', $data); 75 | } 76 | 77 | }//end processFunctionCall() 78 | 79 | 80 | }//end class 81 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionDefinitions/FormAlterDocSniff.php: -------------------------------------------------------------------------------- 1 | getTokens(); 42 | $docCommentEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($functionPtr - 1), null, true); 43 | 44 | // If there is no doc comment there is nothing we can check. 45 | if ($docCommentEnd === false || $tokens[$docCommentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) { 46 | return; 47 | } 48 | 49 | $commentLine = ($docCommentEnd - 1); 50 | $commentFound = false; 51 | while ($tokens[$commentLine]['code'] !== T_DOC_COMMENT_OPEN_TAG) { 52 | if (strpos($tokens[$commentLine]['content'], 'Implements hook_form_alter().') === 0) { 53 | $commentFound = true; 54 | break; 55 | } 56 | 57 | $commentLine--; 58 | } 59 | 60 | if ($commentFound === false) { 61 | return; 62 | } 63 | 64 | $projectName = Project::getName($phpcsFile); 65 | if ($projectName === false) { 66 | return; 67 | } 68 | 69 | if ($tokens[$stackPtr]['content'] !== $projectName.'_form_alter') { 70 | $warning = 'Doc comment indicates hook_form_alter() but function signature is "%s" instead of "%s". Did you mean hook_form_FORM_ID_alter()?'; 71 | $data = [ 72 | $tokens[$stackPtr]['content'], 73 | $projectName.'_form_alter', 74 | ]; 75 | $phpcsFile->addWarning($warning, $commentLine, 'Different', $data); 76 | } 77 | 78 | }//end processFunction() 79 | 80 | 81 | }//end class 82 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionDefinitions/HookInitCssSniff.php: -------------------------------------------------------------------------------- 1 | getFilename(), -6)); 42 | // Only check in *.module files. 43 | if ($fileExtension !== 'module') { 44 | return ($phpcsFile->numTokens + 1); 45 | } 46 | 47 | // This check only applies to Drupal 7, not Drupal 6. 48 | if (Project::getCoreVersion($phpcsFile) !== 7) { 49 | return ($phpcsFile->numTokens + 1); 50 | } 51 | 52 | $fileName = substr(basename($phpcsFile->getFilename()), 0, -7); 53 | $tokens = $phpcsFile->getTokens(); 54 | if ($tokens[$stackPtr]['content'] !== ($fileName.'_init') && $tokens[$stackPtr]['content'] !== ($fileName.'_page_build')) { 55 | return; 56 | } 57 | 58 | // Search in the function body for drupal_add_css() calls. 59 | $string = $phpcsFile->findNext( 60 | T_STRING, 61 | $tokens[$functionPtr]['scope_opener'], 62 | $tokens[$functionPtr]['scope_closer'] 63 | ); 64 | while ($string !== false) { 65 | if ($tokens[$string]['content'] === 'drupal_add_css' || $tokens[$string]['content'] === 'drupal_add_js') { 66 | $opener = $phpcsFile->findNext( 67 | Tokens::$emptyTokens, 68 | ($string + 1), 69 | null, 70 | true 71 | ); 72 | if ($opener !== false 73 | && $tokens[$opener]['code'] === T_OPEN_PARENTHESIS 74 | ) { 75 | if ($tokens[$stackPtr]['content'] === ($fileName.'_init')) { 76 | $warning = 'Do not use %s() in hook_init(), use #attached for CSS and JS in your page/form callback or in hook_page_build() instead'; 77 | $phpcsFile->addWarning($warning, $string, 'AddFunctionFound', [$tokens[$string]['content']]); 78 | } else { 79 | $warning = 'Do not use %s() in hook_page_build(), use #attached for CSS and JS on the $page render array instead'; 80 | $phpcsFile->addWarning($warning, $string, 'AddFunctionFoundPageBuild', [$tokens[$string]['content']]); 81 | } 82 | } 83 | } 84 | 85 | $string = $phpcsFile->findNext( 86 | T_STRING, 87 | ($string + 1), 88 | $tokens[$functionPtr]['scope_closer'] 89 | ); 90 | }//end while 91 | 92 | }//end processFunction() 93 | 94 | 95 | }//end class 96 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/FunctionDefinitions/InstallTSniff.php: -------------------------------------------------------------------------------- 1 | getFilename(), -7)); 42 | // Only check in *.install files. 43 | if ($fileExtension !== 'install') { 44 | return ($phpcsFile->numTokens + 1); 45 | } 46 | 47 | $fileName = substr(basename($phpcsFile->getFilename()), 0, -8); 48 | $tokens = $phpcsFile->getTokens(); 49 | if ($tokens[$stackPtr]['content'] !== ($fileName.'_install') 50 | && $tokens[$stackPtr]['content'] !== ($fileName.'_requirements') 51 | ) { 52 | return; 53 | } 54 | 55 | // This check only applies to Drupal 7, not Drupal 8. 56 | if (Project::getCoreVersion($phpcsFile) !== 7) { 57 | return ($phpcsFile->numTokens + 1); 58 | } 59 | 60 | // Search in the function body for t() calls. 61 | $string = $phpcsFile->findNext( 62 | T_STRING, 63 | $tokens[$functionPtr]['scope_opener'], 64 | $tokens[$functionPtr]['scope_closer'] 65 | ); 66 | while ($string !== false) { 67 | if ($tokens[$string]['content'] === 't' || $tokens[$string]['content'] === 'st') { 68 | $opener = $phpcsFile->findNext( 69 | Tokens::$emptyTokens, 70 | ($string + 1), 71 | null, 72 | true 73 | ); 74 | if ($opener !== false 75 | && $tokens[$opener]['code'] === T_OPEN_PARENTHESIS 76 | ) { 77 | $error = 'Do not use t() or st() in installation phase hooks, use $t = get_t() to retrieve the appropriate localization function name'; 78 | $phpcsFile->addError($error, $string, 'TranslationFound'); 79 | } 80 | } 81 | 82 | $string = $phpcsFile->findNext( 83 | T_STRING, 84 | ($string + 1), 85 | $tokens[$functionPtr]['scope_closer'] 86 | ); 87 | }//end while 88 | 89 | }//end processFunction() 90 | 91 | 92 | }//end class 93 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/General/AccessAdminPagesSniff.php: -------------------------------------------------------------------------------- 1 | getFilename(), -6)); 41 | // Only check in *.module files. 42 | if ($fileExtension !== 'module') { 43 | return; 44 | } 45 | 46 | $tokens = $phpcsFile->getTokens(); 47 | 48 | $fileName = substr(basename($phpcsFile->getFilename()), 0, -7); 49 | if ($tokens[$stackPtr]['content'] !== ($fileName.'_menu')) { 50 | return; 51 | } 52 | 53 | // Search in the function body for "access administration pages" strings. 54 | $string = $phpcsFile->findNext( 55 | T_CONSTANT_ENCAPSED_STRING, 56 | $tokens[$functionPtr]['scope_opener'], 57 | $tokens[$functionPtr]['scope_closer'] 58 | ); 59 | 60 | while ($string !== false) { 61 | if (substr($tokens[$string]['content'], 1, -1) === 'access administration pages') { 62 | $warning = 'The administration menu callback should probably use "administer site configuration" - which implies the user can change something - rather than "access administration pages" which is about viewing but not changing configurations.'; 63 | $phpcsFile->addWarning($warning, $string, 'PermissionFound'); 64 | } 65 | 66 | $string = $phpcsFile->findNext( 67 | T_CONSTANT_ENCAPSED_STRING, 68 | ($string + 1), 69 | $tokens[$functionPtr]['scope_closer'] 70 | ); 71 | }//end while 72 | 73 | }//end processFunction() 74 | 75 | 76 | }//end class 77 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/General/ClassNameSniff.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function register() 34 | { 35 | return [ 36 | T_CLASS, 37 | T_INTERFACE, 38 | ]; 39 | 40 | }//end register() 41 | 42 | 43 | /** 44 | * Processes this test, when one of its tokens is encountered. 45 | * 46 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 47 | * @param int $stackPtr The position of the function 48 | * name in the stack. 49 | * 50 | * @return void 51 | */ 52 | public function process(File $phpcsFile, $stackPtr) 53 | { 54 | // If there is a PHP 5.3 namespace declaration in the file we return 55 | // immediately as classes can be named arbitrary within a namespace. 56 | $namespace = $phpcsFile->findPrevious(T_NAMESPACE, ($stackPtr - 1)); 57 | if ($namespace !== false) { 58 | return; 59 | } 60 | 61 | $moduleName = Project::getName($phpcsFile); 62 | if ($moduleName === false) { 63 | return; 64 | } 65 | 66 | $tokens = $phpcsFile->getTokens(); 67 | 68 | $className = $phpcsFile->findNext(T_STRING, $stackPtr); 69 | $name = trim($tokens[$className]['content']); 70 | 71 | // Underscores are omitted in class names. Also convert all characters 72 | // to lower case to compare them later. 73 | $classPrefix = strtolower(str_replace('_', '', $moduleName)); 74 | // Views classes might have underscores in the name, which is also fine. 75 | $viewsPrefix = strtolower($moduleName); 76 | $name = strtolower($name); 77 | 78 | if (strpos($name, $classPrefix) !== 0 && strpos($name, $viewsPrefix) !== 0) { 79 | $warning = '%s name must be prefixed with the project name "%s"'; 80 | $nameParts = explode('_', $moduleName); 81 | $camelName = ''; 82 | foreach ($nameParts as &$part) { 83 | $camelName .= ucfirst($part); 84 | } 85 | 86 | $errorData = [ 87 | ucfirst($tokens[$stackPtr]['content']), 88 | $camelName, 89 | ]; 90 | $phpcsFile->addWarning($warning, $className, 'ClassPrefix', $errorData); 91 | } 92 | 93 | }//end process() 94 | 95 | 96 | }//end class 97 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/General/DescriptionTSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [T_CONSTANT_ENCAPSED_STRING]; 34 | 35 | }//end register() 36 | 37 | 38 | /** 39 | * Processes this test, when one of its tokens is encountered. 40 | * 41 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 42 | * @param int $stackPtr The position of the function 43 | * name in the stack. 44 | * 45 | * @return void 46 | */ 47 | public function process(File $phpcsFile, $stackPtr) 48 | { 49 | // Look for the string "#description". 50 | $tokens = $phpcsFile->getTokens(); 51 | if ($tokens[$stackPtr]['content'] !== '"#description"' && $tokens[$stackPtr]['content'] !== "'#description'") { 52 | return; 53 | } 54 | 55 | // Look for an array pattern that starts to define #description values. 56 | $statementEnd = $phpcsFile->findNext(T_SEMICOLON, ($stackPtr + 1)); 57 | $arrayString = $phpcsFile->getTokensAsString(($stackPtr + 1), ($statementEnd - $stackPtr)); 58 | // Cut out all the white space. 59 | $arrayString = preg_replace('/\s+/', '', $arrayString); 60 | 61 | if (strpos($arrayString, '=>"') !== 0 && strpos($arrayString, ']="') !== 0 62 | && strpos($arrayString, "=>'") !== 0 && strpos($arrayString, "]='") !== 0 63 | ) { 64 | return; 65 | } 66 | 67 | $stringToken = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($stackPtr + 1)); 68 | $content = strip_tags($tokens[$stringToken]['content']); 69 | 70 | if (strlen($content) > 5) { 71 | $warning = '#description values usually have to run through t() for translation'; 72 | $phpcsFile->addWarning($warning, $stringToken, 'DescriptionT'); 73 | } 74 | 75 | }//end process() 76 | 77 | 78 | }//end class 79 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/General/ExceptionTSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [T_THROW]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the function 44 | * name in the stack. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | 51 | $tokens = $phpcsFile->getTokens(); 52 | $endPtr = $phpcsFile->findEndOfStatement($stackPtr); 53 | 54 | $newPtr = $phpcsFile->findNext(T_NEW, ($stackPtr + 1), $endPtr); 55 | if ($newPtr !== false) { 56 | $openPtr = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($newPtr + 1), $endPtr); 57 | if ($openPtr !== false) { 58 | for ($i = ($openPtr + 1); $i < $tokens[$openPtr]['parenthesis_closer']; $i++) { 59 | if ($tokens[$i]['code'] === T_STRING && $tokens[$i]['content'] === 't') { 60 | $warning = 'Exceptions should not be translated'; 61 | $phpcsFile->addWarning($warning, $stackPtr, 'ExceptionT'); 62 | return; 63 | } 64 | } 65 | } 66 | } 67 | 68 | }//end process() 69 | 70 | 71 | }//end class 72 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/General/FormStateInputSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [T_VARIABLE]; 35 | 36 | }//end register() 37 | 38 | 39 | /** 40 | * Processes this test, when one of its tokens is encountered. 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the function 44 | * name in the stack. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | if ($phpcsFile->getTokensAsString($stackPtr, 4) === '$form_state[\'input\']' 51 | || $phpcsFile->getTokensAsString($stackPtr, 4) === '$form_state["input"]' 52 | ) { 53 | $warning = 'Do not use the raw $form_state[\'input\'], use $form_state[\'values\'] instead where possible'; 54 | $phpcsFile->addWarning($warning, $stackPtr, 'Input'); 55 | } 56 | 57 | }//end process() 58 | 59 | 60 | }//end class 61 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/General/LanguageNoneSniff.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function register() 32 | { 33 | return [ 34 | T_OPEN_SQUARE_BRACKET, 35 | T_OPEN_SHORT_ARRAY, 36 | ]; 37 | 38 | }//end register() 39 | 40 | 41 | /** 42 | * Processes this test, when one of its tokens is encountered. 43 | * 44 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 45 | * @param int $stackPtr The position of the function 46 | * name in the stack. 47 | * 48 | * @return void 49 | */ 50 | public function process(File $phpcsFile, $stackPtr) 51 | { 52 | $sequence = $phpcsFile->getTokensAsString($stackPtr, 3); 53 | if ($sequence === "['und']" || $sequence === '["und"]') { 54 | $warning = "Are you accessing field values here? Then you should use LANGUAGE_NONE instead of 'und'"; 55 | $phpcsFile->addWarning($warning, ($stackPtr + 1), 'Und'); 56 | } 57 | 58 | }//end process() 59 | 60 | 61 | }//end class 62 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/General/VariableNameSniff.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function registerFunctionNames() 34 | { 35 | return ['variable_get']; 36 | 37 | }//end registerFunctionNames() 38 | 39 | 40 | /** 41 | * Processes this function call. 42 | * 43 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 44 | * @param int $stackPtr The position of the function call in 45 | * the stack. 46 | * @param int $openBracket The position of the opening 47 | * parenthesis in the stack. 48 | * @param int $closeBracket The position of the closing 49 | * parenthesis in the stack. 50 | * 51 | * @return void 52 | */ 53 | public function processFunctionCall( 54 | File $phpcsFile, 55 | $stackPtr, 56 | $openBracket, 57 | $closeBracket 58 | ) { 59 | $tokens = $phpcsFile->getTokens(); 60 | 61 | // We assume that the sequence '#default_value' => variable_get(...) 62 | // indicates a variable that the module owns. 63 | $arrow = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); 64 | if ($arrow === false || $tokens[$arrow]['code'] !== T_DOUBLE_ARROW) { 65 | return; 66 | } 67 | 68 | $arrayKey = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($arrow - 1), null, true); 69 | if ($arrayKey === false 70 | || $tokens[$arrayKey]['code'] !== T_CONSTANT_ENCAPSED_STRING 71 | || substr($tokens[$arrayKey]['content'], 1, -1) !== '#default_value' 72 | ) { 73 | return; 74 | } 75 | 76 | $argument = $this->getArgument(1); 77 | 78 | // Variable name is not a literal string, so we return early. 79 | if ($argument === false || $tokens[$argument['start']]['code'] !== T_CONSTANT_ENCAPSED_STRING) { 80 | return; 81 | } 82 | 83 | $moduleName = Project::getName($phpcsFile); 84 | if ($moduleName === false) { 85 | return; 86 | } 87 | 88 | $variableName = substr($tokens[$argument['start']]['content'], 1, -1); 89 | if (strpos($variableName, $moduleName) !== 0) { 90 | $warning = 'All variables defined by your module must be prefixed with your module\'s name to avoid name collisions with others. Expected start with "%s" but found "%s"'; 91 | $data = [ 92 | $moduleName, 93 | $variableName, 94 | ]; 95 | $phpcsFile->addWarning($warning, $argument['start'], 'VariableName', $data); 96 | } 97 | 98 | }//end processFunctionCall() 99 | 100 | 101 | }//end class 102 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/InfoFiles/CoreVersionRequirementSniff.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | public function register() 35 | { 36 | return [T_INLINE_HTML]; 37 | 38 | }//end register() 39 | 40 | 41 | /** 42 | * Processes this test, when one of its tokens is encountered. 43 | * 44 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed. 45 | * @param int $stackPtr The position of the current token 46 | * in the stack passed in $tokens. 47 | * 48 | * @return int 49 | */ 50 | public function process(File $phpcsFile, $stackPtr) 51 | { 52 | $filename = $phpcsFile->getFilename(); 53 | $fileExtension = strtolower(substr($filename, -9)); 54 | if ($fileExtension !== '.info.yml') { 55 | return ($phpcsFile->numTokens + 1); 56 | } 57 | 58 | // Exclude config files which might contain the info.yml extension. 59 | $filenameWithoutExtension = substr($filename, 0, -9); 60 | if (strpos($filenameWithoutExtension, '.') !== false) { 61 | return ($phpcsFile->numTokens + 1); 62 | } 63 | 64 | $contents = file_get_contents($phpcsFile->getFilename()); 65 | try { 66 | $info = Yaml::parse($contents); 67 | } catch (ParseException $e) { 68 | // If the YAML is invalid we ignore this file. 69 | return ($phpcsFile->numTokens + 1); 70 | } 71 | 72 | // Check if the type key is set, to verify if we're inside a project info.yml file. 73 | if (isset($info['type']) === false) { 74 | return ($phpcsFile->numTokens + 1); 75 | } 76 | 77 | // Test modules can omit the core_version_requirement key. 78 | if (isset($info['package']) === true && $info['package'] === 'Testing') { 79 | return ($phpcsFile->numTokens + 1); 80 | } 81 | 82 | if (isset($info['core_version_requirement']) === false) { 83 | $warning = '"core_version_requirement" property is missing in the info.yml file'; 84 | $phpcsFile->addWarning($warning, $stackPtr, 'CoreVersionRequirement'); 85 | } 86 | 87 | return ($phpcsFile->numTokens + 1); 88 | 89 | }//end process() 90 | 91 | 92 | }//end class 93 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/InfoFiles/DescriptionSniff.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | public function register() 35 | { 36 | return [T_INLINE_HTML]; 37 | 38 | }//end register() 39 | 40 | 41 | /** 42 | * Processes this test, when one of its tokens is encountered. 43 | * 44 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed. 45 | * @param int $stackPtr The position of the current token 46 | * in the stack passed in $tokens. 47 | * 48 | * @return int 49 | */ 50 | public function process(File $phpcsFile, $stackPtr) 51 | { 52 | $filename = $phpcsFile->getFilename(); 53 | $fileExtension = strtolower(substr($filename, -9)); 54 | if ($fileExtension !== '.info.yml') { 55 | return ($phpcsFile->numTokens + 1); 56 | } 57 | 58 | // Exclude config files which might contain the info.yml extension. 59 | $filenameWithoutExtension = substr($filename, 0, -9); 60 | if (strpos($filenameWithoutExtension, '.') !== false) { 61 | return ($phpcsFile->numTokens + 1); 62 | } 63 | 64 | try { 65 | $info = Yaml::parseFile($phpcsFile->getFilename()); 66 | } catch (ParseException $e) { 67 | // If the YAML is invalid we ignore this file. 68 | return ($phpcsFile->numTokens + 1); 69 | } 70 | 71 | // Check if the type key is set, to verify if we're inside a project info.yml file. 72 | if (isset($info['type']) === false) { 73 | return ($phpcsFile->numTokens + 1); 74 | } 75 | 76 | if (isset($info['description']) === false) { 77 | $warning = '"Description" property is missing in the info.yml file'; 78 | $phpcsFile->addWarning($warning, $stackPtr, 'Missing'); 79 | } else if ($info['description'] === '') { 80 | $warning = '"Description" should not be empty'; 81 | $phpcsFile->addWarning($warning, $stackPtr, 'Empty'); 82 | } 83 | 84 | return ($phpcsFile->numTokens + 1); 85 | 86 | }//end process() 87 | 88 | 89 | }//end class 90 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/InfoFiles/NamespacedDependencySniff.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | public function register() 35 | { 36 | return [T_INLINE_HTML]; 37 | 38 | }//end register() 39 | 40 | 41 | /** 42 | * Processes this test, when one of its tokens is encountered. 43 | * 44 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed. 45 | * @param int $stackPtr The position of the current token 46 | * in the stack passed in $tokens. 47 | * 48 | * @return int|void 49 | */ 50 | public function process(File $phpcsFile, $stackPtr) 51 | { 52 | $tokens = $phpcsFile->getTokens(); 53 | 54 | $fileExtension = strtolower(substr($phpcsFile->getFilename(), -9)); 55 | if ($fileExtension !== '.info.yml') { 56 | return ($phpcsFile->numTokens + 1); 57 | } 58 | 59 | $contents = file_get_contents($phpcsFile->getFilename()); 60 | try { 61 | $info = Yaml::parse($contents); 62 | // Themes are allowed to have not namespaced dependencies, see 63 | // https://www.drupal.org/project/drupal/issues/474684. 64 | if (isset($info['type']) === true && $info['type'] === 'theme') { 65 | return ($phpcsFile->numTokens + 1); 66 | } 67 | } catch (ParseException $e) { 68 | // If the YAML is invalid we ignore this file. 69 | return ($phpcsFile->numTokens + 1); 70 | } 71 | 72 | if (preg_match('/^dependencies:/', $tokens[$stackPtr]['content']) === 0) { 73 | return; 74 | } 75 | 76 | $nextLine = ($stackPtr + 1); 77 | 78 | while (isset($tokens[$nextLine]) === true) { 79 | // Dependency line without namespace. 80 | if (preg_match('/^[\s]+- [^:]+[\s]*$/', $tokens[$nextLine]['content']) === 1) { 81 | $error = 'All dependencies must be prefixed with the project name, for example "drupal:"'; 82 | $phpcsFile->addWarning($error, $nextLine, 'NonNamespaced'); 83 | } else if (preg_match('/^[\s]+- [^:]+:[^:]+[\s]*$/', $tokens[$nextLine]['content']) === 0 84 | && preg_match('/^[\s]*#.*$/', $tokens[$nextLine]['content']) === 0 85 | ) { 86 | // Not a dependency line with namespace or comment - stop. 87 | return $nextLine; 88 | } 89 | 90 | $nextLine++; 91 | } 92 | 93 | }//end process() 94 | 95 | 96 | }//end class 97 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/Variables/GetRequestDataSniff.php: -------------------------------------------------------------------------------- 1 | 35 | */ 36 | public function register() 37 | { 38 | return [T_VARIABLE]; 39 | 40 | }//end register() 41 | 42 | 43 | /** 44 | * Processes this test, when one of its tokens is encountered. 45 | * 46 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 47 | * @param int $stackPtr The position of the current token 48 | * in the stack passed in $tokens. 49 | * 50 | * @return void|int 51 | */ 52 | public function process(File $phpcsFile, $stackPtr) 53 | { 54 | if (Project::getCoreVersion($phpcsFile) < 8) { 55 | // No need to check this file again, mark it as done. 56 | return ($phpcsFile->numTokens + 1); 57 | } 58 | 59 | $tokens = $phpcsFile->getTokens(); 60 | $varName = $tokens[$stackPtr]['content']; 61 | if ($varName !== '$_REQUEST' 62 | && $varName !== '$_GET' 63 | && $varName !== '$_POST' 64 | && $varName !== '$_FILES' 65 | && $varName !== '$_COOKIE' 66 | ) { 67 | return; 68 | } 69 | 70 | // If we get to here, the super global was used incorrectly. 71 | // First find out how it is being used. 72 | $usedVar = ''; 73 | 74 | $openBracket = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); 75 | if ($tokens[$openBracket]['code'] === T_OPEN_SQUARE_BRACKET) { 76 | $closeBracket = $tokens[$openBracket]['bracket_closer']; 77 | $usedVar = $phpcsFile->getTokensAsString(($openBracket + 1), ($closeBracket - $openBracket - 1)); 78 | } 79 | 80 | $requestPropertyMap = [ 81 | '$_REQUEST' => 'request', 82 | '$_GET' => 'query', 83 | '$_POST' => 'request', 84 | '$_FILES' => 'files', 85 | '$_COOKIE' => 'cookies', 86 | ]; 87 | 88 | $type = 'SuperglobalAccessed'; 89 | $error = 'The %s super global must not be accessed directly; inject the request_stack service and use $stack->getCurrentRequest()->%s'; 90 | $data = [ 91 | $varName, 92 | $requestPropertyMap[$varName], 93 | ]; 94 | 95 | if ($usedVar !== '') { 96 | $type .= 'WithVar'; 97 | $error .= '->get(%s)'; 98 | $data[] = $usedVar; 99 | } 100 | 101 | $error .= ' instead'; 102 | $phpcsFile->addError($error, $stackPtr, $type, $data); 103 | 104 | }//end process() 105 | 106 | 107 | }//end class 108 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/Sniffs/Yaml/RoutingAccessSniff.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | public function register() 35 | { 36 | return [T_INLINE_HTML]; 37 | 38 | }//end register() 39 | 40 | 41 | /** 42 | * Processes this test, when one of its tokens is encountered. 43 | * 44 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed. 45 | * @param int $stackPtr The position of the current token 46 | * in the stack passed in $tokens. 47 | * 48 | * @return void|int 49 | */ 50 | public function process(File $phpcsFile, $stackPtr) 51 | { 52 | $tokens = $phpcsFile->getTokens(); 53 | 54 | $fileExtension = strtolower(substr($phpcsFile->getFilename(), -12)); 55 | if ($fileExtension !== '.routing.yml') { 56 | return ($phpcsFile->numTokens + 1); 57 | } 58 | 59 | if (preg_match('/^[\s]+_access: \'TRUE\'/', $tokens[$stackPtr]['content']) === 1 60 | && isset($tokens[($stackPtr - 1)]) === true 61 | && preg_match('/^[\s]*#/', $tokens[($stackPtr - 1)]['content']) === 0 62 | ) { 63 | $warning = 'Open page callback found, please add a comment before the line why there is no access restriction'; 64 | $phpcsFile->addWarning($warning, $stackPtr, 'OpenCallback'); 65 | } 66 | 67 | if (preg_match('/^[\s]+_permission: \'access administration pages\'/', $tokens[$stackPtr]['content']) === 1) { 68 | $warning = 'The administration page callback should probably use "administer site configuration" - which implies the user can change something - rather than "access administration pages" which is about viewing but not changing configurations.'; 69 | $phpcsFile->addWarning($warning, $stackPtr, 'PermissionFound'); 70 | } 71 | 72 | }//end process() 73 | 74 | 75 | }//end class 76 | -------------------------------------------------------------------------------- /coder_sniffer/DrupalPractice/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Drupal best practice checks 4 | 5 | 6 | 7 | 8 | 9 | 10 | * 11 | 12 | 13 | 14 | 15 | *.tpl.php 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | */\.git/* 26 | */\.svn/* 27 | */\.hg/* 28 | */\.bzr/* 29 | 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drupal/coder", 3 | "type": "phpcodesniffer-standard", 4 | "description": "Coder is a library to review Drupal code.", 5 | "homepage": "https://www.drupal.org/project/coder", 6 | "support": { 7 | "issues": "https://www.drupal.org/project/issues/coder", 8 | "source": "https://www.drupal.org/project/coder" 9 | }, 10 | "keywords": [ 11 | "phpcs", 12 | "standards", 13 | "code review" 14 | ], 15 | "license": "GPL-2.0-or-later", 16 | "require": { 17 | "php": ">=7.2", 18 | "ext-mbstring": "*", 19 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1 || ^1.0.0", 20 | "sirbrillig/phpcs-variable-analysis": "^2.11.7", 21 | "slevomat/coding-standard": "^8.11", 22 | "squizlabs/php_codesniffer": "^3.13", 23 | "symfony/yaml": ">=3.4.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Drupal\\": "coder_sniffer/Drupal/", 28 | "DrupalPractice\\": "coder_sniffer/DrupalPractice/" 29 | } 30 | }, 31 | "config": { 32 | "allow-plugins": { 33 | "dealerdirect/phpcodesniffer-composer-installer": true 34 | }, 35 | "sort-packages": true 36 | }, 37 | "require-dev": { 38 | "phpstan/phpstan": "^1.7.12", 39 | "phpunit/phpunit": "^8.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 6 3 | paths: 4 | - coder_sniffer 5 | - tests 6 | excludePaths: 7 | # Test files that should not be checked. 8 | - 'tests/*/*.tpl.php' 9 | - 'tests/*/*.api.php' 10 | - 'tests/*/good.php' 11 | - 'tests/*/bad.php' 12 | - 'tests/*/drupal[678]/*.php' 13 | bootstrapFiles: 14 | - tests/Drupal/phpunit-bootstrap.php 15 | # PHPStan does not find the constants in this file, so load it manually. 16 | - vendor/squizlabs/php_codesniffer/src/Util/Tokens.php 17 | ignoreErrors: 18 | # PHPStan does not support variable variables, see https://github.com/phpstan/phpstan/issues/2810 19 | - 20 | count: 3 21 | message: '~^Undefined variable: \$value[123]$~' 22 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/ClassFilesSniff.php 23 | - 24 | count: 3 25 | message: '~^Undefined variable: \$value[123]$~' 26 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/DuplicateEntrySniff.php 27 | - 28 | count: 1 29 | message: '~^Variable \$key might not be defined.$~' 30 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/ClassFilesSniff.php 31 | - 32 | count: 1 33 | message: '~^Variable \$key might not be defined.$~' 34 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/DuplicateEntrySniff.php 35 | - 36 | message: "#^Result of \\|\\| is always true\\.$#" 37 | count: 1 38 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/ClassFilesSniff.php 39 | - 40 | message: "#^Offset mixed on array\\{\\} in isset\\(\\) does not exist\\.$#" 41 | count: 1 42 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/ClassFilesSniff.php 43 | - 44 | message: "#^Result of \\|\\| is always true\\.$#" 45 | count: 1 46 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/DuplicateEntrySniff.php 47 | - 48 | message: "#^Strict comparison using \\=\\=\\= between false and true will always evaluate to false\\.$#" 49 | count: 1 50 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/DuplicateEntrySniff.php 51 | - 52 | message: "#^Call to function array_key_exists\\(\\) with mixed and array\\{\\} will always evaluate to false\\.$#" 53 | count: 1 54 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/DuplicateEntrySniff.php 55 | - 56 | message: "#^Offset mixed on array\\{\\} in isset\\(\\) does not exist\\.$#" 57 | count: 1 58 | path: coder_sniffer/Drupal/Sniffs/InfoFiles/DuplicateEntrySniff.php 59 | # We don't want to introduce abstract methods for the 2 calls because 60 | # the class is used in different ways where those methods are not 61 | # always necessary for implementors. 62 | - 63 | count: 2 64 | message: '~^Call to an undefined method Drupal\\Sniffs\\Semantics\\FunctionCall::(registerFunctionNames|processFunctionCall)\(\).$~' 65 | path: coder_sniffer/Drupal/Sniffs/Semantics/FunctionCall.php 66 | # PHPStan Bug https://github.com/phpstan/phpstan/issues/6811 67 | - 68 | message: "#^Strict comparison using \\=\\=\\= between mixed and 0 will always evaluate to false\\.$#" 69 | count: 1 70 | path: coder_sniffer/Drupal/Sniffs/Formatting/MultipleStatementAlignmentSniff.php 71 | --------------------------------------------------------------------------------