Skip to content

Commit

Permalink
Merge pull request #15 from webimpress/hotfix/disallow-fqn-conflict
Browse files Browse the repository at this point in the history
Hotfix DisallowFqn - classes were not imported
  • Loading branch information
michalbundyra committed May 13, 2019
2 parents bb74e90 + 4dd466b commit f306191
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 90 deletions.
1 change: 1 addition & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
<file>src</file>
<file>test</file>

<exclude-pattern>test/Integration/*.php</exclude-pattern>
<exclude-pattern>test/*.inc</exclude-pattern>
</ruleset>
210 changes: 120 additions & 90 deletions src/WebimpressCodingStandard/Sniffs/PHP/DisallowFqnSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,38 +119,55 @@ public function process(File $phpcsFile, $stackPtr)
$namespace = '';
$currentNamespacePtr = null;
$toImport = [];
$toFix = [];

do {
$namespacePtr = $phpcsFile->findPrevious(T_NAMESPACE, $stackPtr - 1) ?: null;

if ($namespacePtr !== $currentNamespacePtr) {
$namespace = $namespacePtr ? $this->getName($phpcsFile, $namespacePtr + 1) : '';
if ($currentNamespacePtr) {
$this->importReferences($phpcsFile, $currentNamespacePtr, $toImport);
if ($toImport || $toFix) {
$phpcsFile->fixer->beginChangeset();
if ($currentNamespacePtr) {
$this->importReferences($phpcsFile, $currentNamespacePtr, $toImport);
}
$this->fixErrors($phpcsFile, $toFix);
$phpcsFile->fixer->endChangeset();
}

$currentNamespacePtr = $namespacePtr;
$toImport = [];
$toFix = [];

$this->imported = $this->getGlobalUses($phpcsFile, $stackPtr, 'all');
}

if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_TAG) {
$this->processTag($phpcsFile, $stackPtr, $namespace, $toImport);
} elseif ($reference = $this->processString($phpcsFile, $stackPtr, $namespace)) {
$toImport[] = $reference;
$this->processTag($phpcsFile, $stackPtr, $namespace, $toImport, $toFix);
} else {
$this->processString($phpcsFile, $stackPtr, $namespace, $toImport, $toFix);
}
} while ($stackPtr = $phpcsFile->findNext($this->register(), $stackPtr + 1));

if ($currentNamespacePtr) {
$this->importReferences($phpcsFile, $currentNamespacePtr, $toImport);
if ($toImport || $toFix) {
$phpcsFile->fixer->beginChangeset();
if ($currentNamespacePtr) {
$this->importReferences($phpcsFile, $currentNamespacePtr, $toImport);
}
$this->fixErrors($phpcsFile, $toFix);
$phpcsFile->fixer->endChangeset();
}

return $phpcsFile->numTokens + 1;
}

private function processTag(File $phpcsFile, int $stackPtr, string $namespace, array &$toImport) : void
{
private function processTag(
File $phpcsFile,
int $stackPtr,
string $namespace,
array &$toImport,
array &$toFix
) : void {
$tokens = $phpcsFile->getTokens();

if (! in_array($tokens[$stackPtr]['content'], CodingStandard::TAG_WITH_TYPE, true)
Expand Down Expand Up @@ -191,10 +208,7 @@ private function processTag(File $phpcsFile, int $stackPtr, string $namespace, a
$toImport = array_merge($toImport, $localToImport);
}

$phpcsFile->fixer->replaceToken(
$stackPtr + 2,
preg_replace('/^' . preg_quote($types, '/') . '/', $newTypes, $string)
);
$toFix[$stackPtr + 2] = preg_replace('/^' . preg_quote($types, '/') . '/', $newTypes, $string);
}
}
}
Expand Down Expand Up @@ -235,25 +249,30 @@ private function getExpectedName(
}

// We need to import it
$toImport[] = $this->import('class', $name, $alias);
$toImport += $this->import('class', $name, $alias);

return $alias;
}

private function processString(File $phpcsFile, int $stackPtr, string $namespace) : ?array
{
private function processString(
File $phpcsFile,
int $stackPtr,
string $namespace,
array &$toImport,
array &$toFix
) : void {
$tokens = $phpcsFile->getTokens();

$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true);

// Part of the name
if ($tokens[$prev]['code'] === T_STRING || $tokens[$prev]['code'] === T_NAMESPACE) {
return null;
return;
}

// In the global use statement
if ($tokens[$prev]['code'] === T_USE && CodingStandard::isGlobalUse($phpcsFile, $prev)) {
return null;
return;
}

if (! $namespace) {
Expand All @@ -263,7 +282,7 @@ private function processString(File $phpcsFile, int $stackPtr, string $namespace
$phpcsFile->fixer->replaceToken($stackPtr, '');
}

return null;
return;
}

$next = $phpcsFile->findNext(
Expand Down Expand Up @@ -357,27 +376,33 @@ private function processString(File $phpcsFile, int $stackPtr, string $namespace
$phpcsFile->fixer->endChangeset();
}

return null;
return;
}

// If function is built-in function; skip
if ($type === 'function' && isset($this->builtInFunctions[strtolower($name)])) {
return null;
return;
}

// If constant is built-in constant; skip
if ($type === 'const' && isset($this->builtInConstants[strtoupper($name)])) {
return null;
return;
}

foreach ($this->imported['class'] ?? [] as $class) {
// If namespace or part of it is already imported
if (stripos($name . '\\', $class['fqn'] . '\\') === 0) {
$error = 'Namespace %s is already imported';
$data = [$class['fqn']];
$this->error($phpcsFile, $error, $stackPtr, 'NamespaceImported', $data, $class['name']);

return null;
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'NamespaceImported', $data);
if ($fix) {
$phpcsFile->fixer->beginChangeset();
$this->fixError($phpcsFile, $stackPtr, $class['name']);
$phpcsFile->fixer->endChangeset();
}

return;
}
}

Expand All @@ -389,9 +414,15 @@ private function processString(File $phpcsFile, int $stackPtr, string $namespace
if (strtolower($function['fqn']) === strtolower($name)) {
$error = 'Function %s is already imported';
$data = [$function['fqn']];
$this->error($phpcsFile, $error, $stackPtr, 'FunctionImported', $data, $function['name']);

return null;
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'FunctionImported', $data);
if ($fix) {
$phpcsFile->fixer->beginChangeset();
$this->fixError($phpcsFile, $stackPtr, $function['name']);
$phpcsFile->fixer->endChangeset();
}

return;
}
}

Expand All @@ -401,7 +432,7 @@ private function processString(File $phpcsFile, int $stackPtr, string $namespace
$data = [$name, $alias];
$phpcsFile->addError($error, $stackPtr, 'FunctionAliasUsed', $data);

return null;
return;
}
}

Expand All @@ -411,9 +442,15 @@ private function processString(File $phpcsFile, int $stackPtr, string $namespace
if (strtolower($const['fqn']) === strtolower($name)) {
$error = 'Constant %s is already imported';
$data = [$const['fqn']];
$this->error($phpcsFile, $error, $stackPtr, 'ConstantImported', $data, $const['name']);

return null;
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'ConstantImported', $data);
if ($fix) {
$phpcsFile->fixer->beginChangeset();
$this->fixError($phpcsFile, $stackPtr, $const['name']);
$phpcsFile->fixer->endChangeset();
}

return;
}
}

Expand All @@ -423,23 +460,22 @@ private function processString(File $phpcsFile, int $stackPtr, string $namespace
$data = [$name, $alias];
$phpcsFile->addError($error, $stackPtr, 'ConstantAliasUsed', $data);

return null;
return;
}
}

if ($type === 'class' && ! $this->isValidClassName($phpcsFile, $stackPtr, $alias, $name)) {
return null;
return;
}

$error = '%s must be imported as %s';
$data = [$name, $alias];
$fix = $this->error($phpcsFile, $error, $stackPtr, 'Import', $data, $alias);

$fix = $phpcsFile->addFixableError($error, $stackPtr, 'Import', $data);
if ($fix) {
return $this->import($type, $name, $alias);
$toFix[$stackPtr] = $alias;
$toImport += $this->import($type, $name, $alias);
}

return null;
}

private function isValidClassName(File $phpcsFile, int $stackPtr, string $alias, string $name) : bool
Expand Down Expand Up @@ -476,60 +512,51 @@ private function isValidClassName(File $phpcsFile, int $stackPtr, string $alias,
return true;
}

private function error(
File $phpcsFile,
string $error,
int $stackPtr,
string $code,
array $data,
string $expected
) : bool {
$fix = $phpcsFile->addFixableError($error, $stackPtr, $code, $data);
if ($fix) {
$tokens = $phpcsFile->getTokens();

if (in_array($tokens[$stackPtr - 1]['code'], [
T_NEW,
T_USE,
T_EXTENDS,
T_IMPLEMENTS,
T_INSTANCEOF,
T_INSTEADOF,
T_CASE,
T_PRINT,
T_ECHO,
T_REQUIRE,
T_REQUIRE_ONCE,
T_INCLUDE,
T_INCLUDE_ONCE,
T_RETURN,
T_LOGICAL_AND,
T_LOGICAL_OR,
T_LOGICAL_XOR,
T_THROW,
], true)) {
$expected = ' ' . $expected;
}

$phpcsFile->fixer->beginChangeset();
private function fixError(File $phpcsFile, int $stackPtr, string $expected) : void
{
$tokens = $phpcsFile->getTokens();

if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_STRING) {
$phpcsFile->fixer->replaceToken($stackPtr, $expected);
$i = $stackPtr;
while (isset($tokens[++$i])) {
if (in_array($tokens[$i]['code'], Tokens::$emptyTokens, true)) {
continue;
}
return;
}

if (! in_array($tokens[$i]['code'], [T_NS_SEPARATOR, T_STRING], true)) {
break;
}
if (in_array($tokens[$stackPtr - 1]['code'], [
T_NEW,
T_USE,
T_EXTENDS,
T_IMPLEMENTS,
T_INSTANCEOF,
T_INSTEADOF,
T_CASE,
T_PRINT,
T_ECHO,
T_REQUIRE,
T_REQUIRE_ONCE,
T_INCLUDE,
T_INCLUDE_ONCE,
T_RETURN,
T_LOGICAL_AND,
T_LOGICAL_OR,
T_LOGICAL_XOR,
T_THROW,
], true)) {
$expected = ' ' . $expected;
}

$phpcsFile->fixer->replaceToken($i, '');
$phpcsFile->fixer->replaceToken($stackPtr, $expected);
$i = $stackPtr;
while (isset($tokens[++$i])) {
if (in_array($tokens[$i]['code'], Tokens::$emptyTokens, true)) {
continue;
}
$phpcsFile->fixer->endChangeset();
}

return $fix;
if (! in_array($tokens[$i]['code'], [T_NS_SEPARATOR, T_STRING], true)) {
break;
}

$phpcsFile->fixer->replaceToken($i, '');
}
}

private function import(string $type, string $fqn, string $alias) : array
Expand All @@ -539,7 +566,7 @@ private function import(string $type, string $fqn, string $alias) : array
'fqn' => $fqn,
];

return [$type, $fqn];
return [$fqn => $type];
}

/**
Expand All @@ -551,8 +578,6 @@ private function importReferences(File $phpcsFile, int $namespacePtr, array $ref
return;
}

$phpcsFile->fixer->beginChangeset();

$tokens = $phpcsFile->getTokens();
if (isset($tokens[$namespacePtr]['scope_opener'])) {
$ptr = $tokens[$namespacePtr]['scope_opener'];
Expand All @@ -562,17 +587,22 @@ private function importReferences(File $phpcsFile, int $namespacePtr, array $ref
}

$content = '';
foreach ($references as $data) {
foreach ($references as $fqn => $type) {
$content .= sprintf(
'%suse %s%s;',
$phpcsFile->eolChar,
$data[0] === 'class' ? '' : $data[0] . ' ',
$data[1]
$type === 'class' ? '' : $type . ' ',
$fqn
);
}

$phpcsFile->fixer->addContent($ptr, $content);
}

$phpcsFile->fixer->endChangeset();
private function fixErrors(File $phpcsFile, array $replacements)
{
foreach ($replacements as $ptr => $alias) {
$this->fixError($phpcsFile, $ptr, $alias);
}
}
}
Loading

0 comments on commit f306191

Please sign in to comment.