Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 26 additions & 21 deletions .github/workflows/code_coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,30 @@ jobs:

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@master
with:
php-version: 8.1
coverage: xdebug
- name: Load dependencies from cache
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.1-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php8.1-composer-
- uses: actions/checkout@v3
- uses: shivammathur/setup-php@master
with:
php-version: 8.3
coverage: xdebug
- name: Load dependencies from cache
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.3-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php8.3-composer-

- run: composer install --prefer-dist --no-progress --no-suggest
- run: php vendor/bin/phpunit --coverage-clover build/logs/clover.xml
- run: php vendor/bin/php-coveralls --verbose
env:
COVERALLS_RUN_LOCALLY: 1
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
- run: composer install --prefer-dist --no-progress --no-suggest
- run: php vendor/bin/phpunit --coverage-clover build/logs/clover.xml

- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: build/logs/clover.xml # optional
flags: unittests # optional
name: codecov-umbrella # optional
fail_ci_if_error: true # optional (default = false)
verbose: true # optional (default = false)
6 changes: 3 additions & 3 deletions .github/workflows/coding_standards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@master
with:
php-version: 8.1
php-version: 8.3
coverage: none
- name: Load dependencies from cache
id: composer-cache
Expand All @@ -22,9 +22,9 @@ jobs:
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.1-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-php8.3-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php8.1-composer-
${{ runner.os }}-php8.3-composer-

- run: composer validate --strict
- run: composer install --prefer-dist --no-progress --no-suggest
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/phpstan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@master
with:
php-version: 8.1
php-version: 8.3
coverage: none
- name: Load dependencies from cache
id: composer-cache
Expand All @@ -22,9 +22,9 @@ jobs:
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.1-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-php8.3-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php8.1-composer-
${{ runner.os }}-php8.3-composer-

- run: composer install --prefer-dist --no-progress --no-suggest
- run: composer run-script phpstan
27 changes: 27 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions

name: "Release"

on:
push:
branches:
- "master"

jobs:
tests:
name: "Create Release"

runs-on: "ubuntu-latest"

steps:
- name: "Checkout"
uses: "actions/checkout@v3"

- uses: "actions/setup-node@v3"
with:
node-version: 'lts/*'

- name: "Run semantic-release"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
8 changes: 4 additions & 4 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: ['7.4', '8.0', '8.1', '8.2']
php-versions: ['8.1', '8.2', '8.3']

name: Tests on PHP ${{ matrix.php-versions }}
steps:
Expand Down Expand Up @@ -41,7 +41,7 @@ jobs:
- uses: actions/checkout@master
- uses: shivammathur/setup-php@v2
with:
php-version: 7.4
php-version: 8.1
coverage: none
- name: Load dependencies from cache
id: composer-cache
Expand All @@ -50,9 +50,9 @@ jobs:
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php7.4-lowest-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-php8.1-lowest-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php7.4-lowest-composer-
${{ runner.os }}-php8.1-lowest-composer-
- run: composer update --no-progress --no-suggest --prefer-lowest
- run: php vendor/bin/phpunit

