Skip to content

Commit

Permalink
Merge pull request #1 from guymers/use-statement-organization
Browse files Browse the repository at this point in the history
Use statement organization detection and autofixer
  • Loading branch information
vektah committed Jul 25, 2013
2 parents fc7a65a + 1f370c4 commit 712420a
Show file tree
Hide file tree
Showing 15 changed files with 494 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
/composer.lock
/.idea
*.iml

.buildpath
.settings
.project

2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},

"require-dev": {
"phake/phake": "1.0.3",
"phake/phake": "v2.0.0-alpha2",
"phpunit/phpunit": "3.7.21"
},

Expand Down
1 change: 1 addition & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<phpunit backupGlobals="true"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
cacheTokens="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
Expand Down
1 change: 1 addition & 0 deletions src/main/bugfree/ErrorType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class ErrorType
const MULTI_STATEMENT_USE = 'multiStatementUse';
const MISSING_NAMESPACE = 'missingNamespace';
const UNUSED_USE = 'unusedUse';
const DISORGANIZED_USES = 'disorganizedUses';

const WARNING = 'warning';
const ERROR = 'error';
Expand Down
2 changes: 1 addition & 1 deletion src/main/bugfree/cli/Lint.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
return $this->lintFiles($basedir, $config, $formatter, $files);
}

