Skip to content

Commit ebf8695

Browse files
committed
Initial commit
0 parents  commit ebf8695

30 files changed

+1130
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
bin/typhp.phar
2+
vendor
3+
composer.lock

.travis.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
language: php
2+
3+
php:
4+
- 7.1
5+
- 7.2
6+
- 7.3
7+
- 7.4snapshot
8+
9+
stages:
10+
- test
11+
- typhp
12+
13+
env:
14+
matrix:
15+
- DEPS="high"
16+
- DEPS="low"
17+
global:
18+
- COMPOSER_MEMORY_LIMIT=-1
19+
- DEFAULT_COMPOSER_FLAGS="--no-interaction --no-suggest --prefer-source"
20+
21+
cache:
22+
directories:
23+
- ./vendor
24+
25+
before_install:
26+
- export INI_DIR=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d
27+
- echo memory_limit = 1G >> $INI_DIR/travis.ini
28+
- if [[ -f $INI_DIR/xdebug.ini ]]; then phpenv config-rm xdebug.ini; fi
29+
- composer clear-cache
30+
31+
install:
32+
- if [[ "$DEPS" = 'high' ]]; then travis_retry composer $DEFAULT_COMPOSER_FLAGS update; fi
33+
- if [[ "$DEPS" = 'low' ]]; then travis_retry composer $DEFAULT_COMPOSER_FLAGS --prefer-lowest --prefer-stable update; fi
34+
35+
script:
36+
- vendor/bin/phpunit
37+
38+
matrix:
39+
fast_finish: true
40+
allow_failures:
41+
- php: 7.4snapshot
42+
43+
jobs:
44+
include:
45+
- stage: typhp
46+
php: 7.3
47+
script:
48+
- bin/typhp

.typhp.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
project:
2+
directories:
3+
- 'src'
4+
- 'tests'
5+
exclude:
6+
directories:
7+
- 'Functional/Scenarios/'

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
## TYPHP
2+
3+
`typhp` is a simple tool checks whether type hint for arguments or return type declared.
4+
Unlike static analysis tools, it doesn't point out possible errors and issues
5+
but suggests typehint everything possible.
6+
7+
#### For whom?
8+
- Who works projects using PHP 7.1 and higher.
9+
- Who doesn't want to point out missing type hint and return type declarations in code review process
10+
by using it as part of CI pipeline.
11+
- Who love strict typing
12+
13+
#### Features
14+
15+
- Respects phpdoc; there are some rare cases mixed or compound types are needed.
16+
If such cases documented in phpdoc, `typhp` doesn't complain. For example: `@return array|bool`, `@param mixed $foo`, etc.
17+
- Analyses based on configuration. Include/exclude files and directories to be analysed.
18+
For optional config file, see the [current project example](https://github.com/seferov/typhp/blob/master/.typhp.yml)
19+
- Does NOT modifies your code
20+
21+
## Installation
22+
23+
Install via composer:
24+
25+
`
26+
composer require seferov/typhp
27+
`
28+
29+
To install globally
30+
31+
`
32+
composer global require seferov/typhp
33+
`
34+
35+
## Usage
36+
37+
`
38+
vendor/bin/typhp analyse path
39+
`
40+
41+
If config file (`.typhp.yml`) is present in project root, it can be run
42+
just by `vendor/bin/typhp`
43+
44+
Example output
45+
46+
<img src="example.png" />
47+
48+
## Todo
49+
50+
- [ ] Analyse closures
51+
52+
- [ ] Check by PHP version. For example, don't suppress `@param object` for >= PHP 7.2
53+
54+
- [ ] Better configuration
55+
56+
- [ ] Check `declare(strict_types=1)` by config
57+
58+
- [ ] Phar file
59+
60+
- [ ] Github Actions

bin/typhp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
declare(strict_types=1);
5+
6+
use Seferov\Typhp\Command\AnalyseCommand;
7+
use Symfony\Component\Console\Application;
8+
9+
require_once __DIR__.'/../vendor/autoload.php';
10+
11+
$application = new Application('Typed PHP');
12+
$analyseCommand = new AnalyseCommand();
13+
$application->add($analyseCommand);
14+
$application->setDefaultCommand($analyseCommand->getName());
15+
$application->run();

composer.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "seferov/typhp",
3+
"description": "Enforce type declaring",
4+
"license": "MIT",
5+
"type": "library",
6+
"keywords": ["type hint", "strict typing"],
7+
"require": {
8+
"php": "^7.1",
9+
"nikic/php-parser": "^4.2",
10+
"phpdocumentor/reflection-docblock": "^4.3",
11+
"symfony/console": "^4.3",
12+
"symfony/finder": "^4.3",
13+
"symfony/yaml": "^4.3",
14+
"symfony/stopwatch": "^4.3",
15+
"symfony/process": "^4.3",
16+
"jean85/pretty-package-versions": "^1.2"
17+
},
18+
"require-dev": {
19+
"phpunit/phpunit": "^7.5",
20+
"symfony/var-dumper": "^4.3"
21+
},
22+
"autoload": {
23+
"psr-4": {
24+
"Seferov\\Typhp\\": "src"
25+
}
26+
},
27+
"autoload-dev": {
28+
"psr-4": {
29+
"Seferov\\Typhp\\Tests\\": "tests"
30+
}
31+
},
32+
"bin": ["bin/typhp"],
33+
"authors": [
34+
{
35+
"name": "Farhad Safarov",
36+
"email": "[email protected]"
37+
}
38+
]
39+
}

