12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267 |
- <?php
- /**
- * PHPCompatibility, an external standard for PHP_CodeSniffer.
- *
- * @package PHPCompatibility
- * @copyright 2012-2019 PHPCompatibility Contributors
- * @license https://opensource.org/licenses/LGPL-3.0 LGPL3
- * @link https://github.com/PHPCompatibility/PHPCompatibility
- */
- namespace PHPCompatibility;
- use PHPCompatibility\PHPCSHelper;
- use PHP_CodeSniffer_Exception as PHPCS_Exception;
- use PHP_CodeSniffer_File as File;
- use PHP_CodeSniffer_Sniff as PHPCS_Sniff;
- use PHP_CodeSniffer_Tokens as Tokens;
- /**
- * Base class from which all PHPCompatibility sniffs extend.
- *
- * @since 5.6
- */
- abstract class Sniff implements PHPCS_Sniff
- {
- /**
- * Regex to match variables in a double quoted string.
- *
- * This matches plain variables, but also more complex variables, such
- * as $obj->prop, self::prop and $var[].
- *
- * @since 7.1.2
- *
- * @var string
- */
- const REGEX_COMPLEX_VARS = '`(?:(\{)?(?<!\\\\)\$)?(\{)?(?<!\\\\)\$(\{)?(?P<varname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:->\$?(?P>varname)|\[[^\]]+\]|::\$?(?P>varname)|\([^\)]*\))*(?(3)\}|)(?(2)\}|)(?(1)\}|)`';
- /**
- * List of superglobals as an array of strings.
- *
- * Used by the ForbiddenParameterShadowSuperGlobals and ForbiddenClosureUseVariableNames sniffs.
- *
- * @since 7.0.0
- * @since 7.1.4 Moved from the `ForbiddenParameterShadowSuperGlobals` sniff to the base `Sniff` class.
- *
- * @var array
- */
- protected $superglobals = array(
- '$GLOBALS' => true,
- '$_SERVER' => true,
- '$_GET' => true,
- '$_POST' => true,
- '$_FILES' => true,
- '$_COOKIE' => true,
- '$_SESSION' => true,
- '$_REQUEST' => true,
- '$_ENV' => true,
- );
- /**
- * List of functions using hash algorithm as parameter (always the first parameter).
- *
- * Used by the new/removed hash algorithm sniffs.
- * Key is the function name, value is the 1-based parameter position in the function call.
- *
- * @since 5.5
- * @since 7.0.7 Moved from the `RemovedHashAlgorithms` sniff to the base `Sniff` class.
- *
- * @var array
- */
- protected $hashAlgoFunctions = array(
- 'hash_file' => 1,
- 'hash_hmac_file' => 1,
- 'hash_hmac' => 1,
- 'hash_init' => 1,
- 'hash_pbkdf2' => 1,
- 'hash' => 1,
- );
- /**
- * List of functions which take an ini directive as parameter (always the first parameter).
- *
- * Used by the new/removed ini directives sniffs.
- * Key is the function name, value is the 1-based parameter position in the function call.
- *
- * @since 7.1.0
- *
- * @var array
- */
- protected $iniFunctions = array(
- 'ini_get' => 1,
- 'ini_set' => 1,
- );
- /**
- * Get the testVersion configuration variable.
- *
- * The testVersion configuration variable may be in any of the following formats:
- * 1) Omitted/empty, in which case no version is specified. This effectively
- * disables all the checks for new PHP features provided by this standard.
- * 2) A single PHP version number, e.g. "5.4" in which case the standard checks that
- * the code will run on that version of PHP (no deprecated features or newer
- * features being used).
- * 3) A range, e.g. "5.0-5.5", in which case the standard checks the code will run
- * on all PHP versions in that range, and that it doesn't use any features that
- * were deprecated by the final version in the list, or which were not available
- * for the first version in the list.
- * We accept ranges where one of the components is missing, e.g. "-5.6" means
- * all versions up to PHP 5.6, and "7.0-" means all versions above PHP 7.0.
- * PHP version numbers should always be in Major.Minor format. Both "5", "5.3.2"
- * would be treated as invalid, and ignored.
- *
- * @since 7.0.0
- * @since 7.1.3 Now allows for partial ranges such as `5.2-`.
- *
- * @return array $arrTestVersions will hold an array containing min/max version
- * of PHP that we are checking against (see above). If only a
- * single version number is specified, then this is used as
- * both the min and max.
- *
- * @throws \PHP_CodeSniffer_Exception If testVersion is invalid.
- */
- private function getTestVersion()
- {
- static $arrTestVersions = array();
- $default = array(null, null);
- $testVersion = trim(PHPCSHelper::getConfigData('testVersion'));
- if (empty($testVersion) === false && isset($arrTestVersions[$testVersion]) === false) {
- $arrTestVersions[$testVersion] = $default;
- if (preg_match('`^\d+\.\d+$`', $testVersion)) {
- $arrTestVersions[$testVersion] = array($testVersion, $testVersion);
- return $arrTestVersions[$testVersion];
- }
- if (preg_match('`^(\d+\.\d+)?\s*-\s*(\d+\.\d+)?$`', $testVersion, $matches)) {
- if (empty($matches[1]) === false || empty($matches[2]) === false) {
- // If no lower-limit is set, we set the min version to 4.0.
- // Whilst development focuses on PHP 5 and above, we also accept
- // sniffs for PHP 4, so we include that as the minimum.
- // (It makes no sense to support PHP 3 as this was effectively a
- // different language).
- $min = empty($matches[1]) ? '4.0' : $matches[1];
- // If no upper-limit is set, we set the max version to 99.9.
- $max = empty($matches[2]) ? '99.9' : $matches[2];
- if (version_compare($min, $max, '>')) {
- trigger_error(
- "Invalid range in testVersion setting: '" . $testVersion . "'",
- \E_USER_WARNING
- );
- return $default;
- } else {
- $arrTestVersions[$testVersion] = array($min, $max);
- return $arrTestVersions[$testVersion];
- }
- }
- }
- trigger_error(
- "Invalid testVersion setting: '" . $testVersion . "'",
- \E_USER_WARNING
- );
- return $default;
- }
- if (isset($arrTestVersions[$testVersion])) {
- return $arrTestVersions[$testVersion];
- }
- return $default;
- }
- /**
- * Check whether a specific PHP version is equal to or higher than the maximum
- * supported PHP version as provided by the user in `testVersion`.
- *
- * Should be used when sniffing for *old* PHP features (deprecated/removed).
- *
- * @since 5.6
- *
- * @param string $phpVersion A PHP version number in 'major.minor' format.
- *
- * @return bool True if testVersion has not been provided or if the PHP version
- * is equal to or higher than the highest supported PHP version
- * in testVersion. False otherwise.
- */
- public function supportsAbove($phpVersion)
- {
- $testVersion = $this->getTestVersion();
- $testVersion = $testVersion[1];
- if (\is_null($testVersion)
- || version_compare($testVersion, $phpVersion) >= 0
- ) {
- return true;
- } else {
- return false;
- }
- }
- /**
- * Check whether a specific PHP version is equal to or lower than the minimum
- * supported PHP version as provided by the user in `testVersion`.
- *
- * Should be used when sniffing for *new* PHP features.
- *
- * @since 5.6
- *
- * @param string $phpVersion A PHP version number in 'major.minor' format.
- *
- * @return bool True if the PHP version is equal to or lower than the lowest
- * supported PHP version in testVersion.
- * False otherwise or if no testVersion is provided.
- */
- public function supportsBelow($phpVersion)
- {
- $testVersion = $this->getTestVersion();
- $testVersion = $testVersion[0];
- if (\is_null($testVersion) === false
- && version_compare($testVersion, $phpVersion) <= 0
- ) {
- return true;
- } else {
- return false;
- }
- }
- /**
- * Add a PHPCS message to the output stack as either a warning or an error.
- *
- * @since 7.1.0
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file the message applies to.
- * @param string $message The message.
- * @param int $stackPtr The position of the token
- * the message relates to.
- * @param bool $isError Whether to report the message as an
- * 'error' or 'warning'.
- * Defaults to true (error).
- * @param string $code The error code for the message.
- * Defaults to 'Found'.
- * @param array $data Optional input for the data replacements.
- *
- * @return void
- */
- public function addMessage(File $phpcsFile, $message, $stackPtr, $isError, $code = 'Found', $data = array())
- {
- if ($isError === true) {
- $phpcsFile->addError($message, $stackPtr, $code, $data);
- } else {
- $phpcsFile->addWarning($message, $stackPtr, $code, $data);
- }
- }
- /**
- * Convert an arbitrary string to an alphanumeric string with underscores.
- *
- * Pre-empt issues with arbitrary strings being used as error codes in XML and PHP.
- *
- * @since 7.1.0
- *
- * @param string $baseString Arbitrary string.
- *
- * @return string
- */
- public function stringToErrorCode($baseString)
- {
- return preg_replace('`[^a-z0-9_]`i', '_', strtolower($baseString));
- }
- /**
- * Strip quotes surrounding an arbitrary string.
- *
- * Intended for use with the contents of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING.
- *
- * @since 7.0.6
- *
- * @param string $string The raw string.
- *
- * @return string String without quotes around it.
- */
- public function stripQuotes($string)
- {
- return preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $string);
- }
- /**
- * Strip variables from an arbitrary double quoted string.
- *
- * Intended for use with the contents of a T_DOUBLE_QUOTED_STRING.
- *
- * @since 7.1.2
- *
- * @param string $string The raw string.
- *
- * @return string String without variables in it.
- */
- public function stripVariables($string)
- {
- if (strpos($string, '$') === false) {
- return $string;
- }
- return preg_replace(self::REGEX_COMPLEX_VARS, '', $string);
- }
- /**
- * Make all top level array keys in an array lowercase.
- *
- * @since 7.1.0
- *
- * @param array $array Initial array.
- *
- * @return array Same array, but with all lowercase top level keys.
- */
- public function arrayKeysToLowercase($array)
- {
- return array_change_key_case($array, \CASE_LOWER);
- }
- /**
- * Checks if a function call has parameters.
- *
- * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call.
- * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
- *
- * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, it
- * will detect whether the array has values or is empty.
- *
- * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/120
- * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/152
- *
- * @since 7.0.3
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the function call token.
- *
- * @return bool
- */
- public function doesFunctionCallHaveParameters(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if (isset($tokens[$stackPtr]) === false) {
- return false;
- }
- // Is this one of the tokens this function handles ?
- if (\in_array($tokens[$stackPtr]['code'], array(\T_STRING, \T_ARRAY, \T_OPEN_SHORT_ARRAY, \T_VARIABLE), true) === false) {
- return false;
- }
- $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
- // Deal with short array syntax.
- if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY) {
- if (isset($tokens[$stackPtr]['bracket_closer']) === false) {
- return false;
- }
- if ($nextNonEmpty === $tokens[$stackPtr]['bracket_closer']) {
- // No parameters.
- return false;
- } else {
- return true;
- }
- }
- // Deal with function calls & long arrays.
- // Next non-empty token should be the open parenthesis.
- if ($nextNonEmpty === false && $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
- return false;
- }
- if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
- return false;
- }
- $closeParenthesis = $tokens[$nextNonEmpty]['parenthesis_closer'];
- $nextNextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $nextNonEmpty + 1, $closeParenthesis + 1, true);
- if ($nextNextNonEmpty === $closeParenthesis) {
- // No parameters.
- return false;
- }
- return true;
- }
- /**
- * Count the number of parameters a function call has been passed.
- *
- * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call.
- * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
- *
- * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
- * it will return the number of values in the array.
- *
- * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/111
- * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/114
- * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/151
- *
- * @since 7.0.3
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the function call token.
- *
- * @return int
- */
- public function getFunctionCallParameterCount(File $phpcsFile, $stackPtr)
- {
- if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
- return 0;
- }
- return \count($this->getFunctionCallParameters($phpcsFile, $stackPtr));
- }
- /**
- * Get information on all parameters passed to a function call.
- *
- * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call.
- * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
- *
- * Will return an multi-dimentional array with the start token pointer, end token
- * pointer and raw parameter value for all parameters. Index will be 1-based.
- * If no parameters are found, will return an empty array.
- *
- * Extra feature: If passed an T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer,
- * it will tokenize the values / key/value pairs contained in the array call.
- *
- * @since 7.0.5 Split off from the `getFunctionCallParameterCount()` method.
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the function call token.
- *
- * @return array
- */
- public function getFunctionCallParameters(File $phpcsFile, $stackPtr)
- {
- if ($this->doesFunctionCallHaveParameters($phpcsFile, $stackPtr) === false) {
- return array();
- }
- // Ok, we know we have a T_STRING, T_VARIABLE, T_ARRAY or T_OPEN_SHORT_ARRAY with parameters
- // and valid open & close brackets/parenthesis.
- $tokens = $phpcsFile->getTokens();
- // Mark the beginning and end tokens.
- if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY) {
- $opener = $stackPtr;
- $closer = $tokens[$stackPtr]['bracket_closer'];
- $nestedParenthesisCount = 0;
- } else {
- $opener = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
- $closer = $tokens[$opener]['parenthesis_closer'];
- $nestedParenthesisCount = 1;
- }
- // Which nesting level is the one we are interested in ?
- if (isset($tokens[$opener]['nested_parenthesis'])) {
- $nestedParenthesisCount += \count($tokens[$opener]['nested_parenthesis']);
- }
- $parameters = array();
- $nextComma = $opener;
- $paramStart = $opener + 1;
- $cnt = 1;
- while (($nextComma = $phpcsFile->findNext(array(\T_COMMA, $tokens[$closer]['code'], \T_OPEN_SHORT_ARRAY, \T_CLOSURE), $nextComma + 1, $closer + 1)) !== false) {
- // Ignore anything within short array definition brackets.
- if ($tokens[$nextComma]['type'] === 'T_OPEN_SHORT_ARRAY'
- && (isset($tokens[$nextComma]['bracket_opener'])
- && $tokens[$nextComma]['bracket_opener'] === $nextComma)
- && isset($tokens[$nextComma]['bracket_closer'])
- ) {
- // Skip forward to the end of the short array definition.
- $nextComma = $tokens[$nextComma]['bracket_closer'];
- continue;
- }
- // Skip past closures passed as function parameters.
- if ($tokens[$nextComma]['type'] === 'T_CLOSURE'
- && (isset($tokens[$nextComma]['scope_condition'])
- && $tokens[$nextComma]['scope_condition'] === $nextComma)
- && isset($tokens[$nextComma]['scope_closer'])
- ) {
- // Skip forward to the end of the closure declaration.
- $nextComma = $tokens[$nextComma]['scope_closer'];
- continue;
- }
- // Ignore comma's at a lower nesting level.
- if ($tokens[$nextComma]['type'] === 'T_COMMA'
- && isset($tokens[$nextComma]['nested_parenthesis'])
- && \count($tokens[$nextComma]['nested_parenthesis']) !== $nestedParenthesisCount
- ) {
- continue;
- }
- // Ignore closing parenthesis/bracket if not 'ours'.
- if ($tokens[$nextComma]['type'] === $tokens[$closer]['type'] && $nextComma !== $closer) {
- continue;
- }
- // Ok, we've reached the end of the parameter.
- $parameters[$cnt]['start'] = $paramStart;
- $parameters[$cnt]['end'] = $nextComma - 1;
- $parameters[$cnt]['raw'] = trim($phpcsFile->getTokensAsString($paramStart, ($nextComma - $paramStart)));
- /*
- * Check if there are more tokens before the closing parenthesis.
- * Prevents code like the following from setting a third parameter:
- * `functionCall( $param1, $param2, );`.
- */
- $hasNextParam = $phpcsFile->findNext(Tokens::$emptyTokens, $nextComma + 1, $closer, true, null, true);
- if ($hasNextParam === false) {
- break;
- }
- // Prepare for the next parameter.
- $paramStart = $nextComma + 1;
- $cnt++;
- }
- return $parameters;
- }
- /**
- * Get information on a specific parameter passed to a function call.
- *
- * Expects to be passed the T_STRING or T_VARIABLE stack pointer for the function call.
- * If passed a T_STRING which is *not* a function call, the behaviour is unreliable.
- *
- * Will return a array with the start token pointer, end token pointer and the raw value
- * of the parameter at a specific offset.
- * If the specified parameter is not found, will return false.
- *
- * @since 7.0.5
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the function call token.
- * @param int $paramOffset The 1-based index position of the parameter to retrieve.
- *
- * @return array|false
- */
- public function getFunctionCallParameter(File $phpcsFile, $stackPtr, $paramOffset)
- {
- $parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
- if (isset($parameters[$paramOffset]) === false) {
- return false;
- } else {
- return $parameters[$paramOffset];
- }
- }
- /**
- * Verify whether a token is within a scoped condition.
- *
- * If the optional $validScopes parameter has been passed, the function
- * will check that the token has at least one condition which is of a
- * type defined in $validScopes.
- *
- * @since 7.0.5 Largely split off from the `inClassScope()` method.
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the token.
- * @param array|int $validScopes Optional. Array of valid scopes
- * or int value of a valid scope.
- * Pass the T_.. constant(s) for the
- * desired scope to this parameter.
- *
- * @return bool Without the optional $scopeTypes: True if within a scope, false otherwise.
- * If the $scopeTypes are set: True if *one* of the conditions is a
- * valid scope, false otherwise.
- */
- public function tokenHasScope(File $phpcsFile, $stackPtr, $validScopes = null)
- {
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if (isset($tokens[$stackPtr]) === false) {
- return false;
- }
- // No conditions = no scope.
- if (empty($tokens[$stackPtr]['conditions'])) {
- return false;
- }
- // Ok, there are conditions, do we have to check for specific ones ?
- if (isset($validScopes) === false) {
- return true;
- }
- return $phpcsFile->hasCondition($stackPtr, $validScopes);
- }
- /**
- * Verify whether a token is within a class scope.
- *
- * @since 7.0.3
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the token.
- * @param bool $strict Whether to strictly check for the T_CLASS
- * scope or also accept interfaces and traits
- * as scope.
- *
- * @return bool True if within class scope, false otherwise.
- */
- public function inClassScope(File $phpcsFile, $stackPtr, $strict = true)
- {
- $validScopes = array(\T_CLASS);
- if (\defined('T_ANON_CLASS') === true) {
- $validScopes[] = \T_ANON_CLASS;
- }
- if ($strict === false) {
- $validScopes[] = \T_INTERFACE;
- $validScopes[] = \T_TRAIT;
- }
- return $phpcsFile->hasCondition($stackPtr, $validScopes);
- }
- /**
- * Returns the fully qualified class name for a new class instantiation.
- *
- * Returns an empty string if the class name could not be reliably inferred.
- *
- * @since 7.0.3
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of a T_NEW token.
- *
- * @return string
- */
- public function getFQClassNameFromNewToken(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if (isset($tokens[$stackPtr]) === false) {
- return '';
- }
- if ($tokens[$stackPtr]['code'] !== \T_NEW) {
- return '';
- }
- $start = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
- if ($start === false) {
- return '';
- }
- // Bow out if the next token is a variable as we don't know where it was defined.
- if ($tokens[$start]['code'] === \T_VARIABLE) {
- return '';
- }
- // Bow out if the next token is the class keyword.
- if ($tokens[$start]['type'] === 'T_ANON_CLASS' || $tokens[$start]['code'] === \T_CLASS) {
- return '';
- }
- $find = array(
- \T_NS_SEPARATOR,
- \T_STRING,
- \T_NAMESPACE,
- \T_WHITESPACE,
- );
- $end = $phpcsFile->findNext($find, ($start + 1), null, true, null, true);
- $className = $phpcsFile->getTokensAsString($start, ($end - $start));
- $className = trim($className);
- return $this->getFQName($phpcsFile, $stackPtr, $className);
- }
- /**
- * Returns the fully qualified name of the class that the specified class extends.
- *
- * Returns an empty string if the class does not extend another class or if
- * the class name could not be reliably inferred.
- *
- * @since 7.0.3
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of a T_CLASS token.
- *
- * @return string
- */
- public function getFQExtendedClassName(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if (isset($tokens[$stackPtr]) === false) {
- return '';
- }
- if ($tokens[$stackPtr]['code'] !== \T_CLASS
- && $tokens[$stackPtr]['type'] !== 'T_ANON_CLASS'
- && $tokens[$stackPtr]['type'] !== 'T_INTERFACE'
- ) {
- return '';
- }
- $extends = PHPCSHelper::findExtendedClassName($phpcsFile, $stackPtr);
- if (empty($extends) || \is_string($extends) === false) {
- return '';
- }
- return $this->getFQName($phpcsFile, $stackPtr, $extends);
- }
- /**
- * Returns the class name for the static usage of a class.
- * This can be a call to a method, the use of a property or constant.
- *
- * Returns an empty string if the class name could not be reliably inferred.
- *
- * @since 7.0.3
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of a T_NEW token.
- *
- * @return string
- */
- public function getFQClassNameFromDoubleColonToken(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if (isset($tokens[$stackPtr]) === false) {
- return '';
- }
- if ($tokens[$stackPtr]['code'] !== \T_DOUBLE_COLON) {
- return '';
- }
- // Nothing to do if previous token is a variable as we don't know where it was defined.
- if ($tokens[$stackPtr - 1]['code'] === \T_VARIABLE) {
- return '';
- }
- // Nothing to do if 'parent' or 'static' as we don't know how far the class tree extends.
- if (\in_array($tokens[$stackPtr - 1]['code'], array(\T_PARENT, \T_STATIC), true)) {
- return '';
- }
- // Get the classname from the class declaration if self is used.
- if ($tokens[$stackPtr - 1]['code'] === \T_SELF) {
- $classDeclarationPtr = $phpcsFile->findPrevious(\T_CLASS, $stackPtr - 1);
- if ($classDeclarationPtr === false) {
- return '';
- }
- $className = $phpcsFile->getDeclarationName($classDeclarationPtr);
- return $this->getFQName($phpcsFile, $classDeclarationPtr, $className);
- }
- $find = array(
- \T_NS_SEPARATOR,
- \T_STRING,
- \T_NAMESPACE,
- \T_WHITESPACE,
- );
- $start = $phpcsFile->findPrevious($find, $stackPtr - 1, null, true, null, true);
- if ($start === false || isset($tokens[($start + 1)]) === false) {
- return '';
- }
- $start = ($start + 1);
- $className = $phpcsFile->getTokensAsString($start, ($stackPtr - $start));
- $className = trim($className);
- return $this->getFQName($phpcsFile, $stackPtr, $className);
- }
- /**
- * Get the Fully Qualified name for a class/function/constant etc.
- *
- * Checks if a class/function/constant name is already fully qualified and
- * if not, enrich it with the relevant namespace information.
- *
- * @since 7.0.3
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the token.
- * @param string $name The class / function / constant name.
- *
- * @return string
- */
- public function getFQName(File $phpcsFile, $stackPtr, $name)
- {
- if (strpos($name, '\\') === 0) {
- // Already fully qualified.
- return $name;
- }
- // Remove the namespace keyword if used.
- if (strpos($name, 'namespace\\') === 0) {
- $name = substr($name, 10);
- }
- $namespace = $this->determineNamespace($phpcsFile, $stackPtr);
- if ($namespace === '') {
- return '\\' . $name;
- } else {
- return '\\' . $namespace . '\\' . $name;
- }
- }
- /**
- * Is the class/function/constant name namespaced or global ?
- *
- * @since 7.0.3
- *
- * @param string $FQName Fully Qualified name of a class, function etc.
- * I.e. should always start with a `\`.
- *
- * @return bool True if namespaced, false if global.
- *
- * @throws \PHP_CodeSniffer_Exception If the name in the passed parameter
- * is not fully qualified.
- */
- public function isNamespaced($FQName)
- {
- if (strpos($FQName, '\\') !== 0) {
- throw new PHPCS_Exception('$FQName must be a fully qualified name');
- }
- return (strpos(substr($FQName, 1), '\\') !== false);
- }
- /**
- * Determine the namespace name an arbitrary token lives in.
- *
- * @since 7.0.3
- *
- * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
- * @param int $stackPtr The token position for which to determine the namespace.
- *
- * @return string Namespace name or empty string if it couldn't be determined or no namespace applies.
- */
- public function determineNamespace(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if (isset($tokens[$stackPtr]) === false) {
- return '';
- }
- // Check for scoped namespace {}.
- if (empty($tokens[$stackPtr]['conditions']) === false) {
- $namespacePtr = $phpcsFile->getCondition($stackPtr, \T_NAMESPACE);
- if ($namespacePtr !== false) {
- $namespace = $this->getDeclaredNamespaceName($phpcsFile, $namespacePtr);
- if ($namespace !== false) {
- return $namespace;
- }
- // We are in a scoped namespace, but couldn't determine the name. Searching for a global namespace is futile.
- return '';
- }
- }
- /*
- * Not in a scoped namespace, so let's see if we can find a non-scoped namespace instead.
- * Keeping in mind that:
- * - there can be multiple non-scoped namespaces in a file (bad practice, but it happens).
- * - the namespace keyword can also be used as part of a function/method call and such.
- * - that a non-named namespace resolves to the global namespace.
- */
- $previousNSToken = $stackPtr;
- $namespace = false;
- do {
- $previousNSToken = $phpcsFile->findPrevious(\T_NAMESPACE, ($previousNSToken - 1));
- // Stop if we encounter a scoped namespace declaration as we already know we're not in one.
- if (empty($tokens[$previousNSToken]['scope_condition']) === false && $tokens[$previousNSToken]['scope_condition'] === $previousNSToken) {
- break;
- }
- $namespace = $this->getDeclaredNamespaceName($phpcsFile, $previousNSToken);
- } while ($namespace === false && $previousNSToken !== false);
- // If we still haven't got a namespace, return an empty string.
- if ($namespace === false) {
- return '';
- } else {
- return $namespace;
- }
- }
- /**
- * Get the complete namespace name for a namespace declaration.
- *
- * For hierarchical namespaces, the name will be composed of several tokens,
- * i.e. MyProject\Sub\Level which will be returned together as one string.
- *
- * @since 7.0.3
- *
- * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
- * @param int|bool $stackPtr The position of a T_NAMESPACE token.
- *
- * @return string|false Namespace name or false if not a namespace declaration.
- * Namespace name can be an empty string for global namespace declaration.
- */
- public function getDeclaredNamespaceName(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if ($stackPtr === false || isset($tokens[$stackPtr]) === false) {
- return false;
- }
- if ($tokens[$stackPtr]['code'] !== \T_NAMESPACE) {
- return false;
- }
- if ($tokens[($stackPtr + 1)]['code'] === \T_NS_SEPARATOR) {
- // Not a namespace declaration, but use of, i.e. `namespace\someFunction();`.
- return false;
- }
- $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true, null, true);
- if ($tokens[$nextToken]['code'] === \T_OPEN_CURLY_BRACKET) {
- /*
- * Declaration for global namespace when using multiple namespaces in a file.
- * I.e.: `namespace {}`.
- */
- return '';
- }
- // Ok, this should be a namespace declaration, so get all the parts together.
- $validTokens = array(
- \T_STRING => true,
- \T_NS_SEPARATOR => true,
- \T_WHITESPACE => true,
- );
- $namespaceName = '';
- while (isset($validTokens[$tokens[$nextToken]['code']]) === true) {
- $namespaceName .= trim($tokens[$nextToken]['content']);
- $nextToken++;
- }
- return $namespaceName;
- }
- /**
- * Get the stack pointer for a return type token for a given function.
- *
- * Compatible layer for older PHPCS versions which don't recognize
- * return type hints correctly.
- *
- * Expects to be passed T_RETURN_TYPE, T_FUNCTION or T_CLOSURE token.
- *
- * @since 7.1.2
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the token.
- *
- * @return int|false Stack pointer to the return type token or false if
- * no return type was found or the passed token was
- * not of the correct type.
- */
- public function getReturnTypeHintToken(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- if (\defined('T_RETURN_TYPE') && $tokens[$stackPtr]['code'] === \T_RETURN_TYPE) {
- return $stackPtr;
- }
- if ($tokens[$stackPtr]['code'] !== \T_FUNCTION && $tokens[$stackPtr]['code'] !== \T_CLOSURE) {
- return false;
- }
- if (isset($tokens[$stackPtr]['parenthesis_closer']) === false) {
- return false;
- }
- // Allow for interface and abstract method declarations.
- $endOfFunctionDeclaration = null;
- if (isset($tokens[$stackPtr]['scope_opener'])) {
- $endOfFunctionDeclaration = $tokens[$stackPtr]['scope_opener'];
- } else {
- $nextSemiColon = $phpcsFile->findNext(\T_SEMICOLON, ($tokens[$stackPtr]['parenthesis_closer'] + 1), null, false, null, true);
- if ($nextSemiColon !== false) {
- $endOfFunctionDeclaration = $nextSemiColon;
- }
- }
- if (isset($endOfFunctionDeclaration) === false) {
- return false;
- }
- $hasColon = $phpcsFile->findNext(
- array(\T_COLON, \T_INLINE_ELSE),
- ($tokens[$stackPtr]['parenthesis_closer'] + 1),
- $endOfFunctionDeclaration
- );
- if ($hasColon === false) {
- return false;
- }
- /*
- * - `self`, `parent` and `callable` are not being recognized as return types in PHPCS < 2.6.0.
- * - Return types are not recognized at all in PHPCS < 2.4.0.
- * - The T_RETURN_TYPE token is defined, but no longer in use since PHPCS 3.3.0+.
- * The token will now be tokenized as T_STRING.
- * - An `array` (return) type declaration was tokenized as `T_ARRAY_HINT` in PHPCS 2.3.3 - 3.2.3
- * to prevent confusing sniffs looking for array declarations.
- * As of PHPCS 3.3.0 `array` as a type declaration will be tokenized as `T_STRING`.
- */
- $unrecognizedTypes = array(
- \T_CALLABLE,
- \T_SELF,
- \T_PARENT,
- \T_ARRAY, // PHPCS < 2.4.0.
- \T_STRING,
- );
- return $phpcsFile->findPrevious($unrecognizedTypes, ($endOfFunctionDeclaration - 1), $hasColon);
- }
- /**
- * Get the complete return type declaration for a given function.
- *
- * Cross-version compatible way to retrieve the complete return type declaration.
- *
- * For a classname-based return type, PHPCS, as well as the Sniff::getReturnTypeHintToken()
- * method will mark the classname as the return type token.
- * This method will find preceeding namespaces and namespace separators and will return a
- * string containing the qualified return type declaration.
- *
- * Expects to be passed a T_RETURN_TYPE token or the return value from a call to
- * the Sniff::getReturnTypeHintToken() method.
- *
- * @since 8.2.0
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the return type token.
- *
- * @return string The name of the return type token.
- */
- public function getReturnTypeHintName(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- // In older PHPCS versions, the nullable indicator will turn a return type colon into a T_INLINE_ELSE.
- $colon = $phpcsFile->findPrevious(array(\T_COLON, \T_INLINE_ELSE, \T_FUNCTION, \T_CLOSE_PARENTHESIS), ($stackPtr - 1));
- if ($colon === false
- || ($tokens[$colon]['code'] !== \T_COLON && $tokens[$colon]['code'] !== \T_INLINE_ELSE)
- ) {
- // Shouldn't happen, just in case.
- return '';
- }
- $returnTypeHint = '';
- for ($i = ($colon + 1); $i <= $stackPtr; $i++) {
- // As of PHPCS 3.3.0+, all tokens are tokenized as "normal", so T_CALLABLE, T_SELF etc are
- // all possible, just exclude anything that's regarded as empty and the nullable indicator.
- if (isset(Tokens::$emptyTokens[$tokens[$i]['code']])) {
- continue;
- }
- if ($tokens[$i]['type'] === 'T_NULLABLE') {
- continue;
- }
- if (\defined('T_NULLABLE') === false && $tokens[$i]['code'] === \T_INLINE_THEN) {
- // Old PHPCS.
- continue;
- }
- $returnTypeHint .= $tokens[$i]['content'];
- }
- return $returnTypeHint;
- }
- /**
- * Check whether a T_VARIABLE token is a class property declaration.
- *
- * Compatibility layer for PHPCS cross-version compatibility
- * as PHPCS 2.4.0 - 2.7.1 does not have good enough support for
- * anonymous classes. Along the same lines, the`getMemberProperties()`
- * method does not support the `var` prefix.
- *
- * @since 7.1.4
- *
- * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
- * @param int $stackPtr The position in the stack of the
- * T_VARIABLE token to verify.
- *
- * @return bool
- */
- public function isClassProperty(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_VARIABLE) {
- return false;
- }
- // Note: interfaces can not declare properties.
- $validScopes = array(
- 'T_CLASS' => true,
- 'T_ANON_CLASS' => true,
- 'T_TRAIT' => true,
- );
- $scopePtr = $this->validDirectScope($phpcsFile, $stackPtr, $validScopes);
- if ($scopePtr !== false) {
- // Make sure it's not a method parameter.
- if (empty($tokens[$stackPtr]['nested_parenthesis']) === true) {
- return true;
- } else {
- $parenthesis = array_keys($tokens[$stackPtr]['nested_parenthesis']);
- $deepestOpen = array_pop($parenthesis);
- if ($deepestOpen < $scopePtr
- || isset($tokens[$deepestOpen]['parenthesis_owner']) === false
- || $tokens[$tokens[$deepestOpen]['parenthesis_owner']]['code'] !== \T_FUNCTION
- ) {
- return true;
- }
- }
- }
- return false;
- }
- /**
- * Check whether a T_CONST token is a class constant declaration.
- *
- * @since 7.1.4
- *
- * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
- * @param int $stackPtr The position in the stack of the
- * T_CONST token to verify.
- *
- * @return bool
- */
- public function isClassConstant(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_CONST) {
- return false;
- }
- // Note: traits can not declare constants.
- $validScopes = array(
- 'T_CLASS' => true,
- 'T_ANON_CLASS' => true,
- 'T_INTERFACE' => true,
- );
- if ($this->validDirectScope($phpcsFile, $stackPtr, $validScopes) !== false) {
- return true;
- }
- return false;
- }
- /**
- * Check whether the direct wrapping scope of a token is within a limited set of
- * acceptable tokens.
- *
- * Used to check, for instance, if a T_CONST is a class constant.
- *
- * @since 7.1.4
- *
- * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
- * @param int $stackPtr The position in the stack of the
- * token to verify.
- * @param array $validScopes Array of token types.
- * Keys should be the token types in string
- * format to allow for newer token types.
- * Value is irrelevant.
- *
- * @return int|bool StackPtr to the scope if valid, false otherwise.
- */
- protected function validDirectScope(File $phpcsFile, $stackPtr, $validScopes)
- {
- $tokens = $phpcsFile->getTokens();
- if (empty($tokens[$stackPtr]['conditions']) === true) {
- return false;
- }
- /*
- * Check only the direct wrapping scope of the token.
- */
- $conditions = array_keys($tokens[$stackPtr]['conditions']);
- $ptr = array_pop($conditions);
- if (isset($tokens[$ptr]) === false) {
- return false;
- }
- if (isset($validScopes[$tokens[$ptr]['type']]) === true) {
- return $ptr;
- }
- return false;
- }
- /**
- * Get an array of just the type hints from a function declaration.
- *
- * Expects to be passed T_FUNCTION or T_CLOSURE token.
- *
- * Strips potential nullable indicator and potential global namespace
- * indicator from the type hints before returning them.
- *
- * @since 7.1.4
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the token.
- *
- * @return array Array with type hints or an empty array if
- * - the function does not have any parameters
- * - no type hints were found
- * - or the passed token was not of the correct type.
- */
- public function getTypeHintsFromFunctionDeclaration(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- if ($tokens[$stackPtr]['code'] !== \T_FUNCTION && $tokens[$stackPtr]['code'] !== \T_CLOSURE) {
- return array();
- }
- $parameters = PHPCSHelper::getMethodParameters($phpcsFile, $stackPtr);
- if (empty($parameters) || \is_array($parameters) === false) {
- return array();
- }
- $typeHints = array();
- foreach ($parameters as $param) {
- if ($param['type_hint'] === '') {
- continue;
- }
- // Strip off potential nullable indication.
- $typeHint = ltrim($param['type_hint'], '?');
- // Strip off potential (global) namespace indication.
- $typeHint = ltrim($typeHint, '\\');
- if ($typeHint !== '') {
- $typeHints[] = $typeHint;
- }
- }
- return $typeHints;
- }
- /**
- * Get the hash algorithm name from the parameter in a hash function call.
- *
- * @since 7.0.7 Logic was originally contained in the `RemovedHashAlgorithms` sniff.
- *
- * @param \PHP_CodeSniffer_File $phpcsFile Instance of phpcsFile.
- * @param int $stackPtr The position of the T_STRING function token.
- *
- * @return string|false The algorithm name without quotes if this was a relevant hash
- * function call or false if it was not.
- */
- public function getHashAlgorithmParameter(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if (isset($tokens[$stackPtr]) === false) {
- return false;
- }
- if ($tokens[$stackPtr]['code'] !== \T_STRING) {
- return false;
- }
- $functionName = $tokens[$stackPtr]['content'];
- $functionNameLc = strtolower($functionName);
- // Bow out if not one of the functions we're targetting.
- if (isset($this->hashAlgoFunctions[$functionNameLc]) === false) {
- return false;
- }
- // Get the parameter from the function call which should contain the algorithm name.
- $algoParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, $this->hashAlgoFunctions[$functionNameLc]);
- if ($algoParam === false) {
- return false;
- }
- // Algorithm is a text string, so we need to remove the quotes.
- $algo = strtolower(trim($algoParam['raw']));
- $algo = $this->stripQuotes($algo);
- return $algo;
- }
- /**
- * Determine whether an arbitrary T_STRING token is the use of a global constant.
- *
- * @since 8.1.0
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the T_STRING token.
- *
- * @return bool
- */
- public function isUseOfGlobalConstant(File $phpcsFile, $stackPtr)
- {
- static $isLowPHPCS, $isLowPHP;
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if (isset($tokens[$stackPtr]) === false) {
- return false;
- }
- // Is this one of the tokens this function handles ?
- if ($tokens[$stackPtr]['code'] !== \T_STRING) {
- return false;
- }
- // Check for older PHP, PHPCS version so we can compensate for misidentified tokens.
- if (isset($isLowPHPCS, $isLowPHP) === false) {
- $isLowPHP = false;
- $isLowPHPCS = false;
- if (version_compare(\PHP_VERSION_ID, '50400', '<')) {
- $isLowPHP = true;
- $isLowPHPCS = version_compare(PHPCSHelper::getVersion(), '2.4.0', '<');
- }
- }
- $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
- if ($next !== false
- && ($tokens[$next]['code'] === \T_OPEN_PARENTHESIS
- || $tokens[$next]['code'] === \T_DOUBLE_COLON)
- ) {
- // Function call or declaration.
- return false;
- }
- // Array of tokens which if found preceding the $stackPtr indicate that a T_STRING is not a global constant.
- $tokensToIgnore = array(
- 'T_NAMESPACE' => true,
- 'T_USE' => true,
- 'T_CLASS' => true,
- 'T_TRAIT' => true,
- 'T_INTERFACE' => true,
- 'T_EXTENDS' => true,
- 'T_IMPLEMENTS' => true,
- 'T_NEW' => true,
- 'T_FUNCTION' => true,
- 'T_DOUBLE_COLON' => true,
- 'T_OBJECT_OPERATOR' => true,
- 'T_INSTANCEOF' => true,
- 'T_INSTEADOF' => true,
- 'T_GOTO' => true,
- 'T_AS' => true,
- 'T_PUBLIC' => true,
- 'T_PROTECTED' => true,
- 'T_PRIVATE' => true,
- );
- $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
- if ($prev !== false
- && (isset($tokensToIgnore[$tokens[$prev]['type']]) === true
- || ($tokens[$prev]['code'] === \T_STRING
- && (($isLowPHPCS === true
- && $tokens[$prev]['content'] === 'trait')
- || ($isLowPHP === true
- && $tokens[$prev]['content'] === 'insteadof'))))
- ) {
- // Not the use of a constant.
- return false;
- }
- if ($prev !== false
- && $tokens[$prev]['code'] === \T_NS_SEPARATOR
- && $tokens[($prev - 1)]['code'] === \T_STRING
- ) {
- // Namespaced constant of the same name.
- return false;
- }
- if ($prev !== false
- && $tokens[$prev]['code'] === \T_CONST
- && $this->isClassConstant($phpcsFile, $prev) === true
- ) {
- // Class constant declaration of the same name.
- return false;
- }
- /*
- * Deal with a number of variations of use statements.
- */
- for ($i = $stackPtr; $i > 0; $i--) {
- if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) {
- break;
- }
- }
- $firstOnLine = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
- if ($firstOnLine !== false && $tokens[$firstOnLine]['code'] === \T_USE) {
- $nextOnLine = $phpcsFile->findNext(Tokens::$emptyTokens, ($firstOnLine + 1), null, true);
- if ($nextOnLine !== false) {
- if (($tokens[$nextOnLine]['code'] === \T_STRING && $tokens[$nextOnLine]['content'] === 'const')
- || $tokens[$nextOnLine]['code'] === \T_CONST // Happens in some PHPCS versions.
- ) {
- $hasNsSep = $phpcsFile->findNext(\T_NS_SEPARATOR, ($nextOnLine + 1), $stackPtr);
- if ($hasNsSep !== false) {
- // Namespaced const (group) use statement.
- return false;
- }
- } else {
- // Not a const use statement.
- return false;
- }
- }
- }
- return true;
- }
- /**
- * Determine whether the tokens between $start and $end together form a positive number
- * as recognized by PHP.
- *
- * The outcome of this function is reliable for `true`, `false` should be regarded as
- * "undetermined".
- *
- * Note: Zero is *not* regarded as a positive number.
- *
- * @since 8.2.0
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $start Start of the snippet (inclusive), i.e. this
- * token will be examined as part of the snippet.
- * @param int $end End of the snippet (inclusive), i.e. this
- * token will be examined as part of the snippet.
- * @param bool $allowFloats Whether to only consider integers, or also floats.
- *
- * @return bool True if PHP would evaluate the snippet as a positive number.
- * False if not or if it could not be reliably determined
- * (variable or calculations and such).
- */
- public function isPositiveNumber(File $phpcsFile, $start, $end, $allowFloats = false)
- {
- $number = $this->isNumber($phpcsFile, $start, $end, $allowFloats);
- if ($number === false) {
- return false;
- }
- return ($number > 0);
- }
- /**
- * Determine whether the tokens between $start and $end together form a negative number
- * as recognized by PHP.
- *
- * The outcome of this function is reliable for `true`, `false` should be regarded as
- * "undetermined".
- *
- * Note: Zero is *not* regarded as a negative number.
- *
- * @since 8.2.0
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $start Start of the snippet (inclusive), i.e. this
- * token will be examined as part of the snippet.
- * @param int $end End of the snippet (inclusive), i.e. this
- * token will be examined as part of the snippet.
- * @param bool $allowFloats Whether to only consider integers, or also floats.
- *
- * @return bool True if PHP would evaluate the snippet as a negative number.
- * False if not or if it could not be reliably determined
- * (variable or calculations and such).
- */
- public function isNegativeNumber(File $phpcsFile, $start, $end, $allowFloats = false)
- {
- $number = $this->isNumber($phpcsFile, $start, $end, $allowFloats);
- if ($number === false) {
- return false;
- }
- return ($number < 0);
- }
- /**
- * Determine whether the tokens between $start and $end together form a number
- * as recognized by PHP.
- *
- * The outcome of this function is reliable for "true-ish" values, `false` should
- * be regarded as "undetermined".
- *
- * @link https://3v4l.org/npTeM
- *
- * Mainly intended for examining variable assignments, function call parameters, array values
- * where the start and end of the snippet to examine is very clear.
- *
- * @since 8.2.0
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $start Start of the snippet (inclusive), i.e. this
- * token will be examined as part of the snippet.
- * @param int $end End of the snippet (inclusive), i.e. this
- * token will be examined as part of the snippet.
- * @param bool $allowFloats Whether to only consider integers, or also floats.
- *
- * @return int|float|bool The number found if PHP would evaluate the snippet as a number.
- * The return type will be int if $allowFloats is false, if
- * $allowFloats is true, the return type will be float.
- * False will be returned when the snippet does not evaluate to a
- * number or if it could not be reliably determined
- * (variable or calculations and such).
- */
- protected function isNumber(File $phpcsFile, $start, $end, $allowFloats = false)
- {
- $stringTokens = Tokens::$heredocTokens + Tokens::$stringTokens;
- $validTokens = array();
- $validTokens[\T_LNUMBER] = true;
- $validTokens[\T_TRUE] = true; // Evaluates to int 1.
- $validTokens[\T_FALSE] = true; // Evaluates to int 0.
- $validTokens[\T_NULL] = true; // Evaluates to int 0.
- if ($allowFloats === true) {
- $validTokens[\T_DNUMBER] = true;
- }
- $maybeValidTokens = $stringTokens + $validTokens;
- $tokens = $phpcsFile->getTokens();
- $searchEnd = ($end + 1);
- $negativeNumber = false;
- if (isset($tokens[$start], $tokens[$searchEnd]) === false) {
- return false;
- }
- $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $start, $searchEnd, true);
- while ($nextNonEmpty !== false
- && ($tokens[$nextNonEmpty]['code'] === \T_PLUS
- || $tokens[$nextNonEmpty]['code'] === \T_MINUS)
- ) {
- if ($tokens[$nextNonEmpty]['code'] === \T_MINUS) {
- $negativeNumber = ($negativeNumber === false) ? true : false;
- }
- $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), $searchEnd, true);
- }
- if ($nextNonEmpty === false || isset($maybeValidTokens[$tokens[$nextNonEmpty]['code']]) === false) {
- return false;
- }
- $content = false;
- if ($tokens[$nextNonEmpty]['code'] === \T_LNUMBER
- || $tokens[$nextNonEmpty]['code'] === \T_DNUMBER
- ) {
- $content = (float) $tokens[$nextNonEmpty]['content'];
- } elseif ($tokens[$nextNonEmpty]['code'] === \T_TRUE) {
- $content = 1.0;
- } elseif ($tokens[$nextNonEmpty]['code'] === \T_FALSE
- || $tokens[$nextNonEmpty]['code'] === \T_NULL
- ) {
- $content = 0.0;
- } elseif (isset($stringTokens[$tokens[$nextNonEmpty]['code']]) === true) {
- if ($tokens[$nextNonEmpty]['code'] === \T_START_HEREDOC
- || $tokens[$nextNonEmpty]['code'] === \T_START_NOWDOC
- ) {
- // Skip past heredoc/nowdoc opener to the first content.
- $firstDocToken = $phpcsFile->findNext(array(\T_HEREDOC, \T_NOWDOC), ($nextNonEmpty + 1), $searchEnd);
- if ($firstDocToken === false) {
- // Live coding or parse error.
- return false;
- }
- $stringContent = $content = $tokens[$firstDocToken]['content'];
- // Skip forward to the end in preparation for the next part of the examination.
- $nextNonEmpty = $phpcsFile->findNext(array(\T_END_HEREDOC, \T_END_NOWDOC), ($nextNonEmpty + 1), $searchEnd);
- if ($nextNonEmpty === false) {
- // Live coding or parse error.
- return false;
- }
- } else {
- // Gather subsequent lines for a multi-line string.
- for ($i = $nextNonEmpty; $i < $searchEnd; $i++) {
- if ($tokens[$i]['code'] !== $tokens[$nextNonEmpty]['code']) {
- break;
- }
- $content .= $tokens[$i]['content'];
- }
- $nextNonEmpty = --$i;
- $content = $this->stripQuotes($content);
- $stringContent = $content;
- }
- /*
- * Regexes based on the formats outlined in the manual, created by JRF.
- * @link https://www.php.net/manual/en/language.types.float.php
- */
- $regexInt = '`^\s*[0-9]+`';
- $regexFloat = '`^\s*(?:[+-]?(?:(?:(?P<LNUM>[0-9]+)|(?P<DNUM>([0-9]*\.(?P>LNUM)|(?P>LNUM)\.[0-9]*)))[eE][+-]?(?P>LNUM))|(?P>DNUM))`';
- $intString = preg_match($regexInt, $content, $intMatch);
- $floatString = preg_match($regexFloat, $content, $floatMatch);
- // Does the text string start with a number ? If so, PHP would juggle it and use it as a number.
- if ($allowFloats === false) {
- if ($intString !== 1 || $floatString === 1) {
- if ($floatString === 1) {
- // Found float. Only integers targetted.
- return false;
- }
- $content = 0.0;
- } else {
- $content = (float) trim($intMatch[0]);
- }
- } else {
- if ($intString !== 1 && $floatString !== 1) {
- $content = 0.0;
- } else {
- $content = ($floatString === 1) ? (float) trim($floatMatch[0]) : (float) trim($intMatch[0]);
- }
- }
- // Allow for different behaviour for hex numeric strings between PHP 5 vs PHP 7.
- if ($intString === 1 && trim($intMatch[0]) === '0'
- && preg_match('`^\s*(0x[A-Fa-f0-9]+)`', $stringContent, $hexNumberString) === 1
- && $this->supportsBelow('5.6') === true
- ) {
- // The filter extension still allows for hex numeric strings in PHP 7, so
- // use that to get the numeric value if possible.
- // If the filter extension is not available, the value will be zero, but so be it.
- if (function_exists('filter_var')) {
- $filtered = filter_var($hexNumberString[1], \FILTER_VALIDATE_INT, \FILTER_FLAG_ALLOW_HEX);
- if ($filtered !== false) {
- $content = $filtered;
- }
- }
- }
- }
- // OK, so we have a number, now is there still more code after it ?
- $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), $searchEnd, true);
- if ($nextNonEmpty !== false) {
- return false;
- }
- if ($negativeNumber === true) {
- $content = -$content;
- }
- if ($allowFloats === false) {
- return (int) $content;
- }
- return $content;
- }
- /**
- * Determine whether the tokens between $start and $end together form a numberic calculation
- * as recognized by PHP.
- *
- * The outcome of this function is reliable for `true`, `false` should be regarded as "undetermined".
- *
- * Mainly intended for examining variable assignments, function call parameters, array values
- * where the start and end of the snippet to examine is very clear.
- *
- * @since 9.0.0
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $start Start of the snippet (inclusive), i.e. this
- * token will be examined as part of the snippet.
- * @param int $end End of the snippet (inclusive), i.e. this
- * token will be examined as part of the snippet.
- *
- * @return bool
- */
- protected function isNumericCalculation(File $phpcsFile, $start, $end)
- {
- $arithmeticTokens = Tokens::$arithmeticTokens;
- // phpcs:disable PHPCompatibility.Constants.NewConstants.t_powFound
- if (\defined('T_POW') && isset($arithmeticTokens[\T_POW]) === false) {
- // T_POW was not added to the arithmetic array until PHPCS 2.9.0.
- $arithmeticTokens[\T_POW] = \T_POW;
- }
- // phpcs:enable
- $skipTokens = Tokens::$emptyTokens;
- $skipTokens[] = \T_MINUS;
- $skipTokens[] = \T_PLUS;
- // Find the first arithmetic operator, but skip past +/- signs before numbers.
- $nextNonEmpty = ($start - 1);
- do {
- $nextNonEmpty = $phpcsFile->findNext($skipTokens, ($nextNonEmpty + 1), ($end + 1), true);
- $arithmeticOperator = $phpcsFile->findNext($arithmeticTokens, ($nextNonEmpty + 1), ($end + 1));
- } while ($nextNonEmpty !== false && $arithmeticOperator !== false && $nextNonEmpty === $arithmeticOperator);
- if ($arithmeticOperator === false) {
- return false;
- }
- $tokens = $phpcsFile->getTokens();
- $subsetStart = $start;
- $subsetEnd = ($arithmeticOperator - 1);
- while ($this->isNumber($phpcsFile, $subsetStart, $subsetEnd, true) !== false
- && isset($tokens[($arithmeticOperator + 1)]) === true
- ) {
- // Recognize T_POW for PHPCS < 2.4.0 on low PHP versions.
- if (\defined('T_POW') === false
- && $tokens[$arithmeticOperator]['code'] === \T_MULTIPLY
- && $tokens[($arithmeticOperator + 1)]['code'] === \T_MULTIPLY
- && isset($tokens[$arithmeticOperator + 2]) === true
- ) {
- // Move operator one forward to the second * in T_POW.
- ++$arithmeticOperator;
- }
- $subsetStart = ($arithmeticOperator + 1);
- $nextNonEmpty = $arithmeticOperator;
- do {
- $nextNonEmpty = $phpcsFile->findNext($skipTokens, ($nextNonEmpty + 1), ($end + 1), true);
- $arithmeticOperator = $phpcsFile->findNext($arithmeticTokens, ($nextNonEmpty + 1), ($end + 1));
- } while ($nextNonEmpty !== false && $arithmeticOperator !== false && $nextNonEmpty === $arithmeticOperator);
- if ($arithmeticOperator === false) {
- // Last calculation operator already reached.
- if ($this->isNumber($phpcsFile, $subsetStart, $end, true) !== false) {
- return true;
- }
- return false;
- }
- $subsetEnd = ($arithmeticOperator - 1);
- }
- return false;
- }
- /**
- * Determine whether a ternary is a short ternary, i.e. without "middle".
- *
- * N.B.: This is a back-fill for a new method which is expected to go into
- * PHP_CodeSniffer 3.5.0.
- * Once that method has been merged into PHPCS, this one should be moved
- * to the PHPCSHelper.php file.
- *
- * @since 9.2.0
- *
- * @codeCoverageIgnore Method as pulled upstream is accompanied by unit tests.
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the ternary operator
- * in the stack.
- *
- * @return bool True if short ternary, or false otherwise.
- */
- public function isShortTernary(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- if (isset($tokens[$stackPtr]) === false
- || $tokens[$stackPtr]['code'] !== \T_INLINE_THEN
- ) {
- return false;
- }
- $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
- if ($nextNonEmpty === false) {
- // Live coding or parse error.
- return false;
- }
- if ($tokens[$nextNonEmpty]['code'] === \T_INLINE_ELSE) {
- return true;
- }
- return false;
- }
- /**
- * Determine whether a T_OPEN/CLOSE_SHORT_ARRAY token is a list() construct.
- *
- * Note: A variety of PHPCS versions have bugs in the tokenizing of short arrays.
- * In that case, the tokens are identified as T_OPEN/CLOSE_SQUARE_BRACKET.
- *
- * @since 8.2.0
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the function call token.
- *
- * @return bool
- */
- public function isShortList(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- // Check for the existence of the token.
- if (isset($tokens[$stackPtr]) === false) {
- return false;
- }
- // Is this one of the tokens this function handles ?
- if ($tokens[$stackPtr]['code'] !== \T_OPEN_SHORT_ARRAY
- && $tokens[$stackPtr]['code'] !== \T_CLOSE_SHORT_ARRAY
- ) {
- return false;
- }
- switch ($tokens[$stackPtr]['code']) {
- case \T_OPEN_SHORT_ARRAY:
- if (isset($tokens[$stackPtr]['bracket_closer']) === true) {
- $opener = $stackPtr;
- $closer = $tokens[$stackPtr]['bracket_closer'];
- }
- break;
- case \T_CLOSE_SHORT_ARRAY:
- if (isset($tokens[$stackPtr]['bracket_opener']) === true) {
- $opener = $tokens[$stackPtr]['bracket_opener'];
- $closer = $stackPtr;
- }
- break;
- }
- if (isset($opener, $closer) === false) {
- // Parse error, live coding or real square bracket.
- return false;
- }
- /*
- * PHPCS cross-version compatibility: work around for square brackets misidentified
- * as short array when preceded by a variable variable in older PHPCS versions.
- */
- $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true, null, true);
- if ($prevNonEmpty !== false
- && $tokens[$prevNonEmpty]['code'] === \T_CLOSE_CURLY_BRACKET
- && isset($tokens[$prevNonEmpty]['bracket_opener']) === true
- ) {
- $maybeVariableVariable = $phpcsFile->findPrevious(
- Tokens::$emptyTokens,
- ($tokens[$prevNonEmpty]['bracket_opener'] - 1),
- null,
- true,
- null,
- true
- );
- if ($tokens[$maybeVariableVariable]['code'] === \T_VARIABLE
- || $tokens[$maybeVariableVariable]['code'] === \T_DOLLAR
- ) {
- return false;
- }
- }
- $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($closer + 1), null, true, null, true);
- if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_EQUAL) {
- return true;
- }
- if ($prevNonEmpty !== false
- && $tokens[$prevNonEmpty]['code'] === \T_AS
- && isset($tokens[$prevNonEmpty]['nested_parenthesis']) === true
- ) {
- $parentheses = array_reverse($tokens[$prevNonEmpty]['nested_parenthesis'], true);
- foreach ($parentheses as $open => $close) {
- if (isset($tokens[$open]['parenthesis_owner'])
- && $tokens[$tokens[$open]['parenthesis_owner']]['code'] === \T_FOREACH
- ) {
- return true;
- }
- }
- }
- // Maybe this is a short list syntax nested inside another short list syntax ?
- $parentOpener = $opener;
- do {
- $parentOpener = $phpcsFile->findPrevious(
- array(\T_OPEN_SHORT_ARRAY, \T_OPEN_SQUARE_BRACKET),
- ($parentOpener - 1),
- null,
- false,
- null,
- true
- );
- if ($parentOpener === false) {
- return false;
- }
- } while (isset($tokens[$parentOpener]['bracket_closer']) === true
- && $tokens[$parentOpener]['bracket_closer'] < $opener
- );
- if (isset($tokens[$parentOpener]['bracket_closer']) === true
- && $tokens[$parentOpener]['bracket_closer'] > $closer
- ) {
- // Work around tokenizer issue in PHPCS 2.0 - 2.7.
- $phpcsVersion = PHPCSHelper::getVersion();
- if ((version_compare($phpcsVersion, '2.0', '>') === true
- && version_compare($phpcsVersion, '2.8', '<') === true)
- && $tokens[$parentOpener]['code'] === \T_OPEN_SQUARE_BRACKET
- ) {
- $nextNonEmpty = $phpcsFile->findNext(
- Tokens::$emptyTokens,
- ($tokens[$parentOpener]['bracket_closer'] + 1),
- null,
- true,
- null,
- true
- );
- if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_EQUAL) {
- return true;
- }
- return false;
- }
- return $this->isShortList($phpcsFile, $parentOpener);
- }
- return false;
- }
- /**
- * Determine whether the tokens between $start and $end could together represent a variable.
- *
- * @since 9.0.0
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $start Starting point stack pointer. Inclusive.
- * I.e. this token should be taken into account.
- * @param int $end End point stack pointer. Exclusive.
- * I.e. this token should not be taken into account.
- * @param int $targetNestingLevel The nesting level the variable should be at.
- *
- * @return bool
- */
- public function isVariable(File $phpcsFile, $start, $end, $targetNestingLevel)
- {
- static $tokenBlackList, $bracketTokens;
- // Create the token arrays only once.
- if (isset($tokenBlackList, $bracketTokens) === false) {
- $tokenBlackList = array(
- \T_OPEN_PARENTHESIS => \T_OPEN_PARENTHESIS,
- \T_STRING_CONCAT => \T_STRING_CONCAT,
- );
- $tokenBlackList += Tokens::$assignmentTokens;
- $tokenBlackList += Tokens::$equalityTokens;
- $tokenBlackList += Tokens::$comparisonTokens;
- $tokenBlackList += Tokens::$operators;
- $tokenBlackList += Tokens::$booleanOperators;
- $tokenBlackList += Tokens::$castTokens;
- /*
- * List of brackets which can be part of a variable variable.
- *
- * Key is the open bracket token, value the close bracket token.
- */
- $bracketTokens = array(
- \T_OPEN_CURLY_BRACKET => \T_CLOSE_CURLY_BRACKET,
- \T_OPEN_SQUARE_BRACKET => \T_CLOSE_SQUARE_BRACKET,
- );
- }
- $tokens = $phpcsFile->getTokens();
- // If no variable at all was found, then it's definitely a no-no.
- $hasVariable = $phpcsFile->findNext(\T_VARIABLE, $start, $end);
- if ($hasVariable === false) {
- return false;
- }
- // Check if the variable found is at the right level. Deeper levels are always an error.
- if (isset($tokens[$hasVariable]['nested_parenthesis'])
- && \count($tokens[$hasVariable]['nested_parenthesis']) !== $targetNestingLevel
- ) {
- return false;
- }
- // Ok, so the first variable is at the right level, now are there any
- // blacklisted tokens within the empty() ?
- $hasBadToken = $phpcsFile->findNext($tokenBlackList, $start, $end);
- if ($hasBadToken === false) {
- return true;
- }
- // If there are also bracket tokens, the blacklisted token might be part of a variable
- // variable, but if there are no bracket tokens, we know we have an error.
- $hasBrackets = $phpcsFile->findNext($bracketTokens, $start, $end);
- if ($hasBrackets === false) {
- return false;
- }
- // Ok, we have both a blacklisted token as well as brackets, so we need to walk
- // the tokens of the variable variable.
- for ($i = $start; $i < $end; $i++) {
- // If this is a bracket token, skip to the end of the bracketed expression.
- if (isset($bracketTokens[$tokens[$i]['code']], $tokens[$i]['bracket_closer'])) {
- $i = $tokens[$i]['bracket_closer'];
- continue;
- }
- // If it's a blacklisted token, not within brackets, we have an error.
- if (isset($tokenBlackList[$tokens[$i]['code']])) {
- return false;
- }
- }
- return true;
- }
- /**
- * Determine whether a T_MINUS/T_PLUS token is a unary operator.
- *
- * N.B.: This is a back-fill for a new method which is expected to go into
- * PHP_CodeSniffer 3.5.0.
- * Once that method has been merged into PHPCS, this one should be moved
- * to the PHPCSHelper.php file.
- *
- * @since 9.2.0
- *
- * @codeCoverageIgnore Method as pulled upstream is accompanied by unit tests.
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr The position of the plus/minus token.
- *
- * @return bool True if the token passed is a unary operator.
- * False otherwise or if the token is not a T_PLUS/T_MINUS token.
- */
- public static function isUnaryPlusMinus(File $phpcsFile, $stackPtr)
- {
- $tokens = $phpcsFile->getTokens();
- if (isset($tokens[$stackPtr]) === false
- || ($tokens[$stackPtr]['code'] !== \T_PLUS
- && $tokens[$stackPtr]['code'] !== \T_MINUS)
- ) {
- return false;
- }
- $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
- if ($next === false) {
- // Live coding or parse error.
- return false;
- }
- if (isset(Tokens::$operators[$tokens[$next]['code']]) === true) {
- // Next token is an operator, so this is not a unary.
- return false;
- }
- $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
- if ($tokens[$prev]['code'] === \T_RETURN) {
- // Just returning a positive/negative value; eg. (return -1).
- return true;
- }
- if (isset(Tokens::$operators[$tokens[$prev]['code']]) === true) {
- // Just trying to operate on a positive/negative value; eg. ($var * -1).
- return true;
- }
- if (isset(Tokens::$comparisonTokens[$tokens[$prev]['code']]) === true) {
- // Just trying to compare a positive/negative value; eg. ($var === -1).
- return true;
- }
- if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) {
- // Just trying to compare a positive/negative value; eg. ($var || -1 === $b).
- return true;
- }
- if (isset(Tokens::$assignmentTokens[$tokens[$prev]['code']]) === true) {
- // Just trying to assign a positive/negative value; eg. ($var = -1).
- return true;
- }
- if (isset(Tokens::$castTokens[$tokens[$prev]['code']]) === true) {
- // Just casting a positive/negative value; eg. (string) -$var.
- return true;
- }
- // Other indicators that a plus/minus sign is a unary operator.
- $invalidTokens = array(
- \T_COMMA => true,
- \T_OPEN_PARENTHESIS => true,
- \T_OPEN_SQUARE_BRACKET => true,
- \T_OPEN_SHORT_ARRAY => true,
- \T_COLON => true,
- \T_INLINE_THEN => true,
- \T_INLINE_ELSE => true,
- \T_CASE => true,
- \T_OPEN_CURLY_BRACKET => true,
- \T_STRING_CONCAT => true,
- );
- if (isset($invalidTokens[$tokens[$prev]['code']]) === true) {
- // Just trying to use a positive/negative value; eg. myFunction($var, -2).
- return true;
- }
- return false;
- }
- /**
- * Get the complete contents of a multi-line text string.
- *
- * N.B.: This is a back-fill for a new method which is expected to go into
- * PHP_CodeSniffer 3.5.0.
- * Once that method has been merged into PHPCS, this one should be moved
- * to the PHPCSHelper.php file.
- *
- * @since 9.3.0
- *
- * @codeCoverageIgnore Method as pulled upstream is accompanied by unit tests.
- *
- * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
- * @param int $stackPtr Pointer to the first text string token
- * of a multi-line text string or to a
- * Nowdoc/Heredoc opener.
- * @param bool $stripQuotes Optional. Whether to strip text delimiter
- * quotes off the resulting text string.
- * Defaults to true.
- *
- * @return string
- *
- * @throws \PHP_CodeSniffer_Exception If the specified position is not a
- * valid text string token or if the
- * token is not the first text string token.
- */
- public function getCompleteTextString(File $phpcsFile, $stackPtr, $stripQuotes = true)
- {
- $tokens = $phpcsFile->getTokens();
- // Must be the start of a text string token.
- if ($tokens[$stackPtr]['code'] !== \T_START_HEREDOC
- && $tokens[$stackPtr]['code'] !== \T_START_NOWDOC
- && $tokens[$stackPtr]['code'] !== \T_CONSTANT_ENCAPSED_STRING
- && $tokens[$stackPtr]['code'] !== \T_DOUBLE_QUOTED_STRING
- ) {
- throw new PHPCS_Exception('$stackPtr must be of type T_START_HEREDOC, T_START_NOWDOC, T_CONSTANT_ENCAPSED_STRING or T_DOUBLE_QUOTED_STRING');
- }
- if ($tokens[$stackPtr]['code'] === \T_CONSTANT_ENCAPSED_STRING
- || $tokens[$stackPtr]['code'] === \T_DOUBLE_QUOTED_STRING
- ) {
- $prev = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
- if ($tokens[$stackPtr]['code'] === $tokens[$prev]['code']) {
- throw new PHPCS_Exception('$stackPtr must be the start of the text string');
- }
- }
- switch ($tokens[$stackPtr]['code']) {
- case \T_START_HEREDOC:
- $stripQuotes = false;
- $targetType = \T_HEREDOC;
- $current = ($stackPtr + 1);
- break;
- case \T_START_NOWDOC:
- $stripQuotes = false;
- $targetType = \T_NOWDOC;
- $current = ($stackPtr + 1);
- break;
- default:
- $targetType = $tokens[$stackPtr]['code'];
- $current = $stackPtr;
- break;
- }
- $string = '';
- do {
- $string .= $tokens[$current]['content'];
- ++$current;
- } while ($tokens[$current]['code'] === $targetType);
- if ($stripQuotes === true) {
- return $this->stripQuotes($string);
- }
- return $string;
- }
- }
|