2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/.phpcs-cache
/.phpunit.result.cache
/.phpunit.cache
/build
/composer.lock
/vendor/
11 changes: 11 additions & 0 deletions .releaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
],
"branches": [
"master"
],
tagFormat: '${version}',
}
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
],
"minimum-stability": "stable",
"require": {
"php": "^7.4|^8.0",
"php": "^8.1",
"nette/utils": "^3.0",
"nikic/php-parser": "^4.3",
"nikic/php-parser": "^4.3|^5.0",
"phpstan/phpstan": "^1.0"
},
"require-dev": {
"brainbits/phpcs-standard": "^4.0",
"php-coveralls/php-coveralls": "^2.0",
"phpstan/phpstan-php-parser": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5.2 || ^9.0.0"
"phpunit/phpunit": "^10.5"
},
"scripts": {
"check-all": [
Expand Down
12 changes: 6 additions & 6 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="./vendor/autoload.php" colors="true" verbose="true">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" bootstrap="./vendor/autoload.php" colors="true" cacheDirectory=".phpunit.cache">
<testsuites>
<testsuite name="main">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
</phpunit>
4 changes: 2 additions & 2 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ parametersSchema:

services:
-
class: BrainbitsPhpStan\CoversAnnotationRule
class: BrainbitsPhpStan\CoversClassPresentRule
arguments:
unitTestNamespaceContainsString: %brainbits.unitTestNamespaceContainsString%
tags:
- phpstan.rules.rule


-
class: BrainbitsPhpStan\CoversExistsRule
class: BrainbitsPhpStan\CoversClassExistsRule
tags:
- phpstan.rules.rule

158 changes: 158 additions & 0 deletions src/CoversClassExistsRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

declare(strict_types=1);

namespace BrainbitsPhpStan;

use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\Broker;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;

use function array_merge;
use function assert;
use function preg_match;
use function preg_split;
use function sha1;
use function sprintf;

// phpcs:disable SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint

/**
* @implements Rule<Class_>
*/
final class CoversClassExistsRule implements Rule
{
/** @var Broker */
private $broker;
/** @var bool[] */
private $alreadyParsedDocComments = [];

public function __construct(Broker $broker)
{
$this->broker = $broker;
}

public function getNodeType(): string
{
return Class_::class;
}

/**
* @param Class_ $node
*
* @return RuleError[] errors
*/
public function processNode(Node $node, Scope $scope): array
{
$messagesAttribute = $this->processNodeAttribute($node, $scope);
$messagesAnnotation = $this->processNodeAnnotation($node, $scope);

return array_merge($messagesAttribute, $messagesAnnotation);
}

/**
* @return RuleError[] errors
*/
public function processNodeAttribute(Class_ $node, Scope $scope): array
{
$messages = [];
if (!$node->attrGroups) {
return [];
}

foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $name => $attr) {
if ((string) $attr->name !== 'PHPUnit\Framework\Attributes\CoversClass') {
continue;
}

if (!$attr->args) {
continue;
}

$arg = $attr->args[0];

if ($arg->value instanceof ClassConstFetch) {
assert($arg->value->class instanceof Name);

$className = (string) $arg->value->class;
if ($this->broker->hasClass($className)) {
continue;
}

$messages[] = RuleErrorBuilder::message(sprintf('Class %s does not exist.', $className))->line($attr->getStartLine())->build();

continue;
}

if ($arg->value instanceof String_) {
$className = (string) $arg->value->value;
if ($this->broker->hasClass($className)) {
continue;
}

$messages[] = RuleErrorBuilder::message(sprintf('Class %s does not exist.', $className))->line($attr->getStartLine())->build();

continue;
}
}
}

return $messages;
}

/**
* @return RuleError[] errors
*/
public function processNodeAnnotation(Class_ $node, Scope $scope): array
{
$messages = [];
$docComment = $node->getDocComment();
if (empty($docComment)) {
return $messages;
}

$hash = sha1(sprintf(
'%s:%s:%s:%s',
$scope->getFile(),
$docComment->getStartLine(),
$docComment->getStartFilePos(),
$docComment->getText()
));
if (isset($this->alreadyParsedDocComments[$hash])) {
return $messages;
}

$this->alreadyParsedDocComments[$hash] = true;

$lines = preg_split('/\R/u', $docComment->getText());
if ($lines === false) {
return $messages;
}

foreach ($lines as $lineNumber => $lineContent) {
$matches = [];

if (! preg_match('/^(?:\s*\*\s*@(?:covers|coversDefaultClass)\h+)\\\\?(?<className>\w[^:\s]*)(?:::\S+)?\s*$/u', $lineContent, $matches)) {
if (! preg_match('/^(?:\s*\/\*\*\s*@(?:covers|coversDefaultClass)\h+)\\\\?(?<className>\w[^:\s]*)(?:::\S+)?\s*\*\/\s*$/u', $lineContent, $matches)) {
continue;
}
}

if ($this->broker->hasClass($matches['className'])) {
continue;
}

$messages[] = RuleErrorBuilder::message(sprintf('Class %s does not exist.', $matches['className']))->line($docComment->getStartLine() + $lineNumber)->build();
}

return $messages;
}
}
Loading