Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bears/php: Add PHPMessDetectorBear #1486

Merged
merged 1 commit into from Apr 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions .ci/deps.sh
Expand Up @@ -174,3 +174,11 @@ if [ ! -e ~/.local/tailor/tailor-latest ]; then
# Provide a constant path for the executable
ln -s ~/.local/tailor/tailor-* ~/.local/tailor/tailor-latest
fi

# PHPMD installation
if [ ! -e ~/phpmd/phpmd ]; then
mkdir -p ~/phpmd
curl -fsSL -o phpmd.phar http://static.phpmd.org/php/latest/phpmd.phar
sudo chmod +x phpmd.phar
sudo mv phpmd.phar ~/phpmd/phpmd
fi
3 changes: 2 additions & 1 deletion .travis.yml
Expand Up @@ -66,14 +66,15 @@ cache:
- ~/elm-format-0.18
- ~/dart-sdk/bin
- ~/.local/tailor/
- ~/phpmd

env:
global:
- CIRCLE_NODE_INDEX=-1 # Avoid accidentially being a CircleCI worker
- USE_PPAS="marutter/rdev staticfloat/juliareleases ondrej/golang"
- R_LIB_USER=~/R/Library
- LINTR_COMMENT_BOT=false
- PATH="/opt/cabal/1.24/bin:$PATH:$TRAVIS_BUILD_DIR/node_modules/.bin:$TRAVIS_BUILD_DIR/vendor/bin:$HOME/dart-sdk/bin:$HOME/.cabal/bin:$HOME/infer-linux64-v0.7.0/infer/bin:$HOME/pmd-bin-5.4.1/bin:$HOME/bakalint-0.4.0:$HOME/elm-format-0.18:$HOME/.local/tailor/tailor-latest/bin"
- PATH="/opt/cabal/1.24/bin:$PATH:$TRAVIS_BUILD_DIR/node_modules/.bin:$TRAVIS_BUILD_DIR/vendor/bin:$HOME/dart-sdk/bin:$HOME/.cabal/bin:$HOME/infer-linux64-v0.7.0/infer/bin:$HOME/pmd-bin-5.4.1/bin:$HOME/bakalint-0.4.0:$HOME/elm-format-0.18:$HOME/.local/tailor/tailor-latest/bin:$HOME/phpmd"

before_install:
# Remove Ruby directive from Gemfile as this image has 2.2.5
Expand Down
39 changes: 39 additions & 0 deletions bears/php/PHPMessDetectorBear.py
@@ -0,0 +1,39 @@
from coalib.bearlib.abstractions.Linter import linter
from coalib.settings.Setting import typed_list
from dependency_management.requirements.DistributionRequirement import (
DistributionRequirement)


@linter(executable='phpmd',
output_format='regex',
output_regex=r':(?P<line>\d+)\s*(?P<message>.*)')
class PHPMessDetectorBear:
"""
The bear takes a given PHP source code base and looks for several
potential problems within that source. These problems can be things like:

- Possible bugs
- Suboptimal code
- Overcomplicated expressions
- Unused parameters, methods, properties
"""

LANGUAGES = {'PHP'}
REQUIREMENTS = {DistributionRequirement(apt_get='phpmd')}
AUTHORS = {'The coala developers'}
AUTHORS_EMAILS = {'coala-devel@googlegroups.com'}
LICENSE = 'AGPL-3.0'
CAN_DETECT = {'Formatting', 'Complexity', 'Unused Code', 'Redundancy',
'Variable Misuse'}
SEE_MORE = 'https://phpmd.org/about.html'

@staticmethod
def create_arguments(filename, file, config_file,
phpmd_rulesets: typed_list(str)):
"""
:param phpmd_rulesets:
A list of rulesets to use for analysis.
Available rulesets: cleancode, codesize, controversial, design,
naming, unusedcode.
"""
return filename, 'text', ','.join(phpmd_rulesets)
1 change: 1 addition & 0 deletions circle.yml
Expand Up @@ -29,6 +29,7 @@ dependencies:
- echo 'export PATH=$PATH:~/bakalint-0.4.0' >> ~/.circlerc
- echo 'export PATH=$PATH:~/.local/tailor/tailor-latest/bin' >> ~/.circlerc
- echo 'export PATH=$PATH:~/elm-format-0.18' >> ~/.circlerc
- echo 'export PATH=$PATH:~/phpmd' >> ~/.circlerc
- echo 'export R_LIB_USER=~/.RLibrary' >> ~/.circlerc
- sed -i '/source \/home\/ubuntu\/virtualenvs\//d' ~/.circlerc
- mkdir -p ~/.RLibrary
Expand Down
122 changes: 122 additions & 0 deletions tests/php/PHPMessDetectorBearTest.py
@@ -0,0 +1,122 @@
import os
from queue import Queue

from bears.php.PHPMessDetectorBear import PHPMessDetectorBear
from coalib.testing.LocalBearTestHelper import LocalBearTestHelper
from coalib.testing.BearTestHelper import generate_skip_decorator
from coalib.results.Result import Result
from coalib.settings.Section import Section
from coalib.settings.Setting import Setting


def get_testfile_path(name):
return os.path.join(os.path.dirname(__file__),
'phpmessdetector_test_files',
name)


def load_testfile(name):
return open(get_testfile_path(name)).readlines()


@generate_skip_decorator(PHPMessDetectorBear)
class PHPMessDetectorBearTest(LocalBearTestHelper):

