Skip to content

Commit

Permalink
Tag version 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-granados committed Feb 9, 2024
1 parent b95fcd1 commit a1e3eb9
Show file tree
Hide file tree
Showing 33 changed files with 1,047 additions and 2 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/all_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: "All Tests"

on:
pull_request:
push:

jobs:
test:
name: "Run all checks for all supported PHP versions"

runs-on: "ubuntu-22.04"

strategy:
fail-fast: false
matrix:
php-version:
- "8.0"
- "8.1"
- "8.2"
- "8.3"

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

- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
tools: composer

- name: Get composer cache directory
id: composercache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composercache.outputs.dir }}
key: "php-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}"
restore-keys: "php-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}"

- name: "Install composer dependencies"
run: "composer install --no-interaction --no-progress"

- name: "Run tests"
run: "composer tests"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/vendor/
composer.lock
.idea
.phpunit.cache
109 changes: 107 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,107 @@
# phpstan-extension
PHPStan extension to read static analysis attributes
# PHP Static Analysis Attributes PHPStan Extension
[![Continuous Integration](https://github.com/php-static-analysis/phpstan-extension/workflows/All%20Tests/badge.svg)](https://github.com/php-static-analysis/phpstan-extension/actions)
[![Latest Stable Version](https://poser.pugx.org/php-static-analysis/phpstan-extension/v/stable)](https://packagist.org/packages/php-static-analysis/phpstan-extension)
[![PHP Version Require](http://poser.pugx.org/php-static-analysis/phpstan-extension/require/php)](https://packagist.org/packages/php-static-analysis/phpstan-extension)
[![License](https://poser.pugx.org/php-static-analysis/phpstan-extension/license)](https://github.com/php-static-analysis/phpstan-extension/blob/main/LICENSE)
[![Total Downloads](https://poser.pugx.org/php-static-analysis/phpstan-extension/downloads)](https://packagist.org/packages/php-static-analysis/phpstan-extension/stats)

Since the release of PHP 8.0 more and more libraries, frameworks and tools have been updated to use attributes instead of annotations in PHPDocs.

However, static analysis tools like PHPStan have not made this transition to attributes and they still rely on annotations in PHPDocs for a lot of their functionality.

This is a PHPStan extension that allows PHPStan to understand a new set of attributes that replace the PHPDoc annotations. These attributes are defined in [this repository](https://github.com/php-static-analysis/attributes)

## Example

In order to show how code would look with these attributes, we can look at the following example. This is how a class looks like with the current annotations:

```php
<?php

class ArrayAdder
{
/** @var array<string> */
private array $result;

/**
* @param array<string> $array1
* @param array<string> $array2
* @return array<string>
*/
public function addArrays(array $array1, array $array2): array
{
$this->result = $array1 + $array2;
return $this->result;
}
}
```

And this is how it would look like using the new attributes:

```php
<?php

use PhpStaticAnalysis\Attributes\Type;
use PhpStaticAnalysis\Attributes\Param;
use PhpStaticAnalysis\Attributes\Returns;

class ArrayAdder
{
#[Type('array<string>')]
private array $result;

#[Param(array1: 'array<string>')]
#[Param(array2: 'array<string>')]
#[Returns('array<string>')]
public function addArrays(array $array1, array $array2): array
{
$this->array = $array1 + $array2;
return $this->array;
}
}
```

## Installation

First of all, to make the attributes available for your codebase use:

```
composer require php-static-analysis/attributes
```

To use this extension, require it in Composer:

```
composer require --dev php-static-analysis/phpstan-extension
```

If you also install [phpstan/extension-installer](https://github.com/phpstan/extension-installer) then you're all set!

<details>
<summary>Manual installation</summary>

If you don't want to use `phpstan/extension-installer`, include `extension.neon` in your project's PHPStan config:

```
includes:
- vendor/php-static-analysis/phpstan-extension/extension.neon
```
</details>

## Using the extension

This extension works by interacting with the parser that PHPStan uses to parse the code and replacing the new Attributes with PHPDoc annotations that PHPStan can understand. The functionality provided by the attribute is exactly the same as the one provided by the corresponding PHPDoc annotation.

These are the available attributes and their corresponding PHPDoc annotations:

| Attribute | PHPDoc Annotation |
|---------------------------------------------------------------------------------------------|-------------------|
| [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |
| [Param](https://github.com/php-static-analysis/attributes/blob/main/doc/Param.md) | `@param` |
| [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
| [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` |




61 changes: 61 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"name": "php-static-analysis/phpstan-extension",
"description": "PHPStan extension to read static analysis attributes",
"type": "phpstan-extension",
"keywords": ["dev", "static analysis"],
"license": "MIT",
"autoload": {
"psr-4": {
"PhpStaticAnalysis\\PHPStanExtension\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"test\\PhpStaticAnalysis\\PHPStanExtension\\": "tests/"
}
},
"authors": [
{
"name": "Carlos Granados",
"email": "[email protected]"
}
],
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=8.0",
"php-static-analysis/attributes": "^0.1 || dev-main",
"php-static-analysis/node-visitor": "^0.1 || dev-main",
"phpstan/phpstan": "^1.8"
},
"require-dev": {
"php-static-analysis/psalm-plugin": "dev-main",
"phpunit/phpunit": "^9.0",
"symplify/easy-coding-standard": "^12.1",
"vimeo/psalm": "^5"
},
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
}
},
"config": {
"sort-packages": true
},
"scripts": {
"phpstan": "phpstan analyse",
"phpstan-debug": "phpstan analyse --xdebug --debug",
"ecs": "ecs",
"ecs-fix": "ecs --fix",
"phpunit": "phpunit",
"psalm": "psalm",
"tests": [
"@ecs",
"@phpstan",
"@phpunit",
"@psalm"
]
}
}
15 changes: 15 additions & 0 deletions ecs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

use PhpCsFixer\Fixer\Import\NoUnusedImportsFixer;
use Symplify\EasyCodingStandard\Config\ECSConfig;

return ECSConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->withPreparedSets(
psr12: true,
);
13 changes: 13 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
services:
attributeParser:
class: PhpStaticAnalysis\PHPStanExtension\Parser\AttributeParser
arguments:
parser: @pathRoutingParser
autowired: false

defaultAnalysisParser:
class: PHPStan\Parser\CachedParser
arguments:
originalParser: @attributeParser
cachedNodesByStringCountMax: %cache.nodesByStringCountMax%
autowired: false
18 changes: 18 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
includes:
- extension.neon
- phar://phpstan.phar/conf/bleedingEdge.neon

parameters:
level: max
paths:
- src
- tests
excludePaths:
- tests/data/*
ignoreErrors:
-
message: '#^Accessing [a-zA-Z\\]+::class is not covered by backward compatibility promise. The class might change in a minor PHPStan version.$#'
path: tests/BaseAttributeTestCase.php
-
message: '#^Calling [a-zA-Z\\]+::[a-zA-Z]+\(\) is not covered by backward compatibility promise. The method might change in a minor PHPStan version.$#'
path: tests/BaseAttributeTestCase.php
19 changes: 19 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheResultFile=".phpunit.cache/test-results"
executionOrder="depends,defects"
beStrictAboutCoversAnnotation="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
convertDeprecationsToExceptions="true"
failOnRisky="true"
failOnWarning="true"
verbose="true">
<testsuites>
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
23 changes: 23 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" ?>
<psalm
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"

errorLevel="1"
findUnusedBaselineEntry="true"
findUnusedCode="false"
>
<projectFiles>
<directory name="src" />
<directory name="tests" />
<ignoreFiles>
<directory name="vendor" />
<directory name="tests/data" />
</ignoreFiles>
</projectFiles>

<plugins>
<pluginClass class="PhpStaticAnalysis\PsalmPlugin\Plugin" />
</plugins>
</psalm>
45 changes: 45 additions & 0 deletions src/Parser/AttributeParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace PhpStaticAnalysis\PHPStanExtension\Parser;

use PhpParser\Node\Stmt;
use PhpParser\NodeTraverser;
use PHPStan\Parser\Parser;
use PhpStaticAnalysis\Attributes\Param;
use PhpStaticAnalysis\Attributes\Returns;
use PhpStaticAnalysis\NodeVisitor\AttributeNodeVisitor;

class AttributeParser implements Parser
{
public function __construct(
private Parser $parser
) {
}

public function parseFile(string $file): array
{
$ast = $this->parser->parseFile($file);
return $this->traverseAst($ast);
}

public function parseString(string $sourceCode): array
{
$ast = $this->parser->parseString($sourceCode);
return $this->traverseAst($ast);
}

#[Param(ast: 'Stmt[]')]
#[Returns('Stmt[]')]
private function traverseAst(array $ast): array
{
$traverser = new NodeTraverser();
$nodeVisitor = new AttributeNodeVisitor();
$traverser->addVisitor($nodeVisitor);

$ast = $traverser->traverse($ast);
/** @var Stmt[] $ast */
return $ast;
}
}
Loading

0 comments on commit a1e3eb9

Please sign in to comment.