<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2020 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/
namespace PHPCompatibility\Sniffs\Classes;
use PHPCompatibility\Helpers\ComplexVersionNewFeatureTrait;
use PHPCompatibility\Helpers\ResolveHelper;
use PHPCompatibility\Helpers\ScannedCode;
use PHPCompatibility\Sniff;
use PHP_CodeSniffer\Files\File;
use PHPCSUtils\Exceptions\ValueError;
use PHPCSUtils\Tokens\Collections;
use PHPCSUtils\Utils\Constants;
use PHPCSUtils\Utils\ControlStructures;
use PHPCSUtils\Utils\FunctionDeclarations;
use PHPCSUtils\Utils\ObjectDeclarations;
use PHPCSUtils\Utils\Parentheses;
use PHPCSUtils\Utils\TypeString;
use PHPCSUtils\Utils\UseStatements;
use PHPCSUtils\Utils\Variables;
/**
* Detect use of new PHP native classes.
*
* The sniff analyses the following constructs to find usage of new classes:
* - Class instantiation using the `new` keyword.
* - (Anonymous) Class declarations to detect new classes being extended by userland classes.
* - Static use of class properties, constants or functions using the double colon.
* - Function/closure declarations to detect new classes used as parameter type declarations.
* - Function/closure declarations to detect new classes used as return type declarations.
* - Property declarations to detect new classes used as property type declarations.
* - Try/catch statements to detect new exception classes being caught.
*
* PHP version All
*
* @since 5.5
* @since 5.6 Now extends the base `Sniff` class.
* @since 7.1.0 Now extends the `AbstractNewFeatureSniff` class.
* @since 10.0.0 - Now extends the base `Sniff` class and uses the `ComplexVersionNewFeatureTrait`.
* - This class is now `final`.
*/
final class NewClassesSniff extends Sniff
{
use ComplexVersionNewFeatureTrait;
/**
* A list of new classes, not present in older versions.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the class appears.
*
* @since 5.5
*
* @var array<string, array<string, bool|string>>
*/
protected $newClasses = [
'ArrayObject' => [
'4.4' => false,
'5.0' => true,
'extension' => 'spl',
],
'ArrayIterator' => [
'4.4' => false,
'5.0' => true,
'extension' => 'spl',
],
'CachingIterator' => [
'4.4' => false,
'5.0' => true,
'extension' => 'spl',
],
'DirectoryIterator' => [
'4.4' => false,
'5.0' => true,
'extension' => 'spl',
],
'RecursiveDirectoryIterator' => [
'4.4' => false,
'5.0' => true,
'extension' => 'spl',
],
'RecursiveIteratorIterator' => [
'4.4' => false,
'5.0' => true,
'extension' => 'spl',
],
'php_user_filter' => [
'4.4' => false,
'5.0' => true,
],
'tidy' => [
'4.4' => false,
'5.0' => true,
'extension' => 'tidy',
],
'tidyNode' => [
'4.4' => false,
'5.0' => true,
'extension' => 'tidy',
],
'Reflection' => [
'4.4' => false,
'5.0' => true,
'extension' => 'reflection',
],
'ReflectionClass' => [
'4.4' => false,
'5.0' => true,
'extension' => 'reflection',
],
'ReflectionExtension' => [
'4.4' => false,
'5.0' => true,
'extension' => 'reflection',
],
'ReflectionFunction' => [
'4.4' => false,
'5.0' => true,
'extension' => 'reflection',
],
'ReflectionMethod' => [
'4.4' => false,
'5.0' => true,
'extension' => 'reflection',
],
'ReflectionObject' => [
'4.4' => false,
'5.0' => true,
'extension' => 'reflection',
],
'ReflectionParameter' => [
'4.4' => false,
'5.0' => true,
'extension' => 'reflection',
],
'ReflectionProperty' => [
'4.4' => false,
'5.0' => true,
'extension' => 'reflection',
],
'SoapClient' => [
'4.4' => false,
'5.0' => true,
'extension' => 'soap',
],
'SoapServer' => [
'4.4' => false,
'5.0' => true,
'extension' => 'soap',
],
'SoapHeader' => [
'4.4' => false,
'5.0' => true,
'extension' => 'soap',
],
'SoapParam' => [
'4.4' => false,
'5.0' => true,
'extension' => 'soap',
],
'SoapVar' => [
'4.4' => false,
'5.0' => true,
'extension' => 'soap',
],
'COMPersistHelper' => [
'4.4' => false,
'5.0' => true,
],
'DOMAttr' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMCdataSection' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMCharacterData' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMComment' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMDocument' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMDocumentFragment' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMDocumentType' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMElement' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMEntity' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMEntityReference' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMImplementation' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMNamedNodeMap' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMNode' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMNodeList' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMNotation' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMProcessingInstruction' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMText' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'DOMXPath' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'SimpleXMLElement' => [
'4.4' => false,
'5.0' => true,
'extension' => 'simplexml',
],
'XSLTProcessor' => [
'4.4' => false,
'5.0' => true,
'extension' => 'xsl',
],
'SQLiteDatabase' => [
'4.4' => false,
'5.0' => true,
'extension' => 'sqlite',
],
'SQLiteResult' => [
'4.4' => false,
'5.0' => true,
'extension' => 'sqlite',
],
'SQLiteUnbuffered' => [
'4.4' => false,
'5.0' => true,
'extension' => 'sqlite',
],
'mysqli' => [
'4.4' => false,
'5.0' => true,
'extension' => 'mysqli',
],
'mysqli_stmt' => [
'4.4' => false,
'5.0' => true,
'extension' => 'mysqli',
],
'mysqli_result' => [
'4.4' => false,
'5.0' => true,
'extension' => 'mysqli',
],
'mysqli_driver' => [
'4.4' => false,
'5.0' => true,
'extension' => 'mysqli',
],
'mysqli_warning' => [
'4.4' => false,
'5.0' => true,
'extension' => 'mysqli',
],
/*
See: https://bugs.php.net/bug.php?id=79625
'OCI-Collection' => [
'4.4' => false,
'5.0' => true,
],
'OCI-Lob' => [
'4.4' => false,
'5.0' => true,
],
*/
'libXMLError' => [
'5.0' => false,
'5.1' => true,
'extension' => 'libxml',
],
'PDO' => [
'5.0' => false,
'5.1' => true,
'extension' => 'pdo',
],
'PDOStatement' => [
'5.0' => false,
'5.1' => true,
'extension' => 'pdo',
],
'AppendIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'EmptyIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'FilterIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'InfiniteIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'IteratorIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'LimitIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'NoRewindIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'ParentIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'RecursiveArrayIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'RecursiveCachingIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'RecursiveFilterIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'SimpleXMLIterator' => [
'5.0' => false,
'5.1' => true,
'extension' => 'simplexml',
],
'SplFileObject' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'SplObjectStorage' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'XMLReader' => [
'5.0' => false,
'5.1' => true,
'extension' => 'xmlreader',
],
'SplFileInfo' => [
'5.1.1' => false,
'5.1.2' => true,
'extension' => 'spl',
],
'SplTempFileObject' => [
'5.1.1' => false,
'5.1.2' => true,
'extension' => 'spl',
],
'XMLWriter' => [
'5.1.1' => false,
'5.1.2' => true,
'extension' => 'xmlwriter',
],
'DateTime' => [
'5.1' => false,
'5.2' => true,
],
'DateTimeZone' => [
'5.1' => false,
'5.2' => true,
],
'RegexIterator' => [
'5.1' => false,
'5.2' => true,
'extension' => 'spl',
],
'RecursiveRegexIterator' => [
'5.1' => false,
'5.2' => true,
'extension' => 'spl',
],
'ReflectionFunctionAbstract' => [
'5.1' => false,
'5.2' => true,
'extension' => 'reflection',
],
'ZipArchive' => [
'5.1' => false,
'5.2' => true,
'extension' => 'zip',
],
'Closure' => [
'5.2' => false,
'5.3' => true,
],
'DateInterval' => [
'5.2' => false,
'5.3' => true,
],
'DatePeriod' => [
'5.2' => false,
'5.3' => true,
],
'finfo' => [
'5.2' => false,
'5.3' => true,
'extension' => 'fileinfo',
],
'Collator' => [
'5.2' => false,
'5.3' => true,
'extension' => 'intl',
],
'NumberFormatter' => [
'5.2' => false,
'5.3' => true,
'extension' => 'intl',
],
'Locale' => [
'5.2' => false,
'5.3' => true,
'extension' => 'intl',
],
'Normalizer' => [
'5.2' => false,
'5.3' => true,
'extension' => 'intl',
],
'MessageFormatter' => [
'5.2' => false,
'5.3' => true,
'extension' => 'intl',
],
'IntlDateFormatter' => [
'5.2' => false,
'5.3' => true,
'extension' => 'intl',
],
'Phar' => [
'5.2' => false,
'5.3' => true,
'extension' => 'phar',
],
'PharData' => [
'5.2' => false,
'5.3' => true,
'extension' => 'phar',
],
'PharFileInfo' => [
'5.2' => false,
'5.3' => true,
'extension' => 'phar',
],
'FilesystemIterator' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'GlobIterator' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'MultipleIterator' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'RecursiveTreeIterator' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'SplDoublyLinkedList' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'SplFixedArray' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'SplHeap' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'SplMaxHeap' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'SplMinHeap' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'SplPriorityQueue' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'SplQueue' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'SplStack' => [
'5.2' => false,
'5.3' => true,
'extension' => 'spl',
],
'SQLite3' => [
'5.2' => false,
'5.3' => true,
'extension' => 'sqlite3',
],
'SQLite3Stmt' => [
'5.2' => false,
'5.3' => true,
'extension' => 'sqlite3',
],
'SQLite3Result' => [
'5.2' => false,
'5.3' => true,
'extension' => 'sqlite3',
],
'ResourceBundle' => [
'5.3.1' => false,
'5.3.2' => true,
'extension' => 'intl',
],
'CallbackFilterIterator' => [
'5.3' => false,
'5.4' => true,
'extension' => 'spl',
],
'RecursiveCallbackFilterIterator' => [
'5.3' => false,
'5.4' => true,
'extension' => 'spl',
],
'ReflectionZendExtension' => [
'5.3' => false,
'5.4' => true,
'extension' => 'reflection',
],
'SessionHandler' => [
'5.3' => false,
'5.4' => true,
],
'SNMP' => [
'5.3' => false,
'5.4' => true,
'extension' => 'snmp',
],
'Transliterator' => [
'5.3' => false,
'5.4' => true,
'extension' => 'intl',
],
'Spoofchecker' => [
'5.3' => false,
'5.4' => true,
'extension' => 'intl',
],
'Generator' => [
'5.4' => false,
'5.5' => true,
],
'CURLFile' => [
'5.4' => false,
'5.5' => true,
'extension' => 'curl',
],
'DateTimeImmutable' => [
'5.4' => false,
'5.5' => true,
],
'IntlCalendar' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'IntlGregorianCalendar' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'IntlTimeZone' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'IntlBreakIterator' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'IntlRuleBasedBreakIterator' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'IntlCodePointBreakIterator' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'IntlPartsIterator' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'IntlIterator' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'UConverter' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'GMP' => [
'5.5' => false,
'5.6' => true,
'extension' => 'gmp',
],
'IntlChar' => [
'5.6' => false,
'7.0' => true,
'extension' => 'intl',
],
'ReflectionType' => [
'5.6' => false,
'7.0' => true,
'extension' => 'reflection',
],
'ReflectionGenerator' => [
'5.6' => false,
'7.0' => true,
'extension' => 'reflection',
],
'ReflectionClassConstant' => [
'7.0' => false,
'7.1' => true,
'extension' => 'reflection',
],
'ReflectionNamedType' => [
'7.0' => false,
'7.1' => true,
'extension' => 'reflection',
],
'HashContext' => [
'7.1' => false,
'7.2' => true,
'extension' => 'hash',
],
'FFI' => [
'7.3' => false,
'7.4' => true,
'extension' => 'ffi',
],
'FFI\CData' => [
'7.3' => false,
'7.4' => true,
'extension' => 'ffi',
],
'FFI\CType' => [
'7.3' => false,
'7.4' => true,
'extension' => 'ffi',
],
'ReflectionReference' => [
'7.3' => false,
'7.4' => true,
'extension' => 'reflection',
],
'WeakReference' => [
'7.3' => false,
'7.4' => true,
],
'Attribute' => [
'7.4' => false,
'8.0' => true,
],
'PhpToken' => [
'7.4' => false,
'8.0' => true,
'extension' => 'tokenizer',
],
'ReflectionUnionType' => [
'7.4' => false,
'8.0' => true,
'extension' => 'reflection',
],
'WeakMap' => [
'7.4' => false,
'8.0' => true,
],
'OCICollection' => [
'7.4' => false,
'8.0' => true,
'extension' => 'oci8',
],
'OCILob' => [
'7.4' => false,
'8.0' => true,
'extension' => 'oci8',
],
'CurlHandle' => [
'7.4' => false,
'8.0' => true,
'extension' => 'curl',
],
'CurlMultiHandle' => [
'7.4' => false,
'8.0' => true,
'extension' => 'curl',
],
'CurlShareHandle' => [
'7.4' => false,
'8.0' => true,
'extension' => 'curl',
],
'EnchantBroker' => [
'7.4' => false,
'8.0' => true,
'extension' => 'enchant',
],
'EnchantDictionary' => [
'7.4' => false,
'8.0' => true,
'extension' => 'enchant',
],
'GdImage' => [
'7.4' => false,
'8.0' => true,
'extension' => 'gd',
],
'OpenSSLCertificate' => [
'7.4' => false,
'8.0' => true,
'extension' => 'openssl',
],
'OpenSSLCertificateSigningRequest' => [
'7.4' => false,
'8.0' => true,
'extension' => 'openssl',
],
'OpenSSLAsymmetricKey' => [
'7.4' => false,
'8.0' => true,
'extension' => 'openssl',
],
'Shmop' => [
'7.4' => false,
'8.0' => true,
'extension' => 'shmop',
],
'AddressInfo' => [
'7.4' => false,
'8.0' => true,
'extension' => 'sockets',
],
'Socket' => [
'7.4' => false,
'8.0' => true,
'extension' => 'sockets',
],
'SysvMessageQueue' => [
'7.4' => false,
'8.0' => true,
'extension' => 'sem',
],
'SysvSemaphore' => [
'7.4' => false,
'8.0' => true,
'extension' => 'sem',
],
'SysvSharedMemory' => [
'7.4' => false,
'8.0' => true,
'extension' => 'sem',
],
'XMLParser' => [
'7.4' => false,
'8.0' => true,
'extension' => 'xml',
],
'DeflateContext' => [
'7.4' => false,
'8.0' => true,
'extension' => 'zlib',
],
'InflateContext' => [
'7.4' => false,
'8.0' => true,
'extension' => 'zlib',
],
'IntlDatePatternGenerator' => [
'8.0' => false,
'8.1' => true,
'extension' => 'intl',
],
'Fiber' => [
'8.0' => false,
'8.1' => true,
'extension' => 'fibers',
],
'ReflectionEnum' => [
'8.0' => false,
'8.1' => true,
'extension' => 'reflection',
],
'ReflectionEnumBackedCase' => [
'8.0' => false,
'8.1' => true,
'extension' => 'reflection',
],
'ReflectionEnumUnitCase' => [
'8.0' => false,
'8.1' => true,
'extension' => 'reflection',
],
'ReflectionFiber' => [
'8.0' => false,
'8.1' => true,
'extension' => 'reflection',
],
'ReflectionIntersectionType' => [
'8.0' => false,
'8.1' => true,
'extension' => 'reflection',
],
'CURLStringFile' => [
'8.0' => false,
'8.1' => true,
'extension' => 'curl',
],
'FTP\Connection' => [
'8.0' => false,
'8.1' => true,
'extension' => 'ftp',
],
'GdFont' => [
'8.0' => false,
'8.1' => true,
'extension' => 'gd',
],
'IMAP\Connection' => [
'8.0' => false,
'8.1' => true,
'extension' => 'imap',
],
'LDAP\Connection' => [
'8.0' => false,
'8.1' => true,
'extension' => 'ldap',
],
'LDAP\Result' => [
'8.0' => false,
'8.1' => true,
'extension' => 'ldap',
],
'LDAP\ResultEntry' => [
'8.0' => false,
'8.1' => true,
'extension' => 'ldap',
],
'PgSql\Connection' => [
'8.0' => false,
'8.1' => true,
'extension' => 'pgsql',
],
'PgSql\Lob' => [
'8.0' => false,
'8.1' => true,
'extension' => 'pgsql',
],
'PgSql\Result' => [
'8.0' => false,
'8.1' => true,
'extension' => 'pgsql',
],
'PSpell\Config' => [
'8.0' => false,
'8.1' => true,
'extension' => 'pspell',
],
'PSpell\Dictionary' => [
'8.0' => false,
'8.1' => true,
'extension' => 'pspell',
],
'Random\Randomizer' => [
'8.1' => false,
'8.2' => true,
'extension' => 'random',
],
'Random\Engine\Secure' => [
'8.1' => false,
'8.2' => true,
'extension' => 'random',
],
'Random\Engine\Mt19937' => [
'8.1' => false,
'8.2' => true,
'extension' => 'random',
],
'Random\Engine\PcgOneseq128XslRr64' => [
'8.1' => false,
'8.2' => true,
'extension' => 'random',
],
'Random\Engine\Xoshiro256StarStar' => [
'8.1' => false,
'8.2' => true,
'extension' => 'random',
],
'BcMath\Number' => [
'8.3' => false,
'8.4' => true,
'extension' => 'bcmath',
],
'Dba\Connection' => [
'8.3' => false,
'8.4' => true,
'extension' => 'dba',
],
'Odbc\Connection' => [
'8.3' => false,
'8.4' => true,
'extension' => 'odbc',
],
'Odbc\Result' => [
'8.3' => false,
'8.4' => true,
'extension' => 'odbc',
],
'Pdo\DbLib' => [
'8.3' => false,
'8.4' => true,
'extension' => 'pdo',
],
'Pdo\Firebird' => [
'8.3' => false,
'8.4' => true,
'extension' => 'pdo',
],
'Pdo\Mysql' => [
'8.3' => false,
'8.4' => true,
'extension' => 'pdo',
],
'Pdo\Odbc' => [
'8.3' => false,
'8.4' => true,
'extension' => 'pdo',
],
'Pdo\Pgsql' => [
'8.3' => false,
'8.4' => true,
'extension' => 'pdo',
],
'Pdo\Sqlite' => [
'8.3' => false,
'8.4' => true,
'extension' => 'pdo',
],
'ReflectionConstant' => [
'8.3' => false,
'8.4' => true,
'extension' => 'reflection',
],
'Soap\Sdl' => [
'8.3' => false,
'8.4' => true,
'extension' => 'soap',
],
'Soap\Url' => [
'8.3' => false,
'8.4' => true,
'extension' => 'soap',
],
'StreamBucket' => [
'8.3' => false,
'8.4' => true,
'extension' => 'streams',
],
];
/**
* A list of new Exception classes, not present in older versions.
*
* The array lists : version number with false (not present) or true (present).
* If's sufficient to list the first version where the class appears.
*
* {@internal Classes listed here do not need to be added to the $newClasses
* property as well.
* This list is automatically added to the $newClasses property
* in the `register()` method.}
*
* {@internal Helper to update this list: https://3v4l.org/MhlUp}
*
* @since 7.1.4
*
* @var array<string, array<string, bool|string>>
*/
protected $newExceptions = [
'com_exception' => [
'4.4' => false,
'5.0' => true,
],
'DOMException' => [
'4.4' => false,
'5.0' => true,
'extension' => 'dom',
],
'Exception' => [
// According to the docs introduced in PHP 5.1, but this appears to be.
// an error. Class was introduced with try/catch keywords in PHP 5.0.
'4.4' => false,
'5.0' => true,
],
'ReflectionException' => [
'4.4' => false,
'5.0' => true,
'extension' => 'reflection',
],
'SoapFault' => [
'4.4' => false,
'5.0' => true,
'extension' => 'soap',
],
'SQLiteException' => [
'4.4' => false,
'5.0' => true,
'extension' => 'sqlite',
],
'mysqli_sql_exception' => [
'4.4' => false,
'5.0' => true,
'extension' => 'mysqli',
],
'ErrorException' => [
'5.0' => false,
'5.1' => true,
],
'BadFunctionCallException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'BadMethodCallException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'DomainException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'InvalidArgumentException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'LengthException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'LogicException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'OutOfBoundsException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'OutOfRangeException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'OverflowException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'PDOException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'pdo',
],
'RangeException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'RuntimeException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'UnderflowException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'UnexpectedValueException' => [
'5.0' => false,
'5.1' => true,
'extension' => 'spl',
],
'PharException' => [
'5.2' => false,
'5.3' => true,
'extension' => 'phar',
],
'SNMPException' => [
'5.3' => false,
'5.4' => true,
'extension' => 'snmp',
],
'IntlException' => [
'5.4' => false,
'5.5' => true,
'extension' => 'intl',
],
'Error' => [
'5.6' => false,
'7.0' => true,
],
'ArithmeticError' => [
'5.6' => false,
'7.0' => true,
],
'AssertionError' => [
'5.6' => false,
'7.0' => true,
],
'DivisionByZeroError' => [
'5.6' => false,
'7.0' => true,
],
'ParseError' => [
'5.6' => false,
'7.0' => true,
],
'TypeError' => [
'5.6' => false,
'7.0' => true,
],
'ClosedGeneratorException' => [
'5.6' => false,
'7.0' => true,
],
'ArgumentCountError' => [
'7.0' => false,
'7.1' => true,
],
'SodiumException' => [
'7.1' => false,
'7.2' => true,
],
'CompileError' => [
'7.2' => false,
'7.3' => true,
],
'JsonException' => [
'7.2' => false,
'7.3' => true,
'extension' => 'json',
],
'FFI\Exception' => [
'7.3' => false,
'7.4' => true,
'extension' => 'ffi',
],
'FFI\ParserException' => [
'7.3' => false,
'7.4' => true,
'extension' => 'ffi',
],
'UnhandledMatchError' => [
'7.4' => false,
'8.0' => true,
],
'ValueError' => [
'7.4' => false,
'8.0' => true,
],
'FiberError' => [
'8.0' => false,
'8.1' => true,
'extension' => 'fibers',
],
'Random\RandomError' => [
'8.1' => false,
'8.2' => true,
'extension' => 'random',
],
'Random\BrokenRandomEngineError' => [
'8.1' => false,
'8.2' => true,
'extension' => 'random',
],
'Random\RandomException' => [
'8.1' => false,
'8.2' => true,
'extension' => 'random',
],
'RequestParseBodyException' => [
'8.3' => false,
'8.4' => true,
],
];
/**
* Current file being scanned.
*
* @since 10.0.0
*
* @var string
*/
private $currentFile = '';
/**
* Stores information about imported, namespaced classes with names which are also in use by PHP.
*
* When those classes are used, they do not point to the PHP classes, but to the
* namespaced, imported class and those usages should be ignored by the sniff.
*
* The array is indexed by unqualified class names in lower case. The value is always true.
* It is structured this way to utilize the isset() function for faster lookups.
*
* @since 10.0.0
*
* @var array<string,true>
*/
private $importedClasses = [];
/**
* Returns an array of tokens this test wants to listen for.
*
* @since 5.5
* @since 7.0.3 - Now also targets the `class` keyword to detect extended classes.
* - Now also targets double colons to detect static class use.
* @since 7.1.4 - Now also targets anonymous classes to detect extended classes.
* - Now also targets functions/closures to detect new classes used
* as parameter type declarations.
* - Now also targets the `catch` control structure to detect new
* exception classes being caught.
* @since 8.2.0 Now also targets the `T_RETURN_TYPE` token to detect new classes used
* as return type declarations.
* @since 10.0.0 `T_RETURN_TYPE` token removed after PHPCS < 3.7.1 version drop.
*
* @return array<int|string>
*/
public function register()
{
// Handle case-insensitivity of class names.
$this->newClasses = \array_change_key_case($this->newClasses, \CASE_LOWER);
$this->newExceptions = \array_change_key_case($this->newExceptions, \CASE_LOWER);
// Add the Exception classes to the Classes list.
$this->newClasses = \array_merge($this->newClasses, $this->newExceptions);
$targets = [
\T_USE => \T_USE,
\T_NEW => \T_NEW,
\T_CLASS => \T_CLASS,
\T_ANON_CLASS => \T_ANON_CLASS,
\T_DOUBLE_COLON => \T_DOUBLE_COLON,
\T_CATCH => \T_CATCH,
\T_CONST => \T_CONST,
];
$targets += Collections::functionDeclarationTokens();
$targets += Collections::ooPropertyScopes();
return $targets;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 5.5
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$fileName = $phpcsFile->getFilename();
if ($this->currentFile !== $fileName) {
// Reset the properties for each new file.
$this->currentFile = $fileName;
$this->importedClasses = [];
}
$tokens = $phpcsFile->getTokens();
switch ($tokens[$stackPtr]['code']) {
case \T_USE:
$this->processUseToken($phpcsFile, $stackPtr);
break;
case \T_CONST:
$this->processConstantToken($phpcsFile, $stackPtr);
break;
case \T_CATCH:
$this->processCatchToken($phpcsFile, $stackPtr);
break;
case \T_NEW:
case \T_CLASS:
case \T_ANON_CLASS:
case \T_DOUBLE_COLON:
$this->processSingularToken($phpcsFile, $stackPtr);
break;
}
if (isset(Collections::ooPropertyScopes()[$tokens[$stackPtr]['code']]) === true) {
$this->processOOProperties($phpcsFile, $stackPtr);
}
if (isset(Collections::functionDeclarationTokens()[$tokens[$stackPtr]['code']]) === true) {
$this->processFunctionToken($phpcsFile, $stackPtr);
}
}
/**
* Processes this test for when a token resulting in a singular class name is encountered.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processSingularToken(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$FQClassName = '';
if ($tokens[$stackPtr]['code'] === \T_NEW) {
$FQClassName = ResolveHelper::getFQClassNameFromNewToken($phpcsFile, $stackPtr);
} elseif ($tokens[$stackPtr]['code'] === \T_CLASS || $tokens[$stackPtr]['code'] === \T_ANON_CLASS) {
$FQClassName = ResolveHelper::getFQExtendedClassName($phpcsFile, $stackPtr);
} elseif ($tokens[$stackPtr]['code'] === \T_DOUBLE_COLON) {
$FQClassName = ResolveHelper::getFQClassNameFromDoubleColonToken($phpcsFile, $stackPtr);
}
if ($FQClassName === '') {
return;
}
$className = \substr($FQClassName, 1); // Remove global namespace indicator.
$classNameLc = \strtolower($className);
if (isset($this->newClasses[$classNameLc]) === false) {
return;
}
$itemInfo = [
'name' => $className,
'nameLc' => $classNameLc,
];
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
/**
* Processes this test for when a function token is encountered.
*
* - Detect new classes when used as a parameter type declaration.
* - Detect new classes when used as a return type declaration.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processFunctionToken(File $phpcsFile, $stackPtr)
{
/*
* Check parameter type declarations.
*/
$parameters = FunctionDeclarations::getParameters($phpcsFile, $stackPtr);
if (empty($parameters) === false && \is_array($parameters) === true) {
foreach ($parameters as $param) {
if ($param['type_hint'] === '') {
continue;
}
$this->checkTypeDeclaration($phpcsFile, $param['type_hint_token'], $param['type_hint']);
}
}
/*
* Check return type declarations.
*/
$properties = FunctionDeclarations::getProperties($phpcsFile, $stackPtr);
if ($properties['return_type'] === '') {
return;
}
$this->checkTypeDeclaration($phpcsFile, $properties['return_type_token'], $properties['return_type']);
}
/**
* Processes this test for when a constant token is encountered.
*
* - Detect new classes when used as a class constant type declaration.
*
* @since 10.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processConstantToken(File $phpcsFile, $stackPtr)
{
try {
$properties = Constants::getProperties($phpcsFile, $stackPtr);
} catch (ValueError $e) {
// Not an OO constant or parse error.
return;
}
if ($properties['type'] === '') {
return;
}
$this->checkTypeDeclaration($phpcsFile, $properties['type_token'], $properties['type']);
}
/**
* Processes an OO token for properties declared in the OO scope.
*
* - Detect new classes when used as a property type declaration.
*
* @since 10.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processOOProperties(File $phpcsFile, $stackPtr)
{
$ooProperties = ObjectDeclarations::getDeclaredProperties($phpcsFile, $stackPtr);
if (empty($ooProperties)) {
return;
}
$tokens = $phpcsFile->getTokens();
$endOfStatement = false;
foreach ($ooProperties as $variableToken) {
if ($endOfStatement !== false && $variableToken < $endOfStatement) {
// Don't throw the same error multiple times for multi-property declarations.
// Also skip over any other constructor promoted properties.
continue;
}
try {
$properties = Variables::getMemberProperties($phpcsFile, $variableToken);
} catch (ValueError $e) {
/*
* This must be constructor property promotion.
* Ignore for now and skip over any other promoted properties, these will be handled
* via the function token for the constructor.
*/
$deepestOpen = Parentheses::getLastOpener($phpcsFile, $variableToken);
if ($deepestOpen !== false
&& $stackPtr < $deepestOpen
&& Parentheses::isOwnerIn($phpcsFile, $deepestOpen, \T_FUNCTION)
&& isset($tokens[$deepestOpen]['parenthesis_closer'])
) {
$endOfStatement = $tokens[$deepestOpen]['parenthesis_closer'];
}
continue;
}
if ($properties['type'] === '') {
continue;
}
$this->checkTypeDeclaration($phpcsFile, $properties['type_token'], $properties['type']);
$endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($variableToken + 1));
}
}
/**
* Processes a type declaration.
*
* @since 10.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
* @param string $typeString The type declaration.
*
* @return void
*/
private function checkTypeDeclaration($phpcsFile, $stackPtr, $typeString)
{
$types = TypeString::filterOOTypes(TypeString::toArray($typeString));
if (empty($types) === true) {
return;
}
foreach ($types as $type) {
// Strip off potential (global) namespace indication.
$type = \ltrim($type, '\\');
if ($type === '') {
continue;
}
$typeLc = \strtolower($type);
if (isset($this->newClasses[$typeLc]) === false) {
continue;
}
$itemInfo = [
'name' => $type,
'nameLc' => $typeLc,
];
$this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
}
}
/**
* Processes this test for when a catch token is encountered.
*
* - Detect exceptions when used in a catch statement.
*
* @since 7.1.4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processCatchToken(File $phpcsFile, $stackPtr)
{
$exceptions = ControlStructures::getCaughtExceptions($phpcsFile, $stackPtr);
if (empty($exceptions) === true) {
return;
}
foreach ($exceptions as $exception) {
// Strip off potential (global) namespace indication.
$name = \ltrim($exception['type'], '\\');
$nameLC = \strtolower($name);
if (isset($this->newExceptions[$nameLC]) === true) {
$itemInfo = [
'name' => $name,
'nameLc' => $nameLC,
];
$this->handleFeature($phpcsFile, $exception['type_token'], $itemInfo);
}
}
}
/**
* Processes this test for when a use token is encountered.
*
* - Save imported classes for later use.
*
* @since 10.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
private function processUseToken(File $phpcsFile, $stackPtr)
{
if (!UseStatements::isImportUse($phpcsFile, $stackPtr)) {
return;
}
$splitUseStatement = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr);
foreach ($splitUseStatement['name'] as $name => $fullyQualifiedName) {
$lowerFullyQualifiedName = \strtolower($fullyQualifiedName);
if (isset($this->newClasses[$lowerFullyQualifiedName])) {
continue;
}
$this->importedClasses[\strtolower($name)] = true;
}
}
/**
* Handle the retrieval of relevant information and - if necessary - throwing of an
* error for a matched item.
*
* @since 10.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
*
* @return void
*/
protected function handleFeature(File $phpcsFile, $stackPtr, array $itemInfo)
{
if (isset($this->importedClasses[$itemInfo['nameLc']])) {
return;
}
$itemArray = $this->newClasses[$itemInfo['nameLc']];
$versionInfo = $this->getVersionInfo($itemArray);
if (empty($versionInfo['not_in_version'])
|| ScannedCode::shouldRunOnOrBelow($versionInfo['not_in_version']) === false
) {
return;
}
$this->addError($phpcsFile, $stackPtr, $itemInfo, $versionInfo);
}
/**
* Generates the error for this item.
*
* @since 10.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the relevant token in
* the stack.
* @param array $itemInfo Base information about the item.
* @param string[] $versionInfo Array with detail (version) information
* relevant to the item.
*
* @return void
*/
protected function addError(File $phpcsFile, $stackPtr, array $itemInfo, array $versionInfo)
{
// Overrule the default message template.
$this->msgTemplate = 'The built-in class %s is not present in PHP version %s or earlier';
$msgInfo = $this->getMessageInfo($itemInfo['name'], $itemInfo['name'], $versionInfo);
$phpcsFile->addError($msgInfo['message'], $stackPtr, $msgInfo['errorcode'], $msgInfo['data']);
}
}