def setUp(self):
self.section = Section('name')
self.uut = PHPMessDetectorBear(self.section, Queue())

def test_cleancode_violation(self):
file_contents = load_testfile('cleancode_violation.php')
self.section.append(Setting('phpmd_rulesets', 'cleancode'))
self.check_results(
self.uut,
file_contents,
[Result.from_values('PHPMessDetectorBear',
'The method bar uses an else expression. Else '
'is never necessary and you can simplify the '
'code to work without else.',
file=get_testfile_path(
'cleancode_violation.php'),
line=8)],
filename=get_testfile_path('cleancode_violation.php'))

def test_codesize_violation(self):
file_contents = load_testfile('codesize_violation.php')
self.section.append(Setting('phpmd_rulesets', 'codesize'))
self.check_results(
self.uut,
file_contents,
[Result.from_values('PHPMessDetectorBear',
'The method example() has a Cyclomatic '
'Complexity of 11. The configured cyclomatic '
'complexity threshold is 10.',
file=get_testfile_path(
'codesize_violation.php'),
line=4)],
filename=get_testfile_path('codesize_violation.php'))

def test_design_violation(self):
file_contents = load_testfile('design_violation.php')
self.section.append(Setting('phpmd_rulesets', 'design'))
self.check_results(
self.uut,
file_contents,
[Result.from_values('PHPMessDetectorBear',
'The method bar() contains an exit '
'expression.',
file=get_testfile_path(
'design_violation.php'),
line=5),
Result.from_values('PHPMessDetectorBear',
'The method foo() contains an eval '
'expression.',
file=get_testfile_path(
'design_violation.php'),
line=12)],
filename=get_testfile_path('design_violation.php'))

def test_naming_violation(self):
file_contents = load_testfile('naming_violation.php')
self.section.append(Setting('phpmd_rulesets', 'naming'))
self.check_results(
self.uut,
file_contents,
[Result.from_values('PHPMessDetectorBear',
'Avoid variables with short names like $q. '
'Configured minimum length is 3.',
file=get_testfile_path(
'naming_violation.php'),
line=3),
Result.from_values('PHPMessDetectorBear',
'Avoid variables with short names like $as. '
'Configured minimum length is 3.',
file=get_testfile_path(
'naming_violation.php'),
line=4),
Result.from_values('PHPMessDetectorBear',
'Avoid variables with short names like $r. '
'Configured minimum length is 3.',
file=get_testfile_path(
'naming_violation.php'),
line=5)],
filename=get_testfile_path('naming_violation.php'))

def test_unusedcode_violation(self):
file_contents = load_testfile('unusedcode_violation.php')
self.section.append(Setting('phpmd_rulesets', 'unusedcode'))
self.check_results(
self.uut,
file_contents,
[Result.from_values('PHPMessDetectorBear',
'Avoid unused local variables such as \'$i\'.',
file=get_testfile_path(
'unusedcode_violation.php'),
line=5),
Result.from_values('PHPMessDetectorBear',
'Avoid unused private methods such '
'as \'foo\'.',
file=get_testfile_path(
'unusedcode_violation.php'),
line=9)],
filename=get_testfile_path('unusedcode_violation.php'))
Empty file.
13 changes: 13 additions & 0 deletions tests/php/phpmessdetector_test_files/cleancode_violation.php
@@ -0,0 +1,13 @@
<?
class Foo
{
public function bar($flag)
{
if ($flag) {
// one branch
} else {
// another branch
}
}
}
?>
39 changes: 39 additions & 0 deletions tests/php/phpmessdetector_test_files/codesize_violation.php
@@ -0,0 +1,39 @@
<?
// Cyclomatic Complexity = 11
class Foo {
public function example() {
if ($a == $b) {
if ($a1 == $b1) {
fiddle();
} elseif ($a2 == $b2) {
fiddle();
} else {
fiddle();
}
} elseif ($c == $d) {
while ($c == $d) {
fiddle();
}
} elseif ($e == $f) {
for ($n = 0; $n < $h; $n++) {
fiddle();
}
} else {
switch ($z) {
case 1:
fiddle();
break;
case 2:
fiddle();
break;
case 3:
fiddle();
break;
default:
fiddle();
break;
}
}
}
}
?>
16 changes: 16 additions & 0 deletions tests/php/phpmessdetector_test_files/design_violation.php
@@ -0,0 +1,16 @@
<?
class Foo {
public function bar($param) {
if ($param === 42) {
exit(23);
}
}
}
class Bar {
public function foo($param) {
if ($param === 42) {
eval('$param = 23;');
}
}
}
?>
11 changes: 11 additions & 0 deletions tests/php/phpmessdetector_test_files/naming_violation.php
@@ -0,0 +1,11 @@
<?
class Something {
private $q = 15; // VIOLATION - Field
public static function main( array $as ) { // VIOLATION - Formal
$r = 20 + $this->q; // VIOLATION - Local
for (int $i = 0; $i < 10; $i++) { // Not a Violation (inside FOR)
$r += $this->q;
}
}
}
?>
11 changes: 11 additions & 0 deletions tests/php/phpmessdetector_test_files/unusedcode_violation.php
@@ -0,0 +1,11 @@
<?
class Foo {
public function doSomething()
{
$i = 5; // Unused
}
}
class Something {
private function foo() {} // unused
}
?>