example.png

233 KB
Loading

phpunit.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.5/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
forceCoversAnnotation="true"
6+
beStrictAboutCoversAnnotation="true"
7+
beStrictAboutOutputDuringTests="true"
8+
beStrictAboutTodoAnnotatedTests="true"
9+
verbose="true">
10+
<testsuites>
11+
<testsuite name="default">
12+
<directory suffix="Test.php">tests</directory>
13+
</testsuite>
14+
</testsuites>
15+
16+
<filter>
17+
<whitelist processUncoveredFilesFromWhitelist="true">
18+
<directory suffix=".php">src</directory>
19+
</whitelist>
20+
</filter>
21+
</phpunit>

src/Analyser.php

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
namespace Seferov\Typhp;
4+
5+
use phpDocumentor\Reflection\DocBlockFactory;
6+
use PhpParser\Node;
7+
use PhpParser\ParserFactory;
8+
use Seferov\Typhp\Issue\UntypedArgumentIssue;
9+
use Seferov\Typhp\Issue\UntypedReturnIssue;
10+
11+
class Analyser
12+
{
13+
/**
14+
* @var string
15+
*/
16+
private $code;
17+
/**
18+
* @var string
19+
*/
20+
private $fileName;
21+
/**
22+
* @var IssueCollection
23+
*/
24+
private $issueCollection;
25+
/**
26+
* @var DocBlockFactory
27+
*/
28+
private $docBlockFactory;
29+
/**
30+
* @var DocBlockAnalyser
31+
*/
32+
private $docBlockAnalyser;
33+
34+
public function __construct(string $fileName, string $code)
35+
{
36+
$this->code = $code;
37+
$this->fileName = $fileName;
38+
$this->issueCollection = new IssueCollection();
39+
$this->docBlockFactory = DocBlockFactory::createInstance();
40+
$this->docBlockAnalyser = new DocBlockAnalyser();
41+
}
42+
43+
public function analyse(): IssueCollection
44+
{
45+
$this->issueCollection->empty();
46+
47+
$parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7);
48+
$ast = $parser->parse($this->code);
49+
50+
foreach ($ast as $node) {
51+
$this->analyseNode($node);
52+
}
53+
54+
return $this->issueCollection;
55+
}
56+
57+
private function analyseNode(Node $node): void
58+
{
59+
if ($node instanceof Node\FunctionLike) {
60+
$this->analyseFunctionLike($node);
61+
}
62+
63+
if (isset($node->stmts)) {
64+
foreach ($node->stmts as $subNode) {
65+
$this->analyseNode($subNode);
66+
}
67+
}
68+
}
69+
70+
private function analyseFunctionLike(Node\FunctionLike $functionLike): void
71+
{
72+
$name = $functionLike->name;
73+
74+
$docBlock = null;
75+
if ($functionLike->getDocComment()) {
76+
try {
77+
$docBlock = $this->docBlockFactory->create($functionLike->getDocComment()->getText());
78+
} catch (\Exception $e) {
79+
}
80+
}
81+
82+
if ($docBlock && $this->docBlockAnalyser->isSuppressedByInheritDoc($docBlock)) {
83+
return;
84+
}
85+
86+
foreach ($functionLike->getParams() as $param) {
87+
if (null === $param->type) {
88+
if ($docBlock && $this->docBlockAnalyser->isParamSuppressedByDocBlock($param->var->name, $docBlock)) {
89+
continue;
90+
}
91+
92+
$this->issueCollection->add(UntypedArgumentIssue::create($name->name, $name->getStartLine(), $param->var->name));
93+
}
94+
}
95+
96+
if (null === $functionLike->getReturnType() && '__construct' !== $name->name) {
97+
if ($docBlock && $this->docBlockAnalyser->isReturnSuppressedByDocBlock($docBlock)) {
98+
return;
99+
}
100+
101+
$this->issueCollection->add(UntypedReturnIssue::create($name->name, $name->getStartLine()));
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)