Skip to content

Commit

Permalink
initial commit to solve feature #186 (SARIF output)
Browse files Browse the repository at this point in the history
  • Loading branch information
llaville committed Feb 22, 2023
1 parent 9002c8f commit 0dc3ed5
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 4 deletions.
11 changes: 9 additions & 2 deletions bin/phplint
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,17 @@ if (true === $input->hasParameterOption(['--no-progress'], true)) {
}
}

$extensions[] = new OutputFormat([
$formats = [
OptionDefinition::LOG_JSON,
OptionDefinition::LOG_JUNIT,
]);
];

if (\class_exists('\Bartlett\Sarif\SarifLog')) {
// only when Composer development dependencies were installed too!
$formats[] = OptionDefinition::LOG_SARIF;
}

$extensions[] = new OutputFormat($formats);

$dispatcher = new EventDispatcher($extensions);

Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@
"symfony/yaml": "^5.4 || ^6.0"
},
"require-dev": {
"php-parallel-lint/php-console-highlighter": "^1.0",
"bamarni/composer-bin-plugin": "^1.4",
"bartlett/sarif-php-sdk": "^1.0",
"brainmaestro/composer-git-hooks": "^2.8.5 || 3.0.0-alpha.1",
"jetbrains/phpstorm-stubs": "^2021.3 || ^2022.3",
"bamarni/composer-bin-plugin": "^1.4"
"php-parallel-lint/php-console-highlighter": "^1.0"
},
"autoload": {
"psr-4": {
Expand Down
6 changes: 6 additions & 0 deletions src/Command/ConfigureCommandTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ protected function configureCommand(Command $command): void
InputOption::VALUE_OPTIONAL,
'Log scan results in JUnit XML format to file (<comment>default: ' . OptionDefinition::DEFAULT_STANDARD_OUTPUT_LABEL . '</comment>)'
)
->addOption(
'log-sarif',
null,
InputOption::VALUE_OPTIONAL,
'Log scan results in SARIF format to file (<comment>default: ' . OptionDefinition::DEFAULT_STANDARD_OUTPUT_LABEL . '</comment>)'
)
->addOption(
'warning',
'w',
Expand Down
2 changes: 2 additions & 0 deletions src/Configuration/AbstractOptionsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public function __construct(InputInterface $input, array $configuration = [])
OptionDefinition::NO_PROGRESS => false,
OptionDefinition::LOG_JSON => null,
OptionDefinition::LOG_JUNIT => null,
OptionDefinition::LOG_SARIF => false,
OptionDefinition::WARNING => false,
OptionDefinition::OPTION_MEMORY_LIMIT => ini_get('memory_limit'),
OptionDefinition::IGNORE_EXIT_CODE => false,
Expand Down Expand Up @@ -96,6 +97,7 @@ public function __construct(InputInterface $input, array $configuration = [])
OptionDefinition::PROGRESS,
OptionDefinition::LOG_JSON,
OptionDefinition::LOG_JUNIT,
OptionDefinition::LOG_SARIF,
OptionDefinition::WARNING,
OptionDefinition::OPTION_MEMORY_LIMIT,
OptionDefinition::IGNORE_EXIT_CODE,
Expand Down
1 change: 1 addition & 0 deletions src/Configuration/OptionDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface OptionDefinition
public const NO_PROGRESS = 'no-progress';
public const LOG_JSON = 'log-json';
public const LOG_JUNIT = 'log-junit';
public const LOG_SARIF = 'log-sarif';
public const IGNORE_EXIT_CODE = 'ignore-exit-code';

public const DEFAULT_JOBS = 5;
Expand Down
2 changes: 2 additions & 0 deletions src/Configuration/OptionsFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ protected function configureOptions(OptionsResolver $resolver): void
OptionDefinition::NO_PROGRESS => 'bool',
OptionDefinition::LOG_JSON => ['bool', 'null', 'string'],
OptionDefinition::LOG_JUNIT => ['bool', 'null', 'string'],
OptionDefinition::LOG_SARIF => ['bool', 'null', 'string'],
OptionDefinition::WARNING => 'bool',
OptionDefinition::OPTION_MEMORY_LIMIT => ['int', 'string'],
OptionDefinition::IGNORE_EXIT_CODE => 'bool',
Expand Down Expand Up @@ -89,5 +90,6 @@ protected function configureOptions(OptionsResolver $resolver): void
};
$resolver->setNormalizer(OptionDefinition::LOG_JSON, $outputFormat);
$resolver->setNormalizer(OptionDefinition::LOG_JUNIT, $outputFormat);
$resolver->setNormalizer(OptionDefinition::LOG_SARIF, $outputFormat);
}
}
3 changes: 3 additions & 0 deletions src/Extension/OutputFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Overtrue\PHPLint\Output\ConsoleOutput;
use Overtrue\PHPLint\Output\JsonOutput;
use Overtrue\PHPLint\Output\JunitOutput;
use Overtrue\PHPLint\Output\SarifOutput;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
Expand Down Expand Up @@ -70,6 +71,8 @@ public function initFormat(ConsoleCommandEvent $event): void
$this->handlers[] = new JsonOutput(fopen($filename, 'w'));
} elseif (OptionDefinition::LOG_JUNIT == $name) {
$this->handlers[] = new JunitOutput(fopen($filename, 'w'));
} elseif (OptionDefinition::LOG_SARIF == $name) {
$this->handlers[] = new SarifOutput(fopen($filename, 'w'));
}
}
}
Expand Down
131 changes: 131 additions & 0 deletions src/Output/SarifOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

