Skip to content

Commit

Permalink
feat: create composer script for phpcbf
Browse files Browse the repository at this point in the history
Add instructions in the README.md file
  • Loading branch information
nelson6e65 committed Mar 14, 2021
1 parent 5b2e005 commit 9b1cf4b
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 1 deletion.
Binary file added .github/screenshots/output1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 80 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,83 @@
# PHP Code Sniffer Helpers

[![License](https://img.shields.io/github/license/nelson6e65/php_nml.svg)](LICENSE)
[![License](https://img.shields.io/github/license/nelson6e65/php-code-sniffer-helpers.svg)](LICENSE)
[![time tracker](https://wakatime.com/badge/github/nelson6e65/php-code-sniffer-helpers.svg)](https://wakatime.com/badge/github/nelson6e65/php-code-sniffer-helpers)

Helpers for PHP Code Sniffer.

## Installation

```sh
composer require --dev nelson6e65/code-sniffer-helpers
```

## Features

### Composer scripts

#### `phpcbf` for lint-staged

A wrapper to fix your staged code (or argumented files/folders) using the **PHP Code Sniffer** auto-fixer.

There is a bug that does not allows you to use it directly as autofixer (https://github.com/squizlabs/PHP_CodeSniffer/issues/1818). There is [a workarround for using it as a composer script](https://github.com/squizlabs/PHP_CodeSniffer/issues/1818#issuecomment-735620637), but does not works for using it in a [lint-staged](https://github.com/okonet/lint-staged) pre-commit hook.

This helper is designed to be run with lint-staged, but you can also use it directly in your composer script.

##### Setup with lint-staged

Add the script to your composer.json:

```json
{
"scripts": {
"cs:fix-filtered": ["NelsonMartell\\PhpCodeSniffer\\ComposerScripts::phpcbf"]
}
}
```

> I used `"cs:fix-filtered"` name, but you can use any script name you like.
Configure your Husky + lint-staged in your package.json

```json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.php": "composer cs:fix-filtered"
}
}
```

> Example for Husky 4. Adapt it if you use Husky 5.
##### Usage

You can also run it directly with composer by using `composer cs:fix-filtered {PATHS}`. Example:

```sh
composer cs:fix-filtered src/ tests/ config/my-config-file.php
```

> Note: Non exixtent files/directories are ignored.
##### Output

The output is inspired on [pretty-quick](https://github.com/azz/pretty-quick) output:

```sh
composer cs:fix-filtered config/ src/Example.php src/non-existent-file.php
```

![output1](.github/screenshots/output1.png)

## License

[![License](https://img.shields.io/github/license/nelson6e65/php-code-sniffer-helpers.svg)](LICENSE)

Copyright (c) 2021 Nelson Martell

Read the [`LICENSE` file](LICENSE) for details.
8 changes: 8 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@
}
],
"scripts": {
"cs:fix-filtered": [
"NelsonMartell\\PhpCodeSniffer\\ComposerScripts::phpcbf"
],
"cs:php": [
"phpcs src/ -q --standard=Generic --sniffs=Generic.PHP.Syntax --colors",
"phpstan analyze"
]
},
"autoload": {
"psr-4": {
"NelsonMartell\\PhpCodeSniffer\\": "src"
}
},
"require": {
"php": ">=7.2"
},
Expand Down
127 changes: 127 additions & 0 deletions src/ComposerScripts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

declare(strict_types=1);

namespace NelsonMartell\PhpCodeSniffer;

use Composer\IO\IOInterface;
use Composer\Script\Event;

/**
* Composer scripts helpers.
*/
class ComposerScripts
{
/**
* @var string|null
*/
protected static $binDir;

/**
* @var string|null
*/
protected static $vendorDir;

protected static function getBinDir(Event $event)
{
if (!static::$binDir) {
static::$binDir = realpath($event->getComposer()->getConfig()->get('bin-dir'));
}

return static::$binDir;
}

protected static function getVendorDir(Event $event)
{
if (!static::$vendorDir) {
static::$vendorDir = realpath($event->getComposer()->getConfig()->get('vendor-dir'));
}

return static::$vendorDir;
}

protected static function bootstrap(Event $event)
{
require_once static::getVendorDir($event) . '/autoload.php';
}

/**
* Custom PHP Code Sniffer Fixer to be run with lint-staged pre-commit hook.
*/
public static function phpcbf(Event $event): void
{
$start_time = microtime(true);

static::bootstrap($event);

$rootDir = realpath(getcwd());

$cmd = str_replace($rootDir . DIRECTORY_SEPARATOR, '', realpath(static::getBinDir($event) . '/phpcbf'));

$files = $event->getArguments();
$count = count($files);

$ignoredPaths = [];

if ($count > 0) {
$event->getIO()->write("Fixing PHP Coding Standard of ${count} paths.");

foreach ($files as $i => $file) {
$realPath = realpath($file);

if (!$realPath) {
$ignoredPaths[] = $file;
continue;
}

// if ($realPath === realpath(__FILE__)) {
// // Do not self-fix this file when lint-staged
// continue;
// }

$relativePath = str_replace(
[$rootDir . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR],
['', '/'],
$realPath
);

$type = strlen($relativePath) < 4 || stripos($relativePath, '.php', -4) === false ? 'directory' : 'file';

$event->getIO()->write("Improving <info>${relativePath}</info> ${type}...");

$output = [];
$return = 0;

// NOTE: workarround: need to run 2 times due to a bug that exits 1 instead of 0 when a file gets fixed
// https://github.com/squizlabs/PHP_CodeSniffer/issues/1818#issuecomment-735620637
exec("${cmd} \"${realPath}\" || ${cmd} \"${realPath}\" -q", $output, $return);

$event->getIO()->write($output, true, IOInterface::VERBOSE);

if ($return !== 0) {
$event->getIO()->error("Error! Unable to autofix the ${relativePath} file!");
$event->getIO()->write(
'<comment>Run <options=bold>`phpcs`</> manually to check the conflicting files</comment>'
);
exit(1);
}
}
}

$event->getIO()->write('<info>Everything is awesome!</info>');

$end_time = microtime(true);
$execution_time = round($end_time - $start_time, 2);

$event->getIO()->write("Done in ${execution_time}s");

if (count($ignoredPaths)) {
$ignoredPaths = array_map(function ($item) {
return ' - ' . $item;
}, $ignoredPaths);

$event->getIO()->write('<comment>Note: Some paths were not found:</comment>');
$event->getIO()->write($ignoredPaths);
}
}
}

0 comments on commit 9b1cf4b

Please # to comment.