Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Checks/BaseCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* Apache 2.0 License
*/

use BambooHR\Guardrail\Metrics\Metric;
use BambooHR\Guardrail\Metrics\MetricOutputInterface;
use PhpParser\Node;
use BambooHR\Guardrail\Scope;
use BambooHR\Guardrail\SymbolTable\SymbolTable;
Expand Down
30 changes: 29 additions & 1 deletion src/Checks/MethodCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use BambooHR\Guardrail\Abstractions\FunctionLikeParameter;
use BambooHR\Guardrail\Abstractions\MethodInterface;
use BambooHR\Guardrail\Attributes;
use BambooHR\Guardrail\Metrics\Metric;
use BambooHR\Guardrail\Metrics\MetricOutputInterface;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Variable;
Expand All @@ -34,18 +36,31 @@ class MethodCall extends BaseCheck {
*/
private $callableCheck;

/** @var MetricOutputInterface */
protected $metricDoc;

/**
* MethodCall constructor.
*
* @param SymbolTable $symbolTable Instance of the SymbolTable
* @param OutputInterface $doc Instance of OutputInterface
*/
public function __construct(SymbolTable $symbolTable, OutputInterface $doc) {
public function __construct(SymbolTable $symbolTable, OutputInterface $doc, MetricOutputInterface $metricOutputInterface) {
$this->metricDoc = $metricOutputInterface;
parent::__construct($symbolTable, $doc);
$this->inferenceEngine = new TypeInferrer($symbolTable);
$this->callableCheck = new CallableCheck($symbolTable, $doc);
}

public function emitMetric($fileName, Node $node, $errorType, $metricData) {
$lineNumber= $node->getLine();
$metric = new Metric($fileName, $lineNumber, $errorType, $metricData);
if ($trait = $node->getAttribute('importedByTrait')) {
$metric->setCausedByTraitData($trait, $node->getAttribute('importedOnLine'));
}
$this->metricDoc->emitMetric($metric);
}

/**
* getCheckNodeTypes
*
Expand Down Expand Up @@ -156,12 +171,25 @@ protected function checkMethod($fileName, $node, $className, $methodName, Scope
if ($method->isDeprecated()) {
$errorType = $method->isInternal() ? ErrorConstants::TYPE_DEPRECATED_INTERNAL : ErrorConstants::TYPE_DEPRECATED_USER;
$this->emitError($fileName, $node, $errorType, "Call to deprecated function " . $method->getName());
$metricType = $method->isInternal() ? MetricConstants::TYPE_DEPRECATED_INTERNAL : MetricConstants::TYPE_DEPRECATED_USER;
$this->emitMetric($fileName, $node, $metricType, ['class' => $className, 'method' => $methodName]);
}

$name = $className . "->" . $methodName;
foreach ($node->args as $index => $arg) {
$this->checkParam($fileName, $node, $name, $scope, $inside, $arg, $index, $params);
}
$calledClassParts = explode('\\', $className);
if ($inside) {
$callingClassParts = $inside->namespacedName->parts;
for ($i = 0; $i < min(count($calledClassParts), count($callingClassParts)); $i++) {
if ($callingClassParts[$i] !== $callingClassParts[$i]) {
break;
}
}
$sharedPrefixParts = $i-1;
$this->emitMetric($fileName, $node, MetricConstants::TYPE_METHOD_CALL, ['callingClass' => implode('\\', $callingClassParts), 'calledClass' => $className, 'calledMethod' => $methodName, 'sharedNamespacePrefixParts' => $sharedPrefixParts]);
}
}

/**
Expand Down
28 changes: 28 additions & 0 deletions src/Checks/MetricConstants.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php namespace BambooHR\Guardrail\Checks;

/**
* Class ErrorConstants
*
* @package BambooHR\Guardrail\Checks
*/
class MetricConstants {


const TYPE_DEPRECATED_INTERNAL = 'Standard.Deprecated.Internal';
const TYPE_DEPRECATED_USER = 'Standard.Deprecated.User';
const TYPE_METHOD_CALL = 'Standard.Method.Call';

/**
* @return string[]
*/
static function getConstants() {
$ret = [];
$selfReflection = new \ReflectionClass(self::class);
$constants = $selfReflection->getConstants();
sort($constants);
foreach ($constants as $name => $value) {
$ret[] = $value;
}
return $ret;
}
}
28 changes: 15 additions & 13 deletions src/CommandLineRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,36 @@ public function usage() {
echo "
Usage: php guardrail.phar [-a] [-i] [-n #] [--format xunit|text] [-o output_file_name] [-p #/#] [--timings] config_file

where: -p #/# = Define the number of partitions and the current partition.
where: -p #/# = Define the number of partitions and the current partition.
Use for multiple hosts. Example: -p 1/4

--format {format} = Use \"xunit\" format or a more console friendly \"text\" format
--format {format} = Use \"xunit\" format or a more console friendly \"text\" format

-n # = number of child process to run.
-n # = number of child process to run.
Use for multiple processes on a single host.

-a = run the \"analyze\" operation
-a = run the \"analyze\" operation

-i = run the \"index\" operation.
-i = run the \"index\" operation.
Defaults to yes if using in memory index.

-s = prefer sqlite index
-s = prefer sqlite index

-j = prefer json index (new)
-j = prefer json index (new)

-m = prefer in memory index (only available when -n=1 and -p=1/1)
-m = prefer in memory index (only available when -n=1 and -p=1/1)

-o output_file_name = Output results to the specified filename
-o output_file_name = Output results to the specified filename

-v = Increase verbosity level. Can be used once or twice.
-v = Increase verbosity level. Can be used once or twice.

-h or --help = Ignore all other options and show this page.
-h or --help = Ignore all other options and show this page.

-l or --list = Ignore all other options and list standard test names.
-l or --list = Ignore all other options and list standard test names.

--timings = Output a summary of how long each check ran for.
--timings = Output a summary of how long each check ran for.

--metrics-file metrics.json = Output metrics to the specified filename
";
}

Expand Down
34 changes: 34 additions & 0 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class Config {
/** @var string */
private $filterFileName = "";

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

/** @var bool */
static private $useDocBlockForProperties = false;

Expand Down Expand Up @@ -152,6 +155,10 @@ public function __construct($argv) {
$this->emitList = $this->config['emit'];
}

if (isset($this->config['emitMetrics']) && is_array($this->config['emitMetrics'])) {
$this->emitMetricList = $this->config['emitMetrics'];
}

if ($this->processes > 1 && $this->preferredTable == self::MEMORY_SYMBOL_TABLE) {
$this->preferredTable = self::SQLITE_SYMBOL_TABLE;
}
Expand Down Expand Up @@ -352,6 +359,13 @@ private function parseArgv(array $argv) {
$filter->display();
$this->filter = $filter;
break;
case '--metrics-file':
if ($argCount + 1 >= count($argv)) {
echo "not enough arguments";
throw new InvalidConfigException();
}
$this->metricOutputFile = $argv[++$argCount];
break;
case '-h':
case '--help':
throw new InvalidConfigException;
Expand Down Expand Up @@ -512,6 +526,22 @@ public function getEmitList() {
return $this->emitList;
}

public function getMetricEmitList() {
return $this->emitMetricList;
/*
return [
[
'emit' => 'Standard.Method.Call',
'threshold' => [
'data.sharedNamespaceParts' => 3,
'operator' => '<'
]
],
'Standard.*'
];
*/
}

/**
* processCount
*
Expand All @@ -538,4 +568,8 @@ public function getOutputFile() {
return $this->outputFile;
}
}

public function getMetricOutputFile() {
return $this->metricOutputFile;
}
}
82 changes: 82 additions & 0 deletions src/Filters/EmitFilterApplier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php namespace BambooHR\Guardrail\Filters;

use Webmozart\Glob\Glob;

class EmitFilterApplier {
/**
* emitPatternMatches
*
* @param string $name The name
* @param string $pattern The pattern
*
* @return bool
*/
static public function emitPatternMatches($name, $pattern) {
if (substr($pattern, -2) == '.*') {
$start = substr($pattern, 0, -2);
return (strpos($name, $start) === 0);
} else {
return $name == $pattern;
}
}

/**
* shouldEmit
*
* @param string $fileName The file name
* @param string $name The name
* @param int $lineNumber The line number the error occurred on.
* @param array $emitList A list of things that should be emitted
* @param array $silenced A list of things to be silenced
* @param FilterInterface $filter A filter to be applied for emit entries limited to "new"
*
* @return bool
*/
static public function shouldEmit($fileName, $name, $lineNumber, $emitList, $silenced, ?FilterInterface $filter) {
if (isset($silenced[$name]) && $silenced[$name] > 0) {
return false;
}
$entry = static::findEmitEntry($emitList, $fileName, $name);
if ($entry === false) {
return $entry;
}
return static::applyEmitEntry($entry, $filter, $fileName, $name, $lineNumber);
}

static public function applyEmitEntry($entry, ?FilterInterface $filter, $fileName, $name, $lineNumber) {
if (
isset($entry['when']) &&
$entry['when'] == 'new' &&
(
!$filter ||
!$filter->shouldEmit($fileName, $name, $lineNumber)
)
) {
return false;
}
return true;
}

static public function findEmitEntry($emitList, $fileName, $name) {
foreach ($emitList as $entry) {
if (
is_array($entry)
) {
if (isset($entry['emit']) && !static::emitPatternMatches($name, $entry['emit'])) {
continue;
}
if (isset($entry['glob']) && !Glob::match( "/" . $fileName, "/" . $entry['glob'])) {
continue;
}
if (isset($entry['ignore']) && Glob::match("/" . $fileName, "/" . $entry['ignore'])) {
continue;
}
return $entry;

} else if (is_string($entry) && static::emitPatternMatches($name, $entry)) {
return $entry;
}
}
return false;
}
}
24 changes: 24 additions & 0 deletions src/Metrics/JsonMetricOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php namespace BambooHR\Guardrail\Metrics;

use BambooHR\Guardrail\Config;
use BambooHR\Guardrail\Exceptions\InvalidConfigException;

class JsonMetricOutput implements MetricOutputInterface {
/** @var resource */
private $fileHandle;
public function __construct(Config $config) {
if ($config->getMetricOutputFile() !== null) {
$this->fileHandle = fopen($config->getMetricOutputFile(), "w+");
if (!$this->fileHandle) {
throw new InvalidConfigException("Cannot write to the metric file: {$config->getMetricOutputFile()}");
}
$this->emitList = $config->getEmitList();
}
}

public function emitMetric(MetricInterface $metric) {
if ($this->fileHandle) {
fwrite($this->fileHandle, json_encode($metric) . "\n");
}
}
}
Loading