/*
* This file is part of the overtrue/phplint package
*
* (c) overtrue
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Overtrue\PHPLint\Output;

use Bartlett\Sarif\Definition\ArtifactLocation;
use Bartlett\Sarif\Definition\Location;
use Bartlett\Sarif\Definition\Message;
use Bartlett\Sarif\Definition\PhysicalLocation;
use Bartlett\Sarif\Definition\Region;
use Bartlett\Sarif\Definition\Result;
use Bartlett\Sarif\Definition\Run;
use Bartlett\Sarif\Definition\Tool;
use Bartlett\Sarif\Definition\ToolComponent;
use Bartlett\Sarif\SarifLog;
use Symfony\Component\Console\Output\StreamOutput;

use function fclose;
use function getcwd;
use function json_encode;
use function parse_url;
use function str_replace;
use function strlen;
use function substr;
use const DIRECTORY_SEPARATOR;
use const JSON_PRETTY_PRINT;
use const JSON_UNESCAPED_SLASHES;
use const PHP_URL_SCHEME;

/**
* @author Laurent Laville
* @since Release 9.1.0
*/
class SarifOutput extends StreamOutput implements OutputInterface
{
public function format(LinterOutput $results): void
{
$failures = $results->getFailures();

$driver = new ToolComponent('PHPLint');
$driver->setInformationUri('https://github.com/overtrue/phplint');
$driver->setVersion('9.1.0');

$tool = new Tool($driver);

$results = [];

foreach ($failures as $file => $failure) {
$result = new Result(new Message($failure['error']));

$artifactLocation = new ArtifactLocation();
$artifactLocation->setUri($this->pathToArtifactLocation($file));
$artifactLocation->setUriBaseId('WORKINGDIR');

$location = new Location();
$physicalLocation = new PhysicalLocation($artifactLocation);
$physicalLocation->setRegion(new Region($failure['line']));
$location->setPhysicalLocation($physicalLocation);
$result->addLocations([$location]);

$results[] = $result;
}

$run = new Run($tool);
$workingDir = new ArtifactLocation();
$workingDir->setUri($this->pathToUri(getcwd() . '/'));
$originalUriBaseIds = [
'WORKINGDIR' => $workingDir,
];
$run->addAdditionalProperties($originalUriBaseIds);
$run->addResults($results);

$log = new SarifLog([$run]);

$jsonString = json_encode(
$log,
JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT
);

$this->write($jsonString, true);
fclose($this->getStream());
}

/**
* Returns path to resource (file) scanned.
*/
protected function pathToArtifactLocation(string $path): string
{
$workingDir = getcwd();
if ($workingDir === false) {
$workingDir = '.';
}
if (substr($path, 0, strlen($workingDir)) === $workingDir) {
// relative path
return substr($path, strlen($workingDir) + 1);
}

// absolute path with protocol
return $this->pathToUri($path);
}

/**
* Returns path to resource (file) scanned with protocol.
*/
protected function pathToUri(string $path): string
{
if (parse_url($path, PHP_URL_SCHEME) !== null) {
// already a URL
return $path;
}

$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);

// file:///C:/... on Windows systems
if (substr($path, 0, 1) !== '/') {
$path = '/' . $path;
}

return 'file://' . $path;
}
}

0 comments on commit 0dc3ed5

Please sign in to comment.