public function lintFiles($basedir, Config $config, OutputFormatter $formatter, array $files)
private function lintFiles($basedir, Config $config, OutputFormatter $formatter, array $files)
{
$bugfree = new Bugfree(new AutoloaderResolver($basedir), $config);

Expand Down
3 changes: 2 additions & 1 deletion src/main/bugfree/config/EmitLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ class EmitLevel
public $multiStatementUse = ErrorType::WARNING;
public $missingNamespace = ErrorType::ERROR;
public $unusedUse = ErrorType::WARNING;
public $disorganizedUses = ErrorType::WARNING;

public function __construct(array $values=[])
public function __construct(array $values = [])
{
foreach ($values as $key => $value) {
$this->$key = $value;
Expand Down
31 changes: 31 additions & 0 deletions src/main/bugfree/fix/AbstractFix.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace bugfree\fix;


abstract class AbstractFix implements Fix
{
/** @var int */
private $line;

/** @var string */
private $reason;

public function __construct($line, $reason)
{
$this->line = $line;
$this->reason = $reason;
}

public function getLine()
{
return $this->line;
}

abstract public function run(array &$fileLines);

public function getReason()
{
return $this->reason;
}
}
26 changes: 2 additions & 24 deletions src/main/bugfree/fix/RemoveLineFix.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,10 @@
namespace bugfree\fix;


class RemoveLineFix implements Fix
class RemoveLineFix extends AbstractFix
{
/** @var int */
private $line;

/** @var string */
private $reason;

public function __construct($line, $reason)
{
$this->line = $line;
$this->reason = $reason;
}

public function getLine()
{
return $this->line;
}

public function run(array &$fileLines)
{
array_splice($fileLines, $this->line - 1, 1);
}

public function getReason()
{
return $this->reason;
array_splice($fileLines, $this->getLine() - 1, 1);
}
}
28 changes: 28 additions & 0 deletions src/main/bugfree/fix/SwapLineFix.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace bugfree\fix;


class SwapLineFix extends AbstractFix
{
/** @var int */
private $destinationLine;

public function __construct($sourceLine, $destinationLine, $reason)
{
parent::__construct($sourceLine, $reason);
$this->destinationLine = $destinationLine;
}

public function run(array &$fileLines)
{
$sourceIndex = $this->getLine() - 1;
$destinationIndex = $this->destinationLine - 1;

$sourceLine = $fileLines[$sourceIndex];
$destinationLine = $fileLines[$destinationIndex];

$fileLines[$sourceIndex] = $destinationLine;
$fileLines[$destinationIndex] = $sourceLine;
}
}
188 changes: 188 additions & 0 deletions src/main/bugfree/helper/UseStatementOrganizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?php

namespace bugfree\helper;


use PHPParser_Node_Stmt_UseUse as UseStatement;

/**
* Checks if use statements are organized and determines what is required to
* organize them.
*/
class UseStatementOrganizer
{
/** @var UseStatement[] */
private $useStatements = [];

/** @var UseStatement[] */
private $organizedUseStatements = [];

/** @var array */
private $lineNumberMapping = null;

/**
* @param UseStatement[] $useStatements An array of use statements in the order they are defined
*/
public function __construct(array $useStatements)
{
$this->useStatements = $useStatements;
$this->organizedUseStatements = $this->getOrganizedUseStatements();
}

/**
* @return UseStatement[]
*/
private function getOrganizedUseStatements()
{
$organizer = function (UseStatement $a, UseStatement $b) {
$aParts = $a->name->parts;
$aNumParts = count($aParts);
$bParts = $b->name->parts;
$bNumParts = count($bParts);
$numParts = min($aNumParts, $bNumParts);

$i = 0;

while ($i < $numParts) {
$aPart = $aParts[$i];
$bPart = $bParts[$i];

// sort \a\b\c\Class before \a\b\c\a\Class
$isLastAPart = $i == ($aNumParts - 1);
$isLastBPart = $i == ($bNumParts - 1);

if ($isLastAPart && !$isLastBPart) {
return -1;
} elseif ($isLastBPart && !$isLastAPart) {
return 1;
}

$comparison = strcasecmp($aPart, $bPart);

if ($comparison !== 0) {
return $comparison;
}

$i++;
}

return 0;
};

// create a copy
$useStatements = $this->useStatements;

usort($useStatements, $organizer);

return $useStatements;
}

/**
* Returns true if the list of use statements are organized.
*
* @return boolean
*/
public function areOrganized()
{
$current = $this->useStatementsToString($this->useStatements);
$organized = $this->useStatementsToString($this->organizedUseStatements);

return count(array_diff_assoc($current, $organized)) == 0;
}

/**
* @param UseStatement[] $useStatements
* @return array
*/
private function useStatementsToString(array $useStatements)
{
$useStrings = [];

foreach ($useStatements as $useStatement) {
$useStrings[] = $useStatement->name->toString();
}

return $useStrings;
}

/**
* Calculates any line swaps that need to take place in order for the use
* statements to be organized.
*
* @return array hash of current line number to new line number
*/
public function getLineSwaps()
{
$lineSwaps = [];
$currentLineNumbers = $this->useStatementsToLineNumbers($this->useStatements);
$organizedLineNumbers = $this->useStatementsToLineNumbers($this->organizedUseStatements);

// keep track of the swaps
$this->lineNumberMapping = [];
foreach ($currentLineNumbers as $currentLineNumber) {
$this->lineNumberMapping[$currentLineNumber] = $currentLineNumber;
}

// start from the bottom
for ($organizedIndex = count($organizedLineNumbers) - 1; $organizedIndex >= 0; $organizedIndex--) {
$currentLineNumber = $currentLineNumbers[$organizedIndex];
$organizedLineNumber = $organizedLineNumbers[$organizedIndex];
$newLineNumber = array_search($organizedLineNumber, $this->lineNumberMapping);

if ($currentLineNumber != $newLineNumber) {
$this->lineNumberMapping = $this->swap($this->lineNumberMapping, $currentLineNumber, $newLineNumber);

$lineSwaps[$currentLineNumber] = $newLineNumber;
}
}

return $lineSwaps;
}

/**
* @param UseStatement[] $useStatements
* @return array
*/
private function useStatementsToLineNumbers(array $useStatements)
{
$useLines = [];

foreach ($useStatements as $useStatement) {
$useLines[] = $useStatement->getLine();
}

return $useLines;
}

/**
* @param array $arr
* @param mixed $currentKey
* @param mixed $newKey
* @return array
*/
private function swap(array $arr, $currentKey, $newKey)
{
$currentValue = $arr[$currentKey];
$newValue = $arr[$newKey];

$arr[$currentKey] = $newValue;
$arr[$newKey] = $currentValue;

return $arr;
}

/**
* Returns a hash with key of the current line number and value of the line
* number it should be at.
*
* @return array
*/
public function getLineNumberMapping()
{
if (!$this->lineNumberMapping) {
throw \BadMethodCallException("You must call getLineSwaps() before calling this function");
}

return $this->lineNumberMapping;
}
}
Loading

0 comments on commit 712420a

Please sign in to comment.