Skip to content

Commit 7b26ad1

Browse files
authored
Create Helpers class for pure functions (#34)
* Move findContainingBrackets to Helpers class * Move areAnyConditionsAClosure/Class to Helpers * Move findPreviousFunctionPtr to Helpers * Move findFunctionCall/Arguments to Helpers * Move findWhereAssignExecuted to Helpers * Move isNextThingAnAssign to Helpers * Move normalizeVarName to Helpers * Move findVariableScope to Helpers * Move getStackPtrIfVariableIsUnused to Helpers * Rename findContainingBrackets to findContainingOpeningBracket
1 parent 3245849 commit 7b26ad1

File tree

2 files changed

+225
-220
lines changed

2 files changed

+225
-220
lines changed

VariableAnalysis/Lib/Helpers.php

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
<?php
2+
3+
namespace VariableAnalysis\Lib;
4+
5+
use PHP_CodeSniffer\Files\File;
6+
7+
class Helpers {
8+
public static function findContainingOpeningBracket(File $phpcsFile, int $stackPtr) {
9+
$tokens = $phpcsFile->getTokens();
10+
if (isset($tokens[$stackPtr]['nested_parenthesis'])) {
11+
$openPtrs = array_keys($tokens[$stackPtr]['nested_parenthesis']);
12+
return end($openPtrs);
13+
}
14+
return false;
15+
}
16+
17+
public static function areAnyConditionsAClosure(File $phpcsFile, array $conditions) {
18+
// self within a closure is invalid
19+
$tokens = $phpcsFile->getTokens();
20+
foreach (array_reverse($conditions, true) as $scopePtr => $scopeCode) {
21+
// Note: have to fetch code from $tokens, T_CLOSURE isn't set for conditions codes.
22+
if ($tokens[$scopePtr]['code'] === T_CLOSURE) {
23+
return true;
24+
}
25+
}
26+
return false;
27+
}
28+
29+
public static function areAnyConditionsAClass(array $conditions) {
30+
foreach (array_reverse($conditions, true) as $scopePtr => $scopeCode) {
31+
if ($scopeCode === T_CLASS) {
32+
return true;
33+
}
34+
}
35+
return false;
36+
}
37+
38+
public static function findPreviousFunctionPtr(File $phpcsFile, int $openPtr) {
39+
// Function names are T_STRING, and return-by-reference is T_BITWISE_AND,
40+
// so we look backwards from the opening bracket for the first thing that
41+
// isn't a function name, reference sigil or whitespace and check if it's a
42+
// function keyword.
43+
$functionPtrTypes = [T_STRING, T_WHITESPACE, T_BITWISE_AND];
44+
return $phpcsFile->findPrevious($functionPtrTypes, $openPtr - 1, null, true, null, true);
45+
}
46+
47+
public static function findFunctionCall(File $phpcsFile, int $stackPtr) {
48+
$tokens = $phpcsFile->getTokens();
49+
50+
$openPtr = Helpers::findContainingOpeningBracket($phpcsFile, $stackPtr);
51+
if ($openPtr) {
52+
// First non-whitespace thing and see if it's a T_STRING function name
53+
$functionPtr = $phpcsFile->findPrevious(T_WHITESPACE, $openPtr - 1, null, true, null, true);
54+
if ($tokens[$functionPtr]['code'] === T_STRING) {
55+
return $functionPtr;
56+
}
57+
}
58+
return false;
59+
}
60+
61+
public static function findFunctionCallArguments(File $phpcsFile, $stackPtr) {
62+
$tokens = $phpcsFile->getTokens();
63+
64+
// Slight hack: also allow this to find args for array constructor.
65+
// TODO: probably should refactor into three functions: arg-finding and bracket-finding
66+
if (($tokens[$stackPtr]['code'] !== T_STRING) && ($tokens[$stackPtr]['code'] !== T_ARRAY)) {
67+
// Assume $stackPtr is something within the brackets, find our function call
68+
$stackPtr = Helpers::findFunctionCall($phpcsFile, $stackPtr);
69+
if ($stackPtr === false) {
70+
return false;
71+
}
72+
}
73+
74+
// $stackPtr is the function name, find our brackets after it
75+
$openPtr = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true, null, true);
76+
if (($openPtr === false) || ($tokens[$openPtr]['code'] !== T_OPEN_PARENTHESIS)) {
77+
return false;
78+
}
79+
80+
if (!isset($tokens[$openPtr]['parenthesis_closer'])) {
81+
return false;
82+
}
83+
$closePtr = $tokens[$openPtr]['parenthesis_closer'];
84+
85+
$argPtrs = [];
86+
$lastPtr = $openPtr;
87+
$lastArgComma = $openPtr;
88+
while (($nextPtr = $phpcsFile->findNext(T_COMMA, $lastPtr + 1, $closePtr)) !== false) {
89+
if (Helpers::findContainingOpeningBracket($phpcsFile, $nextPtr) == $openPtr) {
90+
// Comma is at our level of brackets, it's an argument delimiter.
91+
array_push($argPtrs, range($lastArgComma + 1, $nextPtr - 1));
92+
$lastArgComma = $nextPtr;
93+
}
94+
$lastPtr = $nextPtr;
95+
}
96+
array_push($argPtrs, range($lastArgComma + 1, $closePtr - 1));
97+
98+
return $argPtrs;
99+
}
100+
101+
public static function findWhereAssignExecuted(File $phpcsFile, int $stackPtr) {
102+
$tokens = $phpcsFile->getTokens();
103+
104+
// Write should be recorded at the next statement to ensure we treat the
105+
// assign as happening after the RHS execution.
106+
// eg: $var = $var + 1; -> RHS could still be undef.
107+
// However, if we're within a bracketed expression, we take place at the
108+
// closing bracket, if that's first.
109+
// eg: echo (($var = 12) && ($var == 12));
110+
$semicolonPtr = $phpcsFile->findNext(T_SEMICOLON, $stackPtr + 1, null, false, null, true);
111+
$commaPtr = $phpcsFile->findNext(T_COMMA, $stackPtr + 1, null, false, null, true);
112+
$closePtr = false;
113+
$openPtr = Helpers::findContainingOpeningBracket($phpcsFile, $stackPtr);
114+
if ($openPtr !== false) {
115+
if (isset($tokens[$openPtr]['parenthesis_closer'])) {
116+
$closePtr = $tokens[$openPtr]['parenthesis_closer'];
117+
}
118+
}
119+
120+
// Return the first thing: comma, semicolon, close-bracket, or stackPtr if nothing else
121+
$assignEndTokens = [$commaPtr, $semicolonPtr, $closePtr];
122+
$assignEndTokens = array_filter($assignEndTokens); // remove false values
123+
sort($assignEndTokens);
124+
if (empty($assignEndTokens)) {
125+
return $stackPtr;
126+
}
127+
return $assignEndTokens[0];
128+
}
129+
130+
public static function isNextThingAnAssign(File $phpcsFile, int $stackPtr) {
131+
$tokens = $phpcsFile->getTokens();
132+
133+
// Is the next non-whitespace an assignment?
134+
$nextPtr = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true, null, true);
135+
if ($nextPtr !== false) {
136+
if ($tokens[$nextPtr]['code'] === T_EQUAL) {
137+
return $nextPtr;
138+
}
139+
}
140+
return false;
141+
}
142+
143+
public static function normalizeVarName($varName) {
144+
return preg_replace('/[{}$]/', '', $varName);
145+
}
146+
147+
public static function findFunctionPrototype(File $phpcsFile, int $stackPtr) {
148+
$tokens = $phpcsFile->getTokens();
149+
$token = $tokens[$stackPtr];
150+
151+
$openPtr = Helpers::findContainingOpeningBracket($phpcsFile, $stackPtr);
152+
if ($openPtr === false) {
153+
return false;
154+
}
155+
$functionPtr = Helpers::findPreviousFunctionPtr($phpcsFile, $openPtr);
156+
if (($functionPtr !== false) && ($tokens[$functionPtr]['code'] === T_FUNCTION)) {
157+
return $functionPtr;
158+
}
159+
return false;
160+
}
161+
162+
public static function findVariableScope(File $phpcsFile, int $stackPtr) {
163+
$tokens = $phpcsFile->getTokens();
164+
$token = $tokens[$stackPtr];
165+
166+
$in_class = false;
167+
if (!empty($token['conditions'])) {
168+
foreach (array_reverse($token['conditions'], true) as $scopePtr => $scopeCode) {
169+
if (($scopeCode === T_FUNCTION) || ($scopeCode === T_CLOSURE)) {
170+
return $scopePtr;
171+
}
172+
if (($scopeCode === T_CLASS) || ($scopeCode === T_INTERFACE)) {
173+
$in_class = true;
174+
}
175+
}
176+
}
177+
178+
$scopePtr = Helpers::findFunctionPrototype($phpcsFile, $stackPtr);
179+
if ($scopePtr !== false) {
180+
return $scopePtr;
181+
}
182+
183+
if ($in_class) {
184+
// Member var of a class, we don't care.
185+
return false;
186+
}
187+
188+
// File scope, hmm, lets use first token of file?
189+
return 0;
190+
}
191+
192+
public static function getStackPtrIfVariableIsUnused(VariableInfo $varInfo) {
193+
if (isset($varInfo->firstDeclared)) {
194+
return $varInfo->firstDeclared;
195+
}
196+
if (isset($varInfo->firstInitialized)) {
197+
return $varInfo->firstInitialized;
198+
}
199+
return null;
200+
}
201+
}

0 commit comments

Comments
 (0)