chore: Add php code sniffer and apply fixes

This commit is contained in:
Attila Jozsef Kerekes 2022-11-25 01:35:56 +01:00 committed by Attila Kerekes
parent 181b7564e8
commit 7565bd4028
1532 changed files with 166809 additions and 151 deletions

View file

@ -2,6 +2,7 @@
namespace App; namespace App;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@ -61,16 +62,14 @@ class Application extends Model
$name = $this->name; $name = $this->name;
$name = preg_replace('/[^\p{L}\p{N}]/u', '', $name); $name = preg_replace('/[^\p{L}\p{N}]/u', '', $name);
$class = '\App\SupportedApps\\'.$name.'\\'.$name; return '\App\SupportedApps\\'.$name.'\\'.$name;
return $class;
} }
/** /**
* @param $name * @param $name
* @return string * @return string
*/ */
public static function classFromName($name) public static function classFromName($name): string
{ {
$name = preg_replace('/[^\p{L}\p{N}]/u', '', $name); $name = preg_replace('/[^\p{L}\p{N}]/u', '', $name);
@ -110,6 +109,7 @@ class Application extends Model
/** /**
* @param $appid * @param $appid
* @return mixed|null * @return mixed|null
* @throws GuzzleException
*/ */
public static function getApp($appid) public static function getApp($appid)
{ {
@ -121,13 +121,12 @@ class Application extends Model
$application = ($localapp) ? $localapp : new self; $application = ($localapp) ? $localapp : new self;
// Files missing? || app not in db || old sha version // Files missing? || app not in db || old sha version
if ( if (! file_exists(app_path('SupportedApps/'.className($app->name))) ||
! file_exists(app_path('SupportedApps/'.className($app->name))) ||
! $localapp || ! $localapp ||
$localapp->sha !== $app->sha $localapp->sha !== $app->sha
) { ) {
$gotFiles = SupportedApps::getFiles($app); $gotFiles = SupportedApps::getFiles($app);
if($gotFiles) { if ($gotFiles) {
$app = SupportedApps::saveApp($app, $application); $app = SupportedApps::saveApp($app, $application);
} }
} }
@ -147,7 +146,7 @@ class Application extends Model
if ($app === null) { if ($app === null) {
// Try in db for Private App // Try in db for Private App
$appModel = self::where('appid', $appid)->first(); $appModel = self::where('appid', $appid)->first();
if($appModel) { if ($appModel) {
$app = json_decode($appModel->toJson()); $app = json_decode($appModel->toJson());
} }
} }
@ -176,7 +175,7 @@ class Application extends Model
// Check for private apps in the db // Check for private apps in the db
$appsListFromDB = self::all(['appid', 'name']); $appsListFromDB = self::all(['appid', 'name']);
foreach($appsListFromDB as $app) { foreach ($appsListFromDB as $app) {
// Already existing keys are overwritten, // Already existing keys are overwritten,
// only private apps should be added at the end // only private apps should be added at the end
$list[$app->appid] = $app->name; $list[$app->appid] = $app->name;

View file

@ -54,7 +54,12 @@ class RegisterApp extends Command
} }
} }
public function addApp($folder, $remove = false) /**
* @param $folder
* @param bool $remove
* @return void
*/
public function addApp($folder, bool $remove = false)
{ {
$json = app_path('SupportedApps/'.$folder.'/app.json'); $json = app_path('SupportedApps/'.$folder.'/app.json');
@ -88,7 +93,13 @@ class RegisterApp extends Command
$this->info('Application Added - ' . $app->name . ' - ' . $app->appid); $this->info('Application Added - ' . $app->name . ' - ' . $app->appid);
} }
private function saveIcon($appFolder, $icon) { /**
* @param $appFolder
* @param $icon
* @return void
*/
private function saveIcon($appFolder, $icon)
{
$iconPath = app_path('SupportedApps/' . $appFolder . '/' . $icon); $iconPath = app_path('SupportedApps/' . $appFolder . '/' . $icon);
$contents = file_get_contents($iconPath); $contents = file_get_contents($iconPath);
Storage::disk('public')->put('icons/'.$icon, $contents); Storage::disk('public')->put('icons/'.$icon, $contents);

View file

@ -19,7 +19,7 @@ class Kernel extends ConsoleKernel
/** /**
* Define the application's command schedule. * Define the application's command schedule.
* *
* @param \Illuminate\Console\Scheduling\Schedule $schedule * @param Schedule $schedule
* @return void * @return void
*/ */
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)

View file

@ -2,7 +2,14 @@
use Illuminate\Support\Str; use Illuminate\Support\Str;
function format_bytes($bytes, $is_drive_size = true, $beforeunit = '', $afterunit = '') /**
* @param $bytes
* @param bool $is_drive_size
* @param string $beforeunit
* @param string $afterunit
* @return string
*/
function format_bytes($bytes, bool $is_drive_size = true, string $beforeunit = '', string $afterunit = ''): string
{ {
$btype = ($is_drive_size === true) ? 1000 : 1024; $btype = ($is_drive_size === true) ? 1000 : 1024;
$labels = ['B', 'KB', 'MB', 'GB', 'TB']; $labels = ['B', 'KB', 'MB', 'GB', 'TB'];
@ -18,7 +25,13 @@ function format_bytes($bytes, $is_drive_size = true, $beforeunit = '', $afteruni
} }
} }
function str_slug($title, $separator = '-', $language = 'en') /**
* @param $title
* @param string $separator
* @param string $language
* @return string
*/
function str_slug($title, string $separator = '-', string $language = 'en'): string
{ {
return Str::slug($title, $separator, $language); return Str::slug($title, $separator, $language);
} }
@ -28,17 +41,21 @@ if (! function_exists('str_is')) {
* Determine if a given string matches a given pattern. * Determine if a given string matches a given pattern.
* *
* @param string|array $pattern * @param string|array $pattern
* @param string $value * @param string $value
* @return bool * @return bool
* *
* @deprecated Str::is() should be used directly instead. Will be removed in Laravel 6.0. * @deprecated Str::is() should be used directly instead. Will be removed in Laravel 6.0.
*/ */
function str_is($pattern, $value) function str_is($pattern, string $value): bool
{ {
return Str::is($pattern, $value); return Str::is($pattern, $value);
} }
} }
/**
* @param $hex
* @return float|int
*/
function get_brightness($hex) function get_brightness($hex)
{ {
// returns brightness value from 0 to 255 // returns brightness value from 0 to 255
@ -56,7 +73,11 @@ function get_brightness($hex)
return (($c_r * 299) + ($c_g * 587) + ($c_b * 114)) / 1000; return (($c_r * 299) + ($c_g * 587) + ($c_b * 114)) / 1000;
} }
function title_color($hex) /**
* @param $hex
* @return string
*/
function title_color($hex): string
{ {
if (get_brightness($hex) > 130) { if (get_brightness($hex) > 130) {
return ' black'; return ' black';
@ -65,7 +86,10 @@ function title_color($hex)
} }
} }
function getLinkTargetAttribute() /**
* @return string
*/
function getLinkTargetAttribute(): string
{ {
$target = \App\Setting::fetch('window_target'); $target = \App\Setting::fetch('window_target');
@ -76,9 +100,11 @@ function getLinkTargetAttribute()
} }
} }
/**
* @param $name
* @return array|string|string[]|null
*/
function className($name) function className($name)
{ {
$name = preg_replace('/[^\p{L}\p{N}]/u', '', $name); return preg_replace('/[^\p{L}\p{N}]/u', '', $name);
return $name;
} }

View file

@ -4,11 +4,17 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\User; use App\User;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;
class LoginController extends Controller class LoginController extends Controller
{ {
@ -30,7 +36,7 @@ class LoginController extends Controller
* *
* @var string * @var string
*/ */
protected $redirectTo = '/'; protected string $redirectTo = '/';
/** /**
* Create a new controller instance. * Create a new controller instance.
@ -43,7 +49,10 @@ class LoginController extends Controller
$this->middleware('guest')->except('logout'); $this->middleware('guest')->except('logout');
} }
public function username() /**
* @return string
*/
public function username(): string
{ {
return 'username'; return 'username';
} }
@ -51,12 +60,12 @@ class LoginController extends Controller
/** /**
* Handle a login request to the application. * Handle a login request to the application.
* *
* @param \Illuminate\Http\Request $request * @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse * @return Response
* *
* @throws \Illuminate\Validation\ValidationException * @throws ValidationException
*/ */
public function login(Request $request) public function login(Request $request): Response
{ {
$current_user = User::currentUser(); $current_user = User::currentUser();
$request->merge(['username' => $current_user->username, 'remember' => true]); $request->merge(['username' => $current_user->username, 'remember' => true]);
@ -88,7 +97,11 @@ class LoginController extends Controller
{ {
} }
public function setUser(User $user) /**
* @param User $user
* @return RedirectResponse
*/
public function setUser(User $user): RedirectResponse
{ {
Auth::logout(); Auth::logout();
session(['current_user' => $user]); session(['current_user' => $user]);
@ -96,7 +109,11 @@ class LoginController extends Controller
return redirect()->route('dash'); return redirect()->route('dash');
} }
public function autologin($uuid) /**
* @param $uuid
* @return RedirectResponse
*/
public function autologin($uuid): RedirectResponse
{ {
$user = User::where('autologin', $uuid)->first(); $user = User::where('autologin', $uuid)->first();
Auth::login($user, true); Auth::login($user, true);
@ -108,18 +125,26 @@ class LoginController extends Controller
/** /**
* Show the application's login form. * Show the application's login form.
* *
* @return \Illuminate\Http\Response * @return Application|Factory|View
*/ */
public function showLoginForm() public function showLoginForm()
{ {
return view('auth.login'); return view('auth.login');
} }
protected function authenticated(Request $request, $user) /**
* @param Request $request
* @param $user
* @return RedirectResponse
*/
protected function authenticated(Request $request, $user): RedirectResponse
{ {
return back(); return back();
} }
/**
* @return mixed|string
*/
public function redirectTo() public function redirectTo()
{ {
return Session::get('url.intended') ? Session::get('url.intended') : $this->redirectTo; return Session::get('url.intended') ? Session::get('url.intended') : $this->redirectTo;

View file

@ -27,7 +27,7 @@ class RegisterController extends Controller
* *
* @var string * @var string
*/ */
protected $redirectTo = '/'; protected string $redirectTo = '/';
/** /**
* Create a new controller instance. * Create a new controller instance.
@ -45,7 +45,7 @@ class RegisterController extends Controller
* @param array $data * @param array $data
* @return \Illuminate\Contracts\Validation\Validator * @return \Illuminate\Contracts\Validation\Validator
*/ */
protected function validator(array $data) protected function validator(array $data): \Illuminate\Contracts\Validation\Validator
{ {
return Validator::make($data, [ return Validator::make($data, [
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
@ -58,7 +58,7 @@ class RegisterController extends Controller
* Create a new user instance after a valid registration. * Create a new user instance after a valid registration.
* *
* @param array $data * @param array $data
* @return \App\User * @return User
*/ */
protected function create(array $data) protected function create(array $data)
{ {

View file

@ -25,7 +25,7 @@ class ResetPasswordController extends Controller
* *
* @var string * @var string
*/ */
protected $redirectTo = '/'; protected string $redirectTo = '/';
/** /**
* Create a new controller instance. * Create a new controller instance.

View file

@ -9,6 +9,7 @@ use App\User;
use Artisan; use Artisan;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\ServerException; use GuzzleHttp\Exception\ServerException;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
@ -204,7 +205,7 @@ class ItemController extends Controller
"verify_peer"=>false, "verify_peer"=>false,
"verify_peer_name"=>false, "verify_peer_name"=>false,
), ),
); );
$contents = file_get_contents($request->input('icon'), false, stream_context_create($options)); $contents = file_get_contents($request->input('icon'), false, stream_context_create($options));
if ($application) { if ($application) {
@ -219,9 +220,9 @@ class ItemController extends Controller
// Private apps could have here duplicated icons folder // Private apps could have here duplicated icons folder
if (strpos($path, 'icons/icons/') !== false) { if (strpos($path, 'icons/icons/') !== false) {
$path = str_replace('icons/icons/','icons/',$path); $path = str_replace('icons/icons/', 'icons/', $path);
} }
if(! Storage::disk('public')->exists($path)) { if (! Storage::disk('public')->exists($path)) {
Storage::disk('public')->put($path, $contents); Storage::disk('public')->put($path, $contents);
} }
$request->merge([ $request->merge([
@ -360,6 +361,7 @@ class ItemController extends Controller
* *
* @param Request $request * @param Request $request
* @return string|null * @return string|null
* @throws GuzzleException
*/ */
public function appload(Request $request): ?string public function appload(Request $request): ?string
{ {
@ -386,9 +388,9 @@ class ItemController extends Controller
$output['colour'] = ($app->tile_background == 'light') ? '#fafbfc' : '#161b1f'; $output['colour'] = ($app->tile_background == 'light') ? '#fafbfc' : '#161b1f';
if(strpos($app->icon, '://') !== false) { if (strpos($app->icon, '://') !== false) {
$output['iconview'] = $app->icon; $output['iconview'] = $app->icon;
} elseif(strpos($app->icon, 'icons/') !== false) { } elseif (strpos($app->icon, 'icons/') !== false) {
// Private apps have the icon locally // Private apps have the icon locally
$output['iconview'] = URL::to('/').'/storage/'.$app->icon; $output['iconview'] = URL::to('/').'/storage/'.$app->icon;
$output['icon'] = str_replace('icons/', '', $output['icon']); $output['icon'] = str_replace('icons/', '', $output['icon']);

View file

@ -5,6 +5,8 @@ namespace App\Http\Controllers;
use App\Item; use App\Item;
use App\User; use App\User;
use DB; use DB;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -20,7 +22,7 @@ class TagController extends Controller
/** /**
* Display a listing of the resource. * Display a listing of the resource.
* *
* @return Response * @return Application|Factory|View
*/ */
public function index(Request $request) public function index(Request $request)
{ {
@ -38,7 +40,7 @@ class TagController extends Controller
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *
* @return Response * @return Application|Factory|View
*/ */
public function create() public function create()
{ {
@ -155,6 +157,7 @@ class TagController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param Request $request
* @param int $id * @param int $id
* @return RedirectResponse * @return RedirectResponse
*/ */

View file

@ -4,6 +4,7 @@ namespace App\Http\Middleware;
use App\User; use App\User;
use Closure; use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Session; use Session;
@ -13,11 +14,11 @@ class CheckAllowed
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request * @param Request $request
* @param \Closure $next * @param Closure $next
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle(Request $request, Closure $next)
{ {
$route = Route::currentRouteName(); $route = Route::currentRouteName();
$current_user = User::currentUser(); $current_user = User::currentUser();

View file

@ -3,6 +3,7 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated class RedirectIfAuthenticated
@ -10,8 +11,8 @@ class RedirectIfAuthenticated
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request * @param Request $request
* @param \Closure $next * @param Closure $next
* @param string|null $guard * @param string|null $guard
* @return mixed * @return mixed
*/ */

View file

@ -2,15 +2,22 @@
namespace App; namespace App;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use stdClass;
use Symfony\Component\ClassLoader\ClassMapGenerator; use Symfony\Component\ClassLoader\ClassMapGenerator;
class Item extends Model class Item extends Model
{ {
use SoftDeletes; use SoftDeletes;
/**
* @return void
*/
protected static function boot() protected static function boot()
{ {
parent::boot(); parent::boot();
@ -25,9 +32,20 @@ class Item extends Model
}); });
} }
//
protected $fillable = [ protected $fillable = [
'title', 'url', 'colour', 'icon', 'appdescription', 'description', 'pinned', 'order', 'type', 'class', 'user_id', 'tag_id', 'appid', 'title',
'url',
'colour',
'icon',
'appdescription',
'description',
'pinned',
'order',
'type',
'class',
'user_id',
'tag_id',
'appid',
]; ];
@ -35,10 +53,10 @@ class Item extends Model
/** /**
* Scope a query to only include pinned items. * Scope a query to only include pinned items.
* *
* @param \Illuminate\Database\Eloquent\Builder $query * @param Builder $query
* @return \Illuminate\Database\Eloquent\Builder * @return Builder
*/ */
public function scopePinned($query) public function scopePinned(Builder $query): Builder
{ {
return $query->where('pinned', 1); return $query->where('pinned', 1);
} }
@ -74,16 +92,25 @@ class Item extends Model
return $tagdetails; return $tagdetails;
} }
public function parents() /**
* @return BelongsToMany
*/
public function parents(): BelongsToMany
{ {
return $this->belongsToMany(\App\Item::class, 'item_tag', 'item_id', 'tag_id'); return $this->belongsToMany(Item::class, 'item_tag', 'item_id', 'tag_id');
} }
public function children() /**
* @return BelongsToMany
*/
public function children(): BelongsToMany
{ {
return $this->belongsToMany(\App\Item::class, 'item_tag', 'tag_id', 'item_id'); return $this->belongsToMany(Item::class, 'item_tag', 'tag_id', 'item_id');
} }
/**
* @return \Illuminate\Contracts\Foundation\Application|UrlGenerator|mixed|string
*/
public function getLinkAttribute() public function getLinkAttribute()
{ {
if ((int) $this->type === 1) { if ((int) $this->type === 1) {
@ -93,7 +120,10 @@ class Item extends Model
} }
} }
public function getDroppableAttribute() /**
* @return string
*/
public function getDroppableAttribute(): string
{ {
if ((int) $this->type === 1) { if ((int) $this->type === 1) {
return ' droppable'; return ' droppable';
@ -102,7 +132,10 @@ class Item extends Model
} }
} }
public function getLinkTargetAttribute() /**
* @return string
*/
public function getLinkTargetAttribute(): string
{ {
$target = Setting::fetch('window_target'); $target = Setting::fetch('window_target');
@ -113,7 +146,10 @@ class Item extends Model
} }
} }
public function getLinkIconAttribute() /**
* @return string
*/
public function getLinkIconAttribute(): string
{ {
if ((int) $this->type === 1) { if ((int) $this->type === 1) {
return 'fa-tag'; return 'fa-tag';
@ -122,7 +158,10 @@ class Item extends Model
} }
} }
public function getLinkTypeAttribute() /**
* @return string
*/
public function getLinkTypeAttribute(): string
{ {
if ((int) $this->type === 1) { if ((int) $this->type === 1) {
return 'tags'; return 'tags';
@ -131,6 +170,10 @@ class Item extends Model
} }
} }
/**
* @param $class
* @return false|mixed|string
*/
public static function nameFromClass($class) public static function nameFromClass($class)
{ {
$explode = explode('\\', $class); $explode = explode('\\', $class);
@ -139,6 +182,11 @@ class Item extends Model
return $name; return $name;
} }
/**
* @param $query
* @param $type
* @return mixed
*/
public function scopeOfType($query, $type) public function scopeOfType($query, $type)
{ {
switch ($type) { switch ($type) {
@ -153,7 +201,10 @@ class Item extends Model
return $query->where('type', $typeid); return $query->where('type', $typeid);
} }
public function enhanced() /**
* @return bool
*/
public function enhanced(): bool
{ {
/*if(isset($this->class) && !empty($this->class)) { /*if(isset($this->class) && !empty($this->class)) {
$app = new $this->class; $app = new $this->class;
@ -164,16 +215,24 @@ class Item extends Model
return $this->description !== null; return $this->description !== null;
} }
public static function isEnhanced($class) /**
* @param $class
* @return bool
*/
public static function isEnhanced($class): bool
{ {
if (!class_exists($class, false) || $class === null || $class === 'null') { if (!class_exists($class, false) || $class === null || $class === 'null') {
return false; return false;
} }
$app = new $class; $app = new $class;
return (bool) ($app instanceof \App\EnhancedApps); return (bool) ($app instanceof EnhancedApps);
} }
/**
* @param $class
* @return false|mixed
*/
public static function isSearchProvider($class) public static function isSearchProvider($class)
{ {
if (!class_exists($class, false) || $class === null || $class === 'null') { if (!class_exists($class, false) || $class === null || $class === 'null') {
@ -181,10 +240,13 @@ class Item extends Model
} }
$app = new $class; $app = new $class;
return ((bool) ($app instanceof \App\SearchInterface)) ? $app : false; return ((bool) ($app instanceof SearchInterface)) ? $app : false;
} }
public function enabled() /**
* @return bool
*/
public function enabled(): bool
{ {
if ($this->enhanced()) { if ($this->enhanced()) {
$config = $this->getconfig(); $config = $this->getconfig();
@ -196,12 +258,15 @@ class Item extends Model
return false; return false;
} }
/**
* @return mixed|stdClass
*/
public function getconfig() public function getconfig()
{ {
// $explode = explode('\\', $this->class); // $explode = explode('\\', $this->class);
if (! isset($this->description) || empty($this->description)) { if (! isset($this->description) || empty($this->description)) {
$config = new \stdClass; $config = new stdClass;
// $config->name = end($explode); // $config->name = end($explode);
$config->enabled = false; $config->enabled = false;
$config->override_url = null; $config->override_url = null;
@ -224,7 +289,11 @@ class Item extends Model
return $config; return $config;
} }
public static function applicationDetails($class) /**
* @param $class
* @return false
*/
public static function applicationDetails($class): bool
{ {
if (! empty($class)) { if (! empty($class)) {
$name = self::nameFromClass($class); $name = self::nameFromClass($class);
@ -237,7 +306,11 @@ class Item extends Model
return false; return false;
} }
public static function getApplicationDescription($class) /**
* @param $class
* @return string
*/
public static function getApplicationDescription($class): string
{ {
$details = self::applicationDetails($class); $details = self::applicationDetails($class);
if ($details !== false) { if ($details !== false) {
@ -249,9 +322,11 @@ class Item extends Model
/** /**
* Get the user that owns the item. * Get the user that owns the item.
*
* @return BelongsTo
*/ */
public function user() public function user(): BelongsTo
{ {
return $this->belongsTo(\App\User::class); return $this->belongsTo(User::class);
} }
} }

View file

@ -5,6 +5,7 @@ namespace App\Jobs;
use App\Application; use App\Application;
use App\Item; use App\Item;
use App\SupportedApps; use App\SupportedApps;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
@ -32,6 +33,7 @@ class ProcessApps implements ShouldQueue, ShouldBeUnique
* Execute the job. * Execute the job.
* *
* @return void * @return void
* @throws GuzzleException
*/ */
public function handle() public function handle()
{ {

View file

@ -3,6 +3,7 @@
namespace App\Jobs; namespace App\Jobs;
use App\Application; use App\Application;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
@ -30,6 +31,7 @@ class UpdateApps implements ShouldQueue, ShouldBeUnique
* Execute the job. * Execute the job.
* *
* @return void * @return void
* @throws GuzzleException
*/ */
public function handle() public function handle()
{ {

View file

@ -11,6 +11,8 @@ use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -123,6 +125,8 @@ class AppServiceProvider extends ServiceProvider
* Check if database needs an update or do first time database setup * Check if database needs an update or do first time database setup
* *
* @return void * @return void
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/ */
public function setupDatabase(): void public function setupDatabase(): void
{ {

View file

@ -4,6 +4,7 @@ namespace App;
use Cache; use Cache;
use Form; use Form;
use Illuminate\Support\Collection;
use Yaml; use Yaml;
abstract class Search abstract class Search
@ -11,7 +12,7 @@ abstract class Search
/** /**
* List of all search providers * List of all search providers
* *
* @return \Illuminate\Support\Collection * @return Collection
*/ */
public static function providers() public static function providers()
{ {
@ -121,7 +122,15 @@ abstract class Search
$output .= '<option value="'.$key.'"'.$selected.'>'.$searchprovider['name'].'</option>'; $output .= '<option value="'.$key.'"'.$selected.'>'.$searchprovider['name'].'</option>';
} }
$output .= '</select>'; $output .= '</select>';
$output .= Form::text('q', null, ['class' => 'homesearch', 'autofocus' => 'autofocus', 'placeholder' => __('app.settings.search').'...']); $output .= Form::text(
'q',
null,
[
'class' => 'homesearch',
'autofocus' => 'autofocus',
'placeholder' => __('app.settings.search').'...'
]
);
$output .= '<button type="submit">'.ucwords(__('app.settings.search')).'</button>'; $output .= '<button type="submit">'.ucwords(__('app.settings.search')).'</button>';
$output .= '</div>'; $output .= '</div>';
$output .= '</form>'; $output .= '</form>';

View file

@ -4,7 +4,11 @@ namespace App;
use Form; use Form;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Session\SessionManager;
use Illuminate\Session\Store;
use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Input;
class Setting extends Model class Setting extends Model
@ -57,7 +61,11 @@ class Setting extends Model
switch ($this->type) { switch ($this->type) {
case 'image': case 'image':
if (! empty($this->value)) { if (! empty($this->value)) {
$value = '<a href="'.asset('storage/'.$this->value).'" title="'.__('app.settings.view').'" target="_blank">'.__('app.settings.view').'</a>'; $value = '<a href="'.asset('storage/'.$this->value).'" title="'.
__('app.settings.view').
'" target="_blank">'.
__('app.settings.view').
'</a>';
} else { } else {
$value = __('app.options.none'); $value = __('app.options.none');
} }
@ -75,7 +83,9 @@ class Setting extends Model
if ($this->key === 'search_provider') { if ($this->key === 'search_provider') {
$options = Search::providers()->pluck('name', 'id')->toArray(); $options = Search::providers()->pluck('name', 'id')->toArray();
} }
$value = (array_key_exists($this->value, $options)) ? __($options[$this->value]) : __('app.options.none'); $value = (array_key_exists($this->value, $options))
? __($options[$this->value])
: __('app.options.none');
} else { } else {
$value = __('app.options.none'); $value = __('app.options.none');
} }
@ -100,11 +110,24 @@ class Setting extends Model
case 'image': case 'image':
$value = ''; $value = '';
if (isset($this->value) && ! empty($this->value)) { if (isset($this->value) && ! empty($this->value)) {
$value .= '<a class="setting-view-image" href="'.asset('storage/'.$this->value).'" title="'.__('app.settings.view').'" target="_blank"><img src="'.asset('storage/'.$this->value).'" /></a>'; $value .= '<a class="setting-view-image" href="'.
asset('storage/'.$this->value).
'" title="'.
__('app.settings.view').
'" target="_blank"><img src="'.
asset('storage/'.
$this->value).
'" /></a>';
} }
$value .= Form::file('value', ['class' => 'form-control']); $value .= Form::file('value', ['class' => 'form-control']);
if (isset($this->value) && ! empty($this->value)) { if (isset($this->value) && ! empty($this->value)) {
$value .= '<a class="settinglink" href="'.route('settings.clear', $this->id).'" title="'.__('app.settings.remove').'">'.__('app.settings.reset').'</a>'; $value .= '<a class="settinglink" href="'.
route('settings.clear', $this->id).
'" title="'.
__('app.settings.remove').
'">'.
__('app.settings.reset').
'</a>';
} }
break; break;
@ -143,7 +166,10 @@ class Setting extends Model
return $value; return $value;
} }
public function group() /**
* @return BelongsTo
*/
public function group(): BelongsTo
{ {
return $this->belongsTo(\App\SettingGroup::class, 'group_id'); return $this->belongsTo(\App\SettingGroup::class, 'group_id');
} }
@ -153,7 +179,7 @@ class Setting extends Model
* *
* @return mixed * @return mixed
*/ */
public static function fetch($key) public static function fetch(string $key)
{ {
$user = self::user(); $user = self::user();
@ -220,7 +246,7 @@ class Setting extends Model
* *
* @return bool * @return bool
*/ */
public static function cached($key) public static function cached($key): bool
{ {
return array_key_exists($key, self::$cache); return array_key_exists($key, self::$cache);
} }
@ -228,11 +254,14 @@ class Setting extends Model
/** /**
* The users that belong to the setting. * The users that belong to the setting.
*/ */
public function users() public function users(): BelongsToMany
{ {
return $this->belongsToMany(\App\User::class)->using(\App\SettingUser::class)->withPivot('uservalue'); return $this->belongsToMany(\App\User::class)->using(\App\SettingUser::class)->withPivot('uservalue');
} }
/**
* @return \Illuminate\Contracts\Foundation\Application|SessionManager|Store|mixed
*/
public static function user() public static function user()
{ {
return User::currentUser(); return User::currentUser();

View file

@ -3,6 +3,7 @@
namespace App; namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class SettingGroup extends Model class SettingGroup extends Model
{ {
@ -20,7 +21,10 @@ class SettingGroup extends Model
*/ */
public $timestamps = false; public $timestamps = false;
public function settings() /**
* @return HasMany
*/
public function settings(): HasMany
{ {
return $this->hasMany(\App\Setting::class, 'group_id'); return $this->hasMany(\App\Setting::class, 'group_id');
} }

View file

@ -3,7 +3,9 @@
namespace App; namespace App;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Psr\Http\Message\ResponseInterface;
abstract class SupportedApps abstract class SupportedApps
{ {
@ -13,7 +15,13 @@ abstract class SupportedApps
protected $error; protected $error;
public function appTest($url, $attrs = [], $overridevars = false) /**
* @param $url
* @param array $attrs
* @param bool $overridevars
* @return object
*/
public function appTest($url, array $attrs = [], bool $overridevars = false): object
{ {
if (empty($this->config->url)) { if (empty($this->config->url)) {
return (object) [ return (object) [
@ -52,8 +60,20 @@ abstract class SupportedApps
]; ];
} }
public function execute($url, $attrs = [], $overridevars = false, $overridemethod = false) /**
{ * @param $url
* @param array $attrs
* @param bool $overridevars
* @param bool $overridemethod
* @return ResponseInterface|null
* @throws GuzzleException
*/
public function execute(
$url,
array $attrs = [],
bool $overridevars = false,
bool $overridemethod = false
): ?ResponseInterface {
$res = null; $res = null;
$vars = ($overridevars !== false) ? $vars = ($overridevars !== false) ?
@ -82,11 +102,19 @@ abstract class SupportedApps
return $res; return $res;
} }
/**
* @return void
*/
public function login() public function login()
{ {
} }
public function normaliseurl($url, $addslash = true) /**
* @param string $url
* @param bool $addslash
* @return string
*/
public function normaliseurl(string $url, bool $addslash = true): string
{ {
$url = rtrim($url, '/'); $url = rtrim($url, '/');
if ($addslash) { if ($addslash) {
@ -96,6 +124,11 @@ abstract class SupportedApps
return $url; return $url;
} }
/**
* @param $status
* @param $data
* @return false|string
*/
public function getLiveStats($status, $data) public function getLiveStats($status, $data)
{ {
$className = get_class($this); $className = get_class($this);
@ -108,7 +141,11 @@ abstract class SupportedApps
//return //return
} }
public static function getList() /**
* @return ResponseInterface
* @throws GuzzleException
*/
public static function getList(): ResponseInterface
{ {
// $list_url = 'https://apps.heimdall.site/list'; // $list_url = 'https://apps.heimdall.site/list';
$list_url = config('app.appsource').'list.json'; $list_url = config('app.appsource').'list.json';
@ -129,7 +166,7 @@ abstract class SupportedApps
/** /**
* @param $app * @param $app
* @return bool|false * @return bool|false
* @throws \GuzzleHttp\Exception\GuzzleException * @throws GuzzleException
*/ */
public static function getFiles($app): bool public static function getFiles($app): bool
{ {
@ -165,6 +202,11 @@ abstract class SupportedApps
return true; return true;
} }
/**
* @param $details
* @param $app
* @return mixed
*/
public static function saveApp($details, $app) public static function saveApp($details, $app)
{ {
$app->appid = $details->appid; $app->appid = $details->appid;

View file

@ -27,7 +27,8 @@
"fzaninotto/faker": "~1.4", "fzaninotto/faker": "~1.4",
"mockery/mockery": "~1.0", "mockery/mockery": "~1.0",
"phpunit/phpunit": "~9.0", "phpunit/phpunit": "~9.0",
"symfony/thanks": "^1.0" "symfony/thanks": "^1.0",
"squizlabs/php_codesniffer": "3.*"
}, },
"autoload": { "autoload": {
"classmap": [ "classmap": [

98
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "5ec4ff397b3937979b48679da0338808", "content-hash": "c1478972b2e1dbdb27f39255a644f4be",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@ -1770,16 +1770,16 @@
}, },
{ {
"name": "laravel/tinker", "name": "laravel/tinker",
"version": "v2.7.2", "version": "v2.7.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/tinker.git", "url": "https://github.com/laravel/tinker.git",
"reference": "dff39b661e827dae6e092412f976658df82dbac5" "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/tinker/zipball/dff39b661e827dae6e092412f976658df82dbac5", "url": "https://api.github.com/repos/laravel/tinker/zipball/5062061b4924af3392225dd482ca7b4d85d8b8ef",
"reference": "dff39b661e827dae6e092412f976658df82dbac5", "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1832,9 +1832,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/laravel/tinker/issues", "issues": "https://github.com/laravel/tinker/issues",
"source": "https://github.com/laravel/tinker/tree/v2.7.2" "source": "https://github.com/laravel/tinker/tree/v2.7.3"
}, },
"time": "2022-03-23T12:38:24+00:00" "time": "2022-11-09T15:11:38+00:00"
}, },
{ {
"name": "laravel/ui", "name": "laravel/ui",
@ -2513,25 +2513,25 @@
}, },
{ {
"name": "nette/schema", "name": "nette/schema",
"version": "v1.2.2", "version": "v1.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nette/schema.git", "url": "https://github.com/nette/schema.git",
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f",
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
"php": ">=7.1 <8.2" "php": ">=7.1 <8.3"
}, },
"require-dev": { "require-dev": {
"nette/tester": "^2.3 || ^2.4", "nette/tester": "^2.3 || ^2.4",
"phpstan/phpstan-nette": "^0.12", "phpstan/phpstan-nette": "^1.0",
"tracy/tracy": "^2.7" "tracy/tracy": "^2.7"
}, },
"type": "library", "type": "library",
@ -2569,9 +2569,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nette/schema/issues", "issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.2.2" "source": "https://github.com/nette/schema/tree/v1.2.3"
}, },
"time": "2021-10-15T11:40:02+00:00" "time": "2022-10-13T01:24:26+00:00"
}, },
{ {
"name": "nette/utils", "name": "nette/utils",
@ -7520,16 +7520,16 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "9.2.18", "version": "9.2.19",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a" "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559",
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a", "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -7585,7 +7585,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18" "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19"
}, },
"funding": [ "funding": [
{ {
@ -7593,7 +7593,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-10-27T13:35:33+00:00" "time": "2022-11-18T07:47:47+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -8902,6 +8902,62 @@
], ],
"time": "2020-09-28T06:39:44+00:00" "time": "2020-09-28T06:39:44+00:00"
}, },
{
"name": "squizlabs/php_codesniffer",
"version": "3.7.1",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"bin": [
"bin/phpcs",
"bin/phpcbf"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Greg Sherwood",
"role": "lead"
}
],
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
"keywords": [
"phpcs",
"standards"
],
"support": {
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
},
"time": "2022-06-18T07:21:10+00:00"
},
{ {
"name": "symfony/thanks", "name": "symfony/thanks",
"version": "v1.2.10", "version": "v1.2.10",

120
vendor/bin/phpcbf vendored Executable file
View file

@ -0,0 +1,120 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcbf)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf');
exit(0);
}
}
include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf';

120
vendor/bin/phpcs vendored Executable file
View file

@ -0,0 +1,120 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcs)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs');
exit(0);
}
}
include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs';

View file

@ -32,6 +32,7 @@ return array(
'App\\Item' => $baseDir . '/app/Item.php', 'App\\Item' => $baseDir . '/app/Item.php',
'App\\ItemTag' => $baseDir . '/app/ItemTag.php', 'App\\ItemTag' => $baseDir . '/app/ItemTag.php',
'App\\Jobs\\ProcessApps' => $baseDir . '/app/Jobs/ProcessApps.php', 'App\\Jobs\\ProcessApps' => $baseDir . '/app/Jobs/ProcessApps.php',
'App\\Jobs\\UpdateApps' => $baseDir . '/app/Jobs/UpdateApps.php',
'App\\Providers\\AppServiceProvider' => $baseDir . '/app/Providers/AppServiceProvider.php', 'App\\Providers\\AppServiceProvider' => $baseDir . '/app/Providers/AppServiceProvider.php',
'App\\Providers\\AuthServiceProvider' => $baseDir . '/app/Providers/AuthServiceProvider.php', 'App\\Providers\\AuthServiceProvider' => $baseDir . '/app/Providers/AuthServiceProvider.php',
'App\\Providers\\BroadcastServiceProvider' => $baseDir . '/app/Providers/BroadcastServiceProvider.php', 'App\\Providers\\BroadcastServiceProvider' => $baseDir . '/app/Providers/BroadcastServiceProvider.php',
@ -43,6 +44,33 @@ return array(
'App\\SettingGroup' => $baseDir . '/app/SettingGroup.php', 'App\\SettingGroup' => $baseDir . '/app/SettingGroup.php',
'App\\SettingUser' => $baseDir . '/app/SettingUser.php', 'App\\SettingUser' => $baseDir . '/app/SettingUser.php',
'App\\SupportedApps' => $baseDir . '/app/SupportedApps.php', 'App\\SupportedApps' => $baseDir . '/app/SupportedApps.php',
'App\\SupportedApps\\AMP\\AMP' => $baseDir . '/app/SupportedApps/AMP/AMP.php',
'App\\SupportedApps\\AdGuardHome\\AdGuardHome' => $baseDir . '/app/SupportedApps/AdGuardHome/AdGuardHome.php',
'App\\SupportedApps\\Adminer\\Adminer' => $baseDir . '/app/SupportedApps/Adminer/Adminer.php',
'App\\SupportedApps\\Alertmanager\\Alertmanager' => $baseDir . '/app/SupportedApps/Alertmanager/Alertmanager.php',
'App\\SupportedApps\\ArchiSteamFarm\\ArchiSteamFarm' => $baseDir . '/app/SupportedApps/ArchiSteamFarm/ArchiSteamFarm.php',
'App\\SupportedApps\\ArgoCD\\ArgoCD' => $baseDir . '/app/SupportedApps/ArgoCD/ArgoCD.php',
'App\\SupportedApps\\AriaNg\\AriaNg' => $baseDir . '/app/SupportedApps/AriaNg/AriaNg.php',
'App\\SupportedApps\\Atlantis\\Atlantis' => $baseDir . '/app/SupportedApps/Atlantis/Atlantis.php',
'App\\SupportedApps\\Bastillion\\Bastillion' => $baseDir . '/app/SupportedApps/Bastillion/Bastillion.php',
'App\\SupportedApps\\Jira\\Jira' => $baseDir . '/app/SupportedApps/Jira/Jira.php',
'App\\SupportedApps\\Kibana\\Kibana' => $baseDir . '/app/SupportedApps/Kibana/Kibana.php',
'App\\SupportedApps\\MotionEye\\MotionEye' => $baseDir . '/app/SupportedApps/MotionEye/MotionEye.php',
'App\\SupportedApps\\Munin\\Munin' => $baseDir . '/app/SupportedApps/Munin/Munin.php',
'App\\SupportedApps\\MusicBrainz\\MusicBrainz' => $baseDir . '/app/SupportedApps/MusicBrainz/MusicBrainz.php',
'App\\SupportedApps\\Pihole\\Pihole' => $baseDir . '/app/SupportedApps/Pihole/Pihole.php',
'App\\SupportedApps\\Plex\\Plex' => $baseDir . '/app/SupportedApps/Plex/Plex.php',
'App\\SupportedApps\\Portainer\\Portainer' => $baseDir . '/app/SupportedApps/Portainer/Portainer.php',
'App\\SupportedApps\\Poste\\Poste' => $baseDir . '/app/SupportedApps/Poste/Poste.php',
'App\\SupportedApps\\Printer\\Printer' => $baseDir . '/app/SupportedApps/Printer/Printer.php',
'App\\SupportedApps\\Rancher\\Rancher' => $baseDir . '/app/SupportedApps/Rancher/Rancher.php',
'App\\SupportedApps\\TYPO3\\TYPO3' => $baseDir . '/app/SupportedApps/TYPO3/TYPO3.php',
'App\\SupportedApps\\Tar1090\\Tar1090' => $baseDir . '/app/SupportedApps/Tar1090/Tar1090.php',
'App\\SupportedApps\\Transmission\\Transmission' => $baseDir . '/app/SupportedApps/Transmission/Transmission.php',
'App\\SupportedApps\\Trilium\\Trilium' => $baseDir . '/app/SupportedApps/Trilium/Trilium.php',
'App\\SupportedApps\\TrueNAS\\TrueNAS' => $baseDir . '/app/SupportedApps/TrueNAS/TrueNAS.php',
'App\\SupportedApps\\Ubooquity\\Ubooquity' => $baseDir . '/app/SupportedApps/Ubooquity/Ubooquity.php',
'App\\SupportedApps\\UniFi\\UniFi' => $baseDir . '/app/SupportedApps/UniFi/UniFi.php',
'App\\User' => $baseDir . '/app/User.php', 'App\\User' => $baseDir . '/app/User.php',
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Brick\\Math\\BigDecimal' => $vendorDir . '/brick/math/src/BigDecimal.php', 'Brick\\Math\\BigDecimal' => $vendorDir . '/brick/math/src/BigDecimal.php',

View file

@ -630,6 +630,7 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
'App\\Item' => __DIR__ . '/../..' . '/app/Item.php', 'App\\Item' => __DIR__ . '/../..' . '/app/Item.php',
'App\\ItemTag' => __DIR__ . '/../..' . '/app/ItemTag.php', 'App\\ItemTag' => __DIR__ . '/../..' . '/app/ItemTag.php',
'App\\Jobs\\ProcessApps' => __DIR__ . '/../..' . '/app/Jobs/ProcessApps.php', 'App\\Jobs\\ProcessApps' => __DIR__ . '/../..' . '/app/Jobs/ProcessApps.php',
'App\\Jobs\\UpdateApps' => __DIR__ . '/../..' . '/app/Jobs/UpdateApps.php',
'App\\Providers\\AppServiceProvider' => __DIR__ . '/../..' . '/app/Providers/AppServiceProvider.php', 'App\\Providers\\AppServiceProvider' => __DIR__ . '/../..' . '/app/Providers/AppServiceProvider.php',
'App\\Providers\\AuthServiceProvider' => __DIR__ . '/../..' . '/app/Providers/AuthServiceProvider.php', 'App\\Providers\\AuthServiceProvider' => __DIR__ . '/../..' . '/app/Providers/AuthServiceProvider.php',
'App\\Providers\\BroadcastServiceProvider' => __DIR__ . '/../..' . '/app/Providers/BroadcastServiceProvider.php', 'App\\Providers\\BroadcastServiceProvider' => __DIR__ . '/../..' . '/app/Providers/BroadcastServiceProvider.php',

View file

@ -2015,17 +2015,17 @@
}, },
{ {
"name": "laravel/tinker", "name": "laravel/tinker",
"version": "v2.7.2", "version": "v2.7.3",
"version_normalized": "2.7.2.0", "version_normalized": "2.7.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/tinker.git", "url": "https://github.com/laravel/tinker.git",
"reference": "dff39b661e827dae6e092412f976658df82dbac5" "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/tinker/zipball/dff39b661e827dae6e092412f976658df82dbac5", "url": "https://api.github.com/repos/laravel/tinker/zipball/5062061b4924af3392225dd482ca7b4d85d8b8ef",
"reference": "dff39b661e827dae6e092412f976658df82dbac5", "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2043,7 +2043,7 @@
"suggest": { "suggest": {
"illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0)." "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0)."
}, },
"time": "2022-03-23T12:38:24+00:00", "time": "2022-11-09T15:11:38+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -2080,7 +2080,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/laravel/tinker/issues", "issues": "https://github.com/laravel/tinker/issues",
"source": "https://github.com/laravel/tinker/tree/v2.7.2" "source": "https://github.com/laravel/tinker/tree/v2.7.3"
}, },
"install-path": "../laravel/tinker" "install-path": "../laravel/tinker"
}, },
@ -2922,29 +2922,29 @@
}, },
{ {
"name": "nette/schema", "name": "nette/schema",
"version": "v1.2.2", "version": "v1.2.3",
"version_normalized": "1.2.2.0", "version_normalized": "1.2.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nette/schema.git", "url": "https://github.com/nette/schema.git",
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f",
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
"php": ">=7.1 <8.2" "php": ">=7.1 <8.3"
}, },
"require-dev": { "require-dev": {
"nette/tester": "^2.3 || ^2.4", "nette/tester": "^2.3 || ^2.4",
"phpstan/phpstan-nette": "^0.12", "phpstan/phpstan-nette": "^1.0",
"tracy/tracy": "^2.7" "tracy/tracy": "^2.7"
}, },
"time": "2021-10-15T11:40:02+00:00", "time": "2022-10-13T01:24:26+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -2981,7 +2981,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/nette/schema/issues", "issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.2.2" "source": "https://github.com/nette/schema/tree/v1.2.3"
}, },
"install-path": "../nette/schema" "install-path": "../nette/schema"
}, },
@ -4021,17 +4021,17 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "9.2.18", "version": "9.2.19",
"version_normalized": "9.2.18.0", "version_normalized": "9.2.19.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a" "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559",
"reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a", "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4056,7 +4056,7 @@
"ext-pcov": "*", "ext-pcov": "*",
"ext-xdebug": "*" "ext-xdebug": "*"
}, },
"time": "2022-10-27T13:35:33+00:00", "time": "2022-11-18T07:47:47+00:00",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -4089,7 +4089,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18" "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19"
}, },
"funding": [ "funding": [
{ {
@ -6210,6 +6210,65 @@
], ],
"install-path": "../sebastian/version" "install-path": "../sebastian/version"
}, },
{
"name": "squizlabs/php_codesniffer",
"version": "3.7.1",
"version_normalized": "3.7.1.0",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
"reference": "1359e176e9307e906dc3d890bcc9603ff6d90619",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"time": "2022-06-18T07:21:10+00:00",
"bin": [
"bin/phpcs",
"bin/phpcbf"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"installation-source": "dist",
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Greg Sherwood",
"role": "lead"
}
],
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
"keywords": [
"phpcs",
"standards"
],
"support": {
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
},
"install-path": "../squizlabs/php_codesniffer"
},
{ {
"name": "swiftmailer/swiftmailer", "name": "swiftmailer/swiftmailer",
"version": "v6.3.0", "version": "v6.3.0",
@ -9412,6 +9471,7 @@
"sebastian/resource-operations", "sebastian/resource-operations",
"sebastian/type", "sebastian/type",
"sebastian/version", "sebastian/version",
"squizlabs/php_codesniffer",
"symfony/thanks", "symfony/thanks",
"theseer/tokenizer" "theseer/tokenizer"
] ]

View file

@ -3,7 +3,7 @@
'name' => 'laravel/laravel', 'name' => 'laravel/laravel',
'pretty_version' => '2.x-dev', 'pretty_version' => '2.x-dev',
'version' => '2.9999999.9999999.9999999-dev', 'version' => '2.9999999.9999999.9999999-dev',
'reference' => 'fdd5a78eb8a90d8b9c89baba8d5f752fc93b3140', 'reference' => 'bb07ba5964270b9b986bcd4508fd4a4f5a6c2323',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -433,7 +433,7 @@
'laravel/laravel' => array( 'laravel/laravel' => array(
'pretty_version' => '2.x-dev', 'pretty_version' => '2.x-dev',
'version' => '2.9999999.9999999.9999999-dev', 'version' => '2.9999999.9999999.9999999-dev',
'reference' => 'fdd5a78eb8a90d8b9c89baba8d5f752fc93b3140', 'reference' => 'bb07ba5964270b9b986bcd4508fd4a4f5a6c2323',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -449,9 +449,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'laravel/tinker' => array( 'laravel/tinker' => array(
'pretty_version' => 'v2.7.2', 'pretty_version' => 'v2.7.3',
'version' => '2.7.2.0', 'version' => '2.7.3.0',
'reference' => 'dff39b661e827dae6e092412f976658df82dbac5', 'reference' => '5062061b4924af3392225dd482ca7b4d85d8b8ef',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../laravel/tinker', 'install_path' => __DIR__ . '/../laravel/tinker',
'aliases' => array(), 'aliases' => array(),
@ -554,9 +554,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'nette/schema' => array( 'nette/schema' => array(
'pretty_version' => 'v1.2.2', 'pretty_version' => 'v1.2.3',
'version' => '1.2.2.0', 'version' => '1.2.3.0',
'reference' => '9a39cef03a5b34c7de64f551538cbba05c2be5df', 'reference' => 'abbdbb70e0245d5f3bf77874cea1dfb0c930d06f',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../nette/schema', 'install_path' => __DIR__ . '/../nette/schema',
'aliases' => array(), 'aliases' => array(),
@ -704,9 +704,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'phpunit/php-code-coverage' => array( 'phpunit/php-code-coverage' => array(
'pretty_version' => '9.2.18', 'pretty_version' => '9.2.19',
'version' => '9.2.18.0', 'version' => '9.2.19.0',
'reference' => '12fddc491826940cf9b7e88ad9664cf51f0f6d0a', 'reference' => 'c77b56b63e3d2031bd8997fcec43c1925ae46559',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-code-coverage', 'install_path' => __DIR__ . '/../phpunit/php-code-coverage',
'aliases' => array(), 'aliases' => array(),
@ -1065,6 +1065,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => true, 'dev_requirement' => true,
), ),
'squizlabs/php_codesniffer' => array(
'pretty_version' => '3.7.1',
'version' => '3.7.1.0',
'reference' => '1359e176e9307e906dc3d890bcc9603ff6d90619',
'type' => 'library',
'install_path' => __DIR__ . '/../squizlabs/php_codesniffer',
'aliases' => array(),
'dev_requirement' => true,
),
'swiftmailer/swiftmailer' => array( 'swiftmailer/swiftmailer' => array(
'pretty_version' => 'v6.3.0', 'pretty_version' => 'v6.3.0',
'version' => '6.3.0.0', 'version' => '6.3.0.0',

View file

@ -52,6 +52,10 @@ class TinkerCommand extends Command
$this->getCasters() $this->getCasters()
); );
if ($this->option('execute')) {
$config->setRawOutput(true);
}
$shell = new Shell($config); $shell = new Shell($config);
$shell->addCommands($this->getCommands()); $shell->addCommands($this->getCommands());
$shell->setIncludes($this->argument('include')); $shell->setIncludes($this->argument('include'));

View file

@ -15,13 +15,13 @@
} }
], ],
"require": { "require": {
"php": ">=7.1 <8.2", "php": ">=7.1 <8.3",
"nette/utils": "^2.5.7 || ^3.1.5 || ^4.0" "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0"
}, },
"require-dev": { "require-dev": {
"nette/tester": "^2.3 || ^2.4", "nette/tester": "^2.3 || ^2.4",
"tracy/tracy": "^2.7", "tracy/tracy": "^2.7",
"phpstan/phpstan-nette": "^0.12" "phpstan/phpstan-nette": "^1.0"
}, },
"autoload": { "autoload": {
"classmap": ["src/"] "classmap": ["src/"]

View file

@ -21,7 +21,7 @@ Installation:
composer require nette/schema composer require nette/schema
``` ```
It requires PHP version 7.1 and supports PHP up to 8.1. It requires PHP version 7.1 and supports PHP up to 8.2.
[Support Me](https://github.com/sponsors/dg) [Support Me](https://github.com/sponsors/dg)

View file

@ -32,6 +32,7 @@ final class AnyOf implements Schema
if (!$set) { if (!$set) {
throw new Nette\InvalidStateException('The enumeration must not be empty.'); throw new Nette\InvalidStateException('The enumeration must not be empty.');
} }
$this->set = $set; $this->set = $set;
} }
@ -72,6 +73,7 @@ final class AnyOf implements Schema
unset($value[Helpers::PREVENT_MERGING]); unset($value[Helpers::PREVENT_MERGING]);
return $value; return $value;
} }
return Helpers::merge($value, $base); return Helpers::merge($value, $base);
} }
@ -88,6 +90,7 @@ final class AnyOf implements Schema
$context->warnings = array_merge($context->warnings, $dolly->warnings); $context->warnings = array_merge($context->warnings, $dolly->warnings);
return $this->doFinalize($res, $context); return $this->doFinalize($res, $context);
} }
foreach ($dolly->errors as $error) { foreach ($dolly->errors as $error) {
if ($error->path !== $context->path || empty($error->variables['expected'])) { if ($error->path !== $context->path || empty($error->variables['expected'])) {
$innerErrors[] = $error; $innerErrors[] = $error;
@ -99,6 +102,7 @@ final class AnyOf implements Schema
if ($item === $value) { if ($item === $value) {
return $this->doFinalize($value, $context); return $this->doFinalize($value, $context);
} }
$expecteds[] = Nette\Schema\Helpers::formatValue($item); $expecteds[] = Nette\Schema\Helpers::formatValue($item);
} }
} }
@ -127,9 +131,11 @@ final class AnyOf implements Schema
); );
return null; return null;
} }
if ($this->default instanceof Schema) { if ($this->default instanceof Schema) {
return $this->default->completeDefault($context); return $this->default->completeDefault($context);
} }
return $this->default; return $this->default;
} }
} }

View file

@ -65,7 +65,7 @@ trait Base
} }
public function assert(callable $handler, string $description = null): self public function assert(callable $handler, ?string $description = null): self
{ {
$this->asserts[] = [$handler, $description]; $this->asserts[] = [$handler, $description];
return $this; return $this;
@ -89,6 +89,7 @@ trait Base
); );
return null; return null;
} }
return $this->default; return $this->default;
} }
@ -98,6 +99,7 @@ trait Base
if ($this->before) { if ($this->before) {
$value = ($this->before)($value); $value = ($this->before)($value);
} }
return $value; return $value;
} }
@ -124,6 +126,7 @@ trait Base
); );
return false; return false;
} }
return true; return true;
} }
@ -145,7 +148,6 @@ trait Base
); );
return false; return false;
} }
} elseif ((is_int($value) || is_float($value)) && !self::isInRange($value, $range)) { } elseif ((is_int($value) || is_float($value)) && !self::isInRange($value, $range)) {
$context->addError( $context->addError(
'The %label% %path% expects to be in range %expected%, %value% given.', 'The %label% %path% expects to be in range %expected%, %value% given.',
@ -154,6 +156,7 @@ trait Base
); );
return false; return false;
} }
return true; return true;
} }
@ -175,6 +178,7 @@ trait Base
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
$object->$k = $v; $object->$k = $v;
} }
$value = $object; $value = $object;
} }
} }

View file

@ -105,10 +105,12 @@ final class Structure implements Schema
array_pop($context->path); array_pop($context->path);
} }
} }
if ($prevent) { if ($prevent) {
$value[Helpers::PREVENT_MERGING] = true; $value[Helpers::PREVENT_MERGING] = true;
} }
} }
return $value; return $value;
} }
@ -135,6 +137,7 @@ final class Structure implements Schema
$base[$key] = $val; $base[$key] = $val;
} }
} }
return $base; return $base;
} }
@ -184,6 +187,7 @@ final class Structure implements Schema
$value[$itemKey] = $default; $value[$itemKey] = $default;
} }
} }
array_pop($context->path); array_pop($context->path);
} }

View file

@ -42,7 +42,7 @@ final class Type implements Schema
public function __construct(string $type) public function __construct(string $type)
{ {
static $defaults = ['list' => [], 'array' => []]; $defaults = ['list' => [], 'array' => []];
$this->type = $type; $this->type = $type;
$this->default = strpos($type, '[]') ? [] : $defaults[$type] ?? null; $this->default = strpos($type, '[]') ? [] : $defaults[$type] ?? null;
} }
@ -129,11 +129,14 @@ final class Type implements Schema
$res[$key] = $this->itemsValue->normalize($val, $context); $res[$key] = $this->itemsValue->normalize($val, $context);
array_pop($context->path); array_pop($context->path);
} }
$value = $res; $value = $res;
} }
if ($prevent && is_array($value)) { if ($prevent && is_array($value)) {
$value[Helpers::PREVENT_MERGING] = true; $value[Helpers::PREVENT_MERGING] = true;
} }
return $value; return $value;
} }
@ -144,6 +147,7 @@ final class Type implements Schema
unset($value[Helpers::PREVENT_MERGING]); unset($value[Helpers::PREVENT_MERGING]);
return $value; return $value;
} }
if (is_array($value) && is_array($base) && $this->itemsValue) { if (is_array($value) && is_array($base) && $this->itemsValue) {
$index = 0; $index = 0;
foreach ($value as $key => $val) { foreach ($value as $key => $val) {
@ -156,6 +160,7 @@ final class Type implements Schema
: $val; : $val;
} }
} }
return $base; return $base;
} }
@ -208,15 +213,18 @@ final class Type implements Schema
$res[$key] = $this->itemsValue->complete($val, $context); $res[$key] = $this->itemsValue->complete($val, $context);
array_pop($context->path); array_pop($context->path);
} }
if (count($context->errors) > $errCount) { if (count($context->errors) > $errCount) {
return null; return null;
} }
$value = $res; $value = $res;
} }
if ($merge) { if ($merge) {
$value = Helpers::merge($value, $this->default); $value = Helpers::merge($value, $this->default);
} }
return $this->doFinalize($value, $context); return $this->doFinalize($value, $context);
} }
} }

View file

@ -40,6 +40,7 @@ final class Expect
if ($args) { if ($args) {
$type->default($args[0]); $type->default($args[0]);
} }
return $type; return $type;
} }
@ -93,6 +94,7 @@ final class Expect
} }
} }
} }
return (new Structure($items))->castTo($ro->getName()); return (new Structure($items))->castTo($ro->getName());
} }

View file

@ -44,6 +44,7 @@ final class Helpers
$base[$key] = static::merge($val, $base[$key] ?? null); $base[$key] = static::merge($val, $base[$key] ?? null);
} }
} }
return $base; return $base;
} elseif ($value === null && is_array($base)) { } elseif ($value === null && is_array($base)) {
@ -67,6 +68,7 @@ final class Helpers
return Reflection::expandClassName($m[0], $class); return Reflection::expandClassName($m[0], $class);
}, $type); }, $type);
} }
return null; return null;
} }
@ -80,10 +82,12 @@ final class Helpers
if (!Reflection::areCommentsAvailable()) { if (!Reflection::areCommentsAvailable()) {
throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.'); throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
} }
$re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#'; $re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#';
if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) { if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) {
return $m[1] ?? ''; return $m[1] ?? '';
} }
return null; return null;
} }

View file

@ -66,7 +66,9 @@ final class Message
{ {
$vars = $this->variables; $vars = $this->variables;
$vars['label'] = empty($vars['isKey']) ? 'item' : 'key of item'; $vars['label'] = empty($vars['isKey']) ? 'item' : 'key of item';
$vars['path'] = $this->path ? "'" . implode('  ', $this->path) . "'" : null; $vars['path'] = $this->path
? "'" . implode("\u{a0}\u{a0}", $this->path) . "'"
: null;
$vars['value'] = Helpers::formatValue($vars['value'] ?? null); $vars['value'] = Helpers::formatValue($vars['value'] ?? null);
return preg_replace_callback('~( ?)%(\w+)%~', function ($m) use ($vars) { return preg_replace_callback('~( ?)%(\w+)%~', function ($m) use ($vars) {

View file

@ -67,6 +67,7 @@ final class Processor
$flatten = $first ? $data : $schema->merge($data, $flatten); $flatten = $first ? $data : $schema->merge($data, $flatten);
$first = false; $first = false;
} }
$data = $schema->complete($flatten, $this->context); $data = $schema->complete($flatten, $this->context);
$this->throwsErrors(); $this->throwsErrors();
return $data; return $data;
@ -82,6 +83,7 @@ final class Processor
foreach ($this->context->warnings as $message) { foreach ($this->context->warnings as $message) {
$res[] = $message->toString(); $res[] = $message->toString();
} }
return $res; return $res;
} }

View file

@ -40,6 +40,7 @@ class ValidationException extends Nette\InvalidStateException
foreach ($this->messages as $message) { foreach ($this->messages as $message) {
$res[] = $message->toString(); $res[] = $message->toString();
} }
return $res; return $res;
} }

View file

@ -2,6 +2,17 @@
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [9.2.19] - 2022-11-18
### Fixed
* [#949](https://github.com/sebastianbergmann/php-code-coverage/pull/949): Various issues related to identifying executable lines
### Changed
* Tweaked CSS for HTML report
* Updated bundled CSS/JavaScript components used for HTML report: Bootstrap 4.6.2 and jQuery 3.6.1
## [9.2.18] - 2022-10-27 ## [9.2.18] - 2022-10-27
### Fixed ### Fixed
@ -422,6 +433,7 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt
* This component is no longer supported on PHP 7.1 * This component is no longer supported on PHP 7.1
[9.2.19]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.18...9.2.19
[9.2.18]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.17...9.2.18 [9.2.18]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.17...9.2.18
[9.2.17]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.16...9.2.17 [9.2.17]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.16...9.2.17
[9.2.16]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.15...9.2.16 [9.2.16]: https://github.com/sebastianbergmann/php-code-coverage/compare/9.2.15...9.2.16

View file

@ -15,8 +15,12 @@ use function array_flip;
use function array_intersect; use function array_intersect;
use function array_intersect_key; use function array_intersect_key;
use function count; use function count;
use function explode;
use function file_get_contents;
use function in_array; use function in_array;
use function is_file;
use function range; use function range;
use function trim;
use SebastianBergmann\CodeCoverage\Driver\Driver; use SebastianBergmann\CodeCoverage\Driver\Driver;
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;

View file

@ -22,7 +22,7 @@ final class Version
public static function id(): string public static function id(): string
{ {
if (self::$version === null) { if (self::$version === null) {
self::$version = (new VersionId('9.2.18', dirname(__DIR__)))->getVersion(); self::$version = (new VersionId('9.2.19', dirname(__DIR__)))->getVersion();
} }
return self::$version; return self::$version;

View file

@ -0,0 +1,13 @@
Contributing
-------------
Before you contribute code to PHP\_CodeSniffer, please make sure it conforms to the PHPCS coding standard and that the PHP\_CodeSniffer unit tests still pass. The easiest way to contribute is to work on a checkout of the repository, or your own fork, rather than an installed PEAR version. If you do this, you can run the following commands to check if everything is ready to submit:
cd PHP_CodeSniffer
php bin/phpcs
Which should display no coding standard errors. And then:
phpunit
Which should give you no failures or errors. You can ignore any skipped tests as these are for external tools.

View file

@ -0,0 +1,9 @@
<?php
$phpCodeSnifferConfig = array (
'default_standard' => 'PSR2',
'report_format' => 'summary',
'show_warnings' => '0',
'show_progress' => '1',
'report_width' => '120',
);
?>

View file

@ -0,0 +1,134 @@
## About
PHP_CodeSniffer is a set of two PHP scripts; the main `phpcs` script that tokenizes PHP, JavaScript and CSS files to detect violations of a defined coding standard, and a second `phpcbf` script to automatically correct coding standard violations. PHP_CodeSniffer is an essential development tool that ensures your code remains clean and consistent.
[![Build Status](https://github.com/squizlabs/PHP_CodeSniffer/workflows/Validate/badge.svg?branch=master)](https://github.com/squizlabs/PHP_CodeSniffer/actions)
[![Build Status](https://github.com/squizlabs/PHP_CodeSniffer/workflows/Test/badge.svg?branch=master)](https://github.com/squizlabs/PHP_CodeSniffer/actions)
[![Code consistency](http://squizlabs.github.io/PHP_CodeSniffer/analysis/squizlabs/PHP_CodeSniffer/grade.svg)](http://squizlabs.github.io/PHP_CodeSniffer/analysis/squizlabs/PHP_CodeSniffer)
[![Join the chat at https://gitter.im/squizlabs/PHP_CodeSniffer](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/squizlabs/PHP_CodeSniffer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Requirements
PHP_CodeSniffer requires PHP version 5.4.0 or greater, although individual sniffs may have additional requirements such as external applications and scripts. See the [Configuration Options manual page](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options) for a list of these requirements.
If you're using PHP_CodeSniffer as part of a team, or you're running it on a [CI](https://en.wikipedia.org/wiki/Continuous_integration) server, you may want to configure your project's settings [using a configuration file](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Advanced-Usage#using-a-default-configuration-file).
## Installation
The easiest way to get started with PHP_CodeSniffer is to download the Phar files for each of the commands:
```
# Download using curl
curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcbf.phar
# Or download using wget
wget https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
wget https://squizlabs.github.io/PHP_CodeSniffer/phpcbf.phar
# Then test the downloaded PHARs
php phpcs.phar -h
php phpcbf.phar -h
```
### Composer
If you use Composer, you can install PHP_CodeSniffer system-wide with the following command:
```bash
composer global require "squizlabs/php_codesniffer=*"
```
Make sure you have the composer bin dir in your PATH. The default value is `~/.composer/vendor/bin/`, but you can check the value that you need to use by running `composer global config bin-dir --absolute`.
Or alternatively, include a dependency for `squizlabs/php_codesniffer` in your `composer.json` file. For example:
```json
{
"require-dev": {
"squizlabs/php_codesniffer": "3.*"
}
}
```
You will then be able to run PHP_CodeSniffer from the vendor bin directory:
```bash
./vendor/bin/phpcs -h
./vendor/bin/phpcbf -h
```
### Phive
If you use Phive, you can install PHP_CodeSniffer as a project tool using the following commands:
```bash
phive install phpcs
phive install phpcbf
```
You will then be able to run PHP_CodeSniffer from the tools directory:
```bash
./tools/phpcs -h
./tools/phpcbf -h
```
### PEAR
If you use PEAR, you can install PHP_CodeSniffer using the PEAR installer. This will make the `phpcs` and `phpcbf` commands immediately available for use. To install PHP_CodeSniffer using the PEAR installer, first ensure you have [installed PEAR](http://pear.php.net/manual/en/installation.getting.php) and then run the following command:
```bash
pear install PHP_CodeSniffer
```
### Git Clone
You can also download the PHP_CodeSniffer source and run the `phpcs` and `phpcbf` commands directly from the Git clone:
```bash
git clone https://github.com/squizlabs/PHP_CodeSniffer.git
cd PHP_CodeSniffer
php bin/phpcs -h
php bin/phpcbf -h
```
## Getting Started
The default coding standard used by PHP_CodeSniffer is the PEAR coding standard. To check a file against the PEAR coding standard, simply specify the file's location:
```bash
phpcs /path/to/code/myfile.php
```
Or if you wish to check an entire directory you can specify the directory location instead of a file.
```bash
phpcs /path/to/code-directory
```
If you wish to check your code against the PSR-12 coding standard, use the `--standard` command line argument:
```bash
phpcs --standard=PSR12 /path/to/code-directory
```
If PHP_CodeSniffer finds any coding standard errors, a report will be shown after running the command.
Full usage information and example reports are available on the [usage page](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage).
## Documentation
The documentation for PHP_CodeSniffer is available on the [Github wiki](https://github.com/squizlabs/PHP_CodeSniffer/wiki).
## Issues
Bug reports and feature requests can be submitted on the [Github Issue Tracker](https://github.com/squizlabs/PHP_CodeSniffer/issues).
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for information.
## Versioning
PHP_CodeSniffer uses a `MAJOR.MINOR.PATCH` version number format.
The `MAJOR` version is incremented when:
- backwards-incompatible changes are made to how the `phpcs` or `phpcbf` commands are used, or
- backwards-incompatible changes are made to the `ruleset.xml` format, or
- backwards-incompatible changes are made to the API used by sniff developers, or
- custom PHP_CodeSniffer token types are removed, or
- existing sniffs are removed from PHP_CodeSniffer entirely
The `MINOR` version is incremented when:
- new backwards-compatible features are added to the `phpcs` and `phpcbf` commands, or
- backwards-compatible changes are made to the `ruleset.xml` format, or
- backwards-compatible changes are made to the API used by sniff developers, or
- new sniffs are added to an included standard, or
- existing sniffs are removed from an included standard
> NOTE: Backwards-compatible changes to the API used by sniff developers will allow an existing sniff to continue running without producing fatal errors but may not result in the sniff reporting the same errors as it did previously without changes being required.
The `PATCH` version is incremented when:
- backwards-compatible bug fixes are made
> NOTE: As PHP_CodeSniffer exists to report and fix issues, most bugs are the result of coding standard errors being incorrectly reported or coding standard errors not being reported when they should be. This means that the messages produced by PHP_CodeSniffer, and the fixes it makes, are likely to be different between PATCH versions.

View file

@ -0,0 +1,342 @@
<?php
/**
* Autoloads files for PHP_CodeSniffer and tracks what has been loaded.
*
* Due to different namespaces being used for custom coding standards,
* the autoloader keeps track of what class is loaded after a file is included,
* even if the file is ultimately included by another autoloader (such as composer).
*
* This allows PHP_CodeSniffer to request the class name after loading a class
* when it only knows the filename, without having to parse the file to find it.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer;
if (class_exists('PHP_CodeSniffer\Autoload', false) === false) {
class Autoload
{
/**
* The composer autoloader.
*
* @var \Composer\Autoload\ClassLoader
*/
private static $composerAutoloader = null;
/**
* A mapping of file names to class names.
*
* @var array<string, string>
*/
private static $loadedClasses = [];
/**
* A mapping of class names to file names.
*
* @var array<string, string>
*/
private static $loadedFiles = [];
/**
* A list of additional directories to search during autoloading.
*
* This is typically a list of coding standard directories.
*
* @var string[]
*/
private static $searchPaths = [];
/**
* Loads a class.
*
* This method only loads classes that exist in the PHP_CodeSniffer namespace.
* All other classes are ignored and loaded by subsequent autoloaders.
*
* @param string $class The name of the class to load.
*
* @return bool
*/
public static function load($class)
{
// Include the composer autoloader if there is one, but re-register it
// so this autoloader runs before the composer one as we need to include
// all files so we can figure out what the class/interface/trait name is.
if (self::$composerAutoloader === null) {
// Make sure we don't try to load any of Composer's classes
// while the autoloader is being setup.
if (strpos($class, 'Composer\\') === 0) {
return;
}
if (strpos(__DIR__, 'phar://') !== 0
&& @file_exists(__DIR__.'/../../autoload.php') === true
) {
self::$composerAutoloader = include __DIR__.'/../../autoload.php';
if (self::$composerAutoloader instanceof \Composer\Autoload\ClassLoader) {
self::$composerAutoloader->unregister();
self::$composerAutoloader->register();
} else {
// Something went wrong, so keep going without the autoloader
// although namespaced sniffs might error.
self::$composerAutoloader = false;
}
} else {
self::$composerAutoloader = false;
}
}//end if
$ds = DIRECTORY_SEPARATOR;
$path = false;
if (substr($class, 0, 16) === 'PHP_CodeSniffer\\') {
if (substr($class, 0, 22) === 'PHP_CodeSniffer\Tests\\') {
$isInstalled = !is_dir(__DIR__.$ds.'tests');
if ($isInstalled === false) {
$path = __DIR__.$ds.'tests';
} else {
$path = '@test_dir@'.$ds.'PHP_CodeSniffer'.$ds.'CodeSniffer';
}
$path .= $ds.substr(str_replace('\\', $ds, $class), 22).'.php';
} else {
$path = __DIR__.$ds.'src'.$ds.substr(str_replace('\\', $ds, $class), 16).'.php';
}
}
// See if the composer autoloader knows where the class is.
if ($path === false && self::$composerAutoloader !== false) {
$path = self::$composerAutoloader->findFile($class);
}
// See if the class is inside one of our alternate search paths.
if ($path === false) {
foreach (self::$searchPaths as $searchPath => $nsPrefix) {
$className = $class;
if ($nsPrefix !== '' && substr($class, 0, strlen($nsPrefix)) === $nsPrefix) {
$className = substr($class, (strlen($nsPrefix) + 1));
}
$path = $searchPath.$ds.str_replace('\\', $ds, $className).'.php';
if (is_file($path) === true) {
break;
}
$path = false;
}
}
if ($path !== false && is_file($path) === true) {
self::loadFile($path);
return true;
}
return false;
}//end load()
/**
* Includes a file and tracks what class or interface was loaded as a result.
*
* @param string $path The path of the file to load.
*
* @return string The fully qualified name of the class in the loaded file.
*/
public static function loadFile($path)
{
if (strpos(__DIR__, 'phar://') !== 0) {
$path = realpath($path);
if ($path === false) {
return false;
}
}
if (isset(self::$loadedClasses[$path]) === true) {
return self::$loadedClasses[$path];
}
$classesBeforeLoad = [
'classes' => get_declared_classes(),
'interfaces' => get_declared_interfaces(),
'traits' => get_declared_traits(),
];
include $path;
$classesAfterLoad = [
'classes' => get_declared_classes(),
'interfaces' => get_declared_interfaces(),
'traits' => get_declared_traits(),
];
$className = self::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
self::$loadedClasses[$path] = $className;
self::$loadedFiles[$className] = $path;
return self::$loadedClasses[$path];
}//end loadFile()
/**
* Determine which class was loaded based on the before and after lists of loaded classes.
*
* @param array $classesBeforeLoad The classes/interfaces/traits before the file was included.
* @param array $classesAfterLoad The classes/interfaces/traits after the file was included.
*
* @return string The fully qualified name of the class in the loaded file.
*/
public static function determineLoadedClass($classesBeforeLoad, $classesAfterLoad)
{
$className = null;
$newClasses = array_diff($classesAfterLoad['classes'], $classesBeforeLoad['classes']);
if (PHP_VERSION_ID < 70400) {
$newClasses = array_reverse($newClasses);
}
// Since PHP 7.4 get_declared_classes() does not guarantee any order, making
// it impossible to use order to determine which is the parent an which is the child.
// Let's reduce the list of candidates by removing all the classes known to be "parents".
// That way, at the end, only the "main" class just included will remain.
$newClasses = array_reduce(
$newClasses,
function ($remaining, $current) {
return array_diff($remaining, class_parents($current));
},
$newClasses
);
foreach ($newClasses as $name) {
if (isset(self::$loadedFiles[$name]) === false) {
$className = $name;
break;
}
}
if ($className === null) {
$newClasses = array_reverse(array_diff($classesAfterLoad['interfaces'], $classesBeforeLoad['interfaces']));
foreach ($newClasses as $name) {
if (isset(self::$loadedFiles[$name]) === false) {
$className = $name;
break;
}
}
}
if ($className === null) {
$newClasses = array_reverse(array_diff($classesAfterLoad['traits'], $classesBeforeLoad['traits']));
foreach ($newClasses as $name) {
if (isset(self::$loadedFiles[$name]) === false) {
$className = $name;
break;
}
}
}
return $className;
}//end determineLoadedClass()
/**
* Adds a directory to search during autoloading.
*
* @param string $path The path to the directory to search.
* @param string $nsPrefix The namespace prefix used by files under this path.
*
* @return void
*/
public static function addSearchPath($path, $nsPrefix='')
{
self::$searchPaths[$path] = rtrim(trim((string) $nsPrefix), '\\');
}//end addSearchPath()
/**
* Retrieve the namespaces and paths registered by external standards.
*
* @return array
*/
public static function getSearchPaths()
{
return self::$searchPaths;
}//end getSearchPaths()
/**
* Gets the class name for the given file path.
*
* @param string $path The name of the file.
*
* @throws \Exception If the file path has not been loaded.
* @return string
*/
public static function getLoadedClassName($path)
{
if (isset(self::$loadedClasses[$path]) === false) {
throw new \Exception("Cannot get class name for $path; file has not been included");
}
return self::$loadedClasses[$path];
}//end getLoadedClassName()
/**
* Gets the file path for the given class name.
*
* @param string $class The name of the class.
*
* @throws \Exception If the class name has not been loaded
* @return string
*/
public static function getLoadedFileName($class)
{
if (isset(self::$loadedFiles[$class]) === false) {
throw new \Exception("Cannot get file name for $class; class has not been included");
}
return self::$loadedFiles[$class];
}//end getLoadedFileName()
/**
* Gets the mapping of file names to class names.
*
* @return array<string, string>
*/
public static function getLoadedClasses()
{
return self::$loadedClasses;
}//end getLoadedClasses()
/**
* Gets the mapping of class names to file names.
*
* @return array<string, string>
*/
public static function getLoadedFiles()
{
return self::$loadedFiles;
}//end getLoadedFiles()
}//end class
// Register the autoloader before any existing autoloaders to ensure
// it gets a chance to hear about every autoload request, and record
// the file and class name for it.
spl_autoload_register(__NAMESPACE__.'\Autoload::load', true, true);
}//end if

19
vendor/squizlabs/php_codesniffer/bin/phpcbf vendored Executable file
View file

@ -0,0 +1,19 @@
#!/usr/bin/env php
<?php
/**
* PHP Code Beautifier and Fixer fixes violations of a defined coding standard.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
if (is_file(__DIR__.'/../autoload.php') === true) {
include_once __DIR__.'/../autoload.php';
} else {
include_once 'PHP/CodeSniffer/autoload.php';
}
$runner = new PHP_CodeSniffer\Runner();
$exitCode = $runner->runPHPCBF();
exit($exitCode);

View file

@ -0,0 +1,12 @@
@echo off
REM PHP Code Beautifier and Fixer fixes violations of a defined coding standard.
REM
REM @author Greg Sherwood <gsherwood@squiz.net>
REM @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
REM @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
if "%PHP_PEAR_PHP_BIN%" neq "" (
set PHPBIN=%PHP_PEAR_PHP_BIN%
) else set PHPBIN=php
"%PHPBIN%" "%~dp0\phpcbf" %*

19
vendor/squizlabs/php_codesniffer/bin/phpcs vendored Executable file
View file

@ -0,0 +1,19 @@
#!/usr/bin/env php
<?php
/**
* PHP_CodeSniffer detects violations of a defined coding standard.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
if (is_file(__DIR__.'/../autoload.php') === true) {
include_once __DIR__.'/../autoload.php';
} else {
include_once 'PHP/CodeSniffer/autoload.php';
}
$runner = new PHP_CodeSniffer\Runner();
$exitCode = $runner->runPHPCS();
exit($exitCode);

View file

@ -0,0 +1,12 @@
@echo off
REM PHP_CodeSniffer detects violations of a defined coding standard.
REM
REM @author Greg Sherwood <gsherwood@squiz.net>
REM @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
REM @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
if "%PHP_PEAR_PHP_BIN%" neq "" (
set PHPBIN=%PHP_PEAR_PHP_BIN%
) else set PHPBIN=php
"%PHPBIN%" "%~dp0\phpcs" %*

View file

@ -0,0 +1,40 @@
{
"name": "squizlabs/php_codesniffer",
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
"type": "library",
"keywords": [
"phpcs",
"standards"
],
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Greg Sherwood",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki",
"source": "https://github.com/squizlabs/PHP_CodeSniffer"
},
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"require": {
"php": ">=5.4.0",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"ext-simplexml": "*"
},
"require-dev": {
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"bin": [
"bin/phpcs",
"bin/phpcbf"
]
}

View file

@ -0,0 +1,24 @@
Copyright (c) 2012, Squiz Pty Ltd (ABN 77 084 670 600)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" attributeFormDefault="unqualified" elementFormDefault="qualified">
<xs:element name="ruleset">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="description" type="xs:string" maxOccurs="1" minOccurs="0"></xs:element>
<xs:element name="config" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute name="name" type="xs:string" use="required"></xs:attribute>
<xs:attribute name="value" type="xs:string" use="required"></xs:attribute>
<xs:attributeGroup ref="applySelectively"/>
</xs:complexType>
</xs:element>
<xs:element name="file" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attributeGroup ref="applySelectively"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="exclude-pattern" type="patternType" maxOccurs="unbounded" minOccurs="0"></xs:element>
<xs:element name="arg" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute name="name" type="xs:string"></xs:attribute>
<xs:attribute name="value" type="xs:string"></xs:attribute>
<xs:attributeGroup ref="applySelectively"/>
</xs:complexType>
</xs:element>
<xs:element name="ini" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute name="name" type="xs:string" use="required"></xs:attribute>
<xs:attribute name="value" type="xs:string" use="required"></xs:attribute>
<xs:attributeGroup ref="applySelectively"/>
</xs:complexType>
</xs:element>
<xs:element name="autoload" type="xs:string" maxOccurs="unbounded" minOccurs="0"></xs:element>
<xs:element name="rule" type="ruleType" maxOccurs="unbounded" minOccurs="0"></xs:element>
</xs:choice>
<xs:attribute name="name" type="xs:string"></xs:attribute>
<xs:attribute name="namespace" type="xs:string"></xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType name="ruleType">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="exclude" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute name="name" type="xs:string" use="required"></xs:attribute>
<xs:attributeGroup ref="applySelectively"/>
</xs:complexType>
</xs:element>
<xs:element name="message" type="xs:string" maxOccurs="1" minOccurs="0"></xs:element>
<xs:element name="severity" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:integer">
<xs:attributeGroup ref="applySelectively"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="type" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="messageType">
<xs:attributeGroup ref="applySelectively"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="exclude-pattern" type="patternType" maxOccurs="unbounded" minOccurs="0"></xs:element>
<xs:element name="include-pattern" type="patternType" maxOccurs="unbounded" minOccurs="0"></xs:element>
<xs:element name="properties" type="propertiesType" maxOccurs="1" minOccurs="0"></xs:element>
</xs:choice>
<xs:attribute name="ref" type="xs:string" use="required"></xs:attribute>
<xs:attributeGroup ref="applySelectively"/>
</xs:complexType>
<xs:complexType name="patternType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="type">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="relative"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attributeGroup ref="applySelectively"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="propertiesType">
<xs:sequence>
<xs:element name="property" maxOccurs="unbounded" minOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="element" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute name="key" type="xs:string"></xs:attribute>
<xs:attribute name="value" type="xs:string" use="required"></xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="type">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="array"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="name" type="xs:string" use="required"></xs:attribute>
<xs:attribute name="value" type="xs:string"></xs:attribute>
<xs:attribute name="extend" type="xs:boolean" default="false"/>
<xs:attributeGroup ref="applySelectively"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="messageType">
<xs:restriction base="xs:string">
<xs:enumeration value="error"></xs:enumeration>
<xs:enumeration value="warning"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:attributeGroup name="applySelectively">
<xs:attribute name="phpcs-only" type="xs:boolean" default="false"/>
<xs:attribute name="phpcbf-only" type="xs:boolean" default="false"/>
</xs:attributeGroup>
</xs:schema>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
<?php
/**
* An exception thrown by PHP_CodeSniffer when it wants to exit from somewhere not in the main runner.
*
* Allows the runner to return an exit code instead of putting exit codes elsewhere
* in the source code.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Exceptions;
class DeepExitException extends \Exception
{
}//end class

View file

@ -0,0 +1,15 @@
<?php
/**
* An exception thrown by PHP_CodeSniffer when it encounters an unrecoverable error.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Exceptions;
class RuntimeException extends \RuntimeException
{
}//end class

View file

@ -0,0 +1,15 @@
<?php
/**
* An exception thrown by PHP_CodeSniffer when it encounters an unrecoverable tokenizer error.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Exceptions;
class TokenizerException extends \Exception
{
}//end class

View file

@ -0,0 +1,82 @@
<?php
/**
* A dummy file represents a chunk of text that does not have a file system location.
*
* Dummy files can also represent a changed (but not saved) version of a file
* and so can have a file path either set manually, or set by putting
* phpcs_input_file: /path/to/file
* as the first line of the file contents.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Files;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Config;
class DummyFile extends File
{
/**
* Creates a DummyFile object and sets the content.
*
* @param string $content The content of the file.
* @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
* @param \PHP_CodeSniffer\Config $config The config data for the run.
*
* @return void
*/
public function __construct($content, Ruleset $ruleset, Config $config)
{
$this->setContent($content);
// See if a filename was defined in the content.
// This is done by including: phpcs_input_file: [file path]
// as the first line of content.
$path = 'STDIN';
if ($content !== '') {
if (substr($content, 0, 17) === 'phpcs_input_file:') {
$eolPos = strpos($content, $this->eolChar);
$filename = trim(substr($content, 17, ($eolPos - 17)));
$content = substr($content, ($eolPos + strlen($this->eolChar)));
$path = $filename;
$this->setContent($content);
}
}
// The CLI arg overrides anything passed in the content.
if ($config->stdinPath !== null) {
$path = $config->stdinPath;
}
parent::__construct($path, $ruleset, $config);
}//end __construct()
/**
* Set the error, warning, and fixable counts for the file.
*
* @param int $errorCount The number of errors found.
* @param int $warningCount The number of warnings found.
* @param int $fixableCount The number of fixable errors found.
* @param int $fixedCount The number of errors that were fixed.
*
* @return void
*/
public function setErrorCounts($errorCount, $warningCount, $fixableCount, $fixedCount)
{
$this->errorCount = $errorCount;
$this->warningCount = $warningCount;
$this->fixableCount = $fixableCount;
$this->fixedCount = $fixedCount;
}//end setErrorCounts()
}//end class

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,255 @@
<?php
/**
* Represents a list of files on the file system that are to be checked during the run.
*
* File objects are created as needed rather than all at once.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Files;
use PHP_CodeSniffer\Autoload;
use PHP_CodeSniffer\Util;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Exceptions\DeepExitException;
use ReturnTypeWillChange;
class FileList implements \Iterator, \Countable
{
/**
* A list of file paths that are included in the list.
*
* @var array
*/
private $files = [];
/**
* The number of files in the list.
*
* @var integer
*/
private $numFiles = 0;
/**
* The config data for the run.
*
* @var \PHP_CodeSniffer\Config
*/
public $config = null;
/**
* The ruleset used for the run.
*
* @var \PHP_CodeSniffer\Ruleset
*/
public $ruleset = null;
/**
* An array of patterns to use for skipping files.
*
* @var array
*/
protected $ignorePatterns = [];
/**
* Constructs a file list and loads in an array of file paths to process.
*
* @param \PHP_CodeSniffer\Config $config The config data for the run.
* @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
*
* @return void
*/
public function __construct(Config $config, Ruleset $ruleset)
{
$this->ruleset = $ruleset;
$this->config = $config;
$paths = $config->files;
foreach ($paths as $path) {
$isPharFile = Util\Common::isPharFile($path);
if (is_dir($path) === true || $isPharFile === true) {
if ($isPharFile === true) {
$path = 'phar://'.$path;
}
$filterClass = $this->getFilterClass();
$di = new \RecursiveDirectoryIterator($path, (\RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS));
$filter = new $filterClass($di, $path, $config, $ruleset);
$iterator = new \RecursiveIteratorIterator($filter);
foreach ($iterator as $file) {
$this->files[$file->getPathname()] = null;
$this->numFiles++;
}
} else {
$this->addFile($path);
}//end if
}//end foreach
reset($this->files);
}//end __construct()
/**
* Add a file to the list.
*
* If a file object has already been created, it can be passed here.
* If it is left NULL, it will be created when accessed.
*
* @param string $path The path to the file being added.
* @param \PHP_CodeSniffer\Files\File $file The file being added.
*
* @return void
*/
public function addFile($path, $file=null)
{
// No filtering is done for STDIN when the filename
// has not been specified.
if ($path === 'STDIN') {
$this->files[$path] = $file;
$this->numFiles++;
return;
}
$filterClass = $this->getFilterClass();
$di = new \RecursiveArrayIterator([$path]);
$filter = new $filterClass($di, $path, $this->config, $this->ruleset);
$iterator = new \RecursiveIteratorIterator($filter);
foreach ($iterator as $path) {
$this->files[$path] = $file;
$this->numFiles++;
}
}//end addFile()
/**
* Get the class name of the filter being used for the run.
*
* @return string
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the specified filter could not be found.
*/
private function getFilterClass()
{
$filterType = $this->config->filter;
if ($filterType === null) {
$filterClass = '\PHP_CodeSniffer\Filters\Filter';
} else {
if (strpos($filterType, '.') !== false) {
// This is a path to a custom filter class.
$filename = realpath($filterType);
if ($filename === false) {
$error = "ERROR: Custom filter \"$filterType\" not found".PHP_EOL;
throw new DeepExitException($error, 3);
}
$filterClass = Autoload::loadFile($filename);
} else {
$filterClass = '\PHP_CodeSniffer\Filters\\'.$filterType;
}
}
return $filterClass;
}//end getFilterClass()
/**
* Rewind the iterator to the first file.
*
* @return void
*/
#[ReturnTypeWillChange]
public function rewind()
{
reset($this->files);
}//end rewind()
/**
* Get the file that is currently being processed.
*
* @return \PHP_CodeSniffer\Files\File
*/
#[ReturnTypeWillChange]
public function current()
{
$path = key($this->files);
if (isset($this->files[$path]) === false) {
$this->files[$path] = new LocalFile($path, $this->ruleset, $this->config);
}
return $this->files[$path];
}//end current()
/**
* Return the file path of the current file being processed.
*
* @return void
*/
#[ReturnTypeWillChange]
public function key()
{
return key($this->files);
}//end key()
/**
* Move forward to the next file.
*
* @return void
*/
#[ReturnTypeWillChange]
public function next()
{
next($this->files);
}//end next()
/**
* Checks if current position is valid.
*
* @return boolean
*/
#[ReturnTypeWillChange]
public function valid()
{
if (current($this->files) === false) {
return false;
}
return true;
}//end valid()
/**
* Return the number of files in the list.
*
* @return integer
*/
#[ReturnTypeWillChange]
public function count()
{
return $this->numFiles;
}//end count()
}//end class

View file

@ -0,0 +1,219 @@
<?php
/**
* A local file represents a chunk of text has a file system location.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Files;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Util\Cache;
use PHP_CodeSniffer\Util\Common;
class LocalFile extends File
{
/**
* Creates a LocalFile object and sets the content.
*
* @param string $path The absolute path to the file.
* @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
* @param \PHP_CodeSniffer\Config $config The config data for the run.
*
* @return void
*/
public function __construct($path, Ruleset $ruleset, Config $config)
{
$this->path = trim($path);
if (Common::isReadable($this->path) === false) {
parent::__construct($this->path, $ruleset, $config);
$error = 'Error opening file; file no longer exists or you do not have access to read the file';
$this->addMessage(true, $error, 1, 1, 'Internal.LocalFile', [], 5, false);
$this->ignored = true;
return;
}
// Before we go and spend time tokenizing this file, just check
// to see if there is a tag up top to indicate that the whole
// file should be ignored. It must be on one of the first two lines.
if ($config->annotations === true) {
$handle = fopen($this->path, 'r');
if ($handle !== false) {
$firstContent = fgets($handle);
$firstContent .= fgets($handle);
fclose($handle);
if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false
|| stripos($firstContent, 'phpcs:ignorefile') !== false
) {
// We are ignoring the whole file.
$this->ignored = true;
return;
}
}
}
$this->reloadContent();
parent::__construct($this->path, $ruleset, $config);
}//end __construct()
/**
* Loads the latest version of the file's content from the file system.
*
* @return void
*/
public function reloadContent()
{
$this->setContent(file_get_contents($this->path));
}//end reloadContent()
/**
* Processes the file.
*
* @return void
*/
public function process()
{
if ($this->ignored === true) {
return;
}
if ($this->configCache['cache'] === false) {
parent::process();
return;
}
$hash = md5_file($this->path);
$hash .= fileperms($this->path);
$cache = Cache::get($this->path);
if ($cache !== false && $cache['hash'] === $hash) {
// We can't filter metrics, so just load all of them.
$this->metrics = $cache['metrics'];
if ($this->configCache['recordErrors'] === true) {
// Replay the cached errors and warnings to filter out the ones
// we don't need for this specific run.
$this->configCache['cache'] = false;
$this->replayErrors($cache['errors'], $cache['warnings']);
$this->configCache['cache'] = true;
} else {
$this->errorCount = $cache['errorCount'];
$this->warningCount = $cache['warningCount'];
$this->fixableCount = $cache['fixableCount'];
}
if (PHP_CODESNIFFER_VERBOSITY > 0
|| (PHP_CODESNIFFER_CBF === true && empty($this->config->files) === false)
) {
echo "[loaded from cache]... ";
}
$this->numTokens = $cache['numTokens'];
$this->fromCache = true;
return;
}//end if
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo PHP_EOL;
}
parent::process();
$cache = [
'hash' => $hash,
'errors' => $this->errors,
'warnings' => $this->warnings,
'metrics' => $this->metrics,
'errorCount' => $this->errorCount,
'warningCount' => $this->warningCount,
'fixableCount' => $this->fixableCount,
'numTokens' => $this->numTokens,
];
Cache::set($this->path, $cache);
// During caching, we don't filter out errors in any way, so
// we need to do that manually now by replaying them.
if ($this->configCache['recordErrors'] === true) {
$this->configCache['cache'] = false;
$this->replayErrors($this->errors, $this->warnings);
$this->configCache['cache'] = true;
}
}//end process()
/**
* Clears and replays error and warnings for the file.
*
* Replaying errors and warnings allows for filtering rules to be changed
* and then errors and warnings to be reapplied with the new rules. This is
* particularly useful while caching.
*
* @param array $errors The list of errors to replay.
* @param array $warnings The list of warnings to replay.
*
* @return void
*/
private function replayErrors($errors, $warnings)
{
$this->errors = [];
$this->warnings = [];
$this->errorCount = 0;
$this->warningCount = 0;
$this->fixableCount = 0;
$this->replayingErrors = true;
foreach ($errors as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$this->activeListener = $error['listener'];
$this->addMessage(
true,
$error['message'],
$line,
$column,
$error['source'],
[],
$error['severity'],
$error['fixable']
);
}
}
}
foreach ($warnings as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$this->activeListener = $error['listener'];
$this->addMessage(
false,
$error['message'],
$line,
$column,
$error['source'],
[],
$error['severity'],
$error['fixable']
);
}
}
}
$this->replayingErrors = false;
}//end replayErrors()
}//end class

View file

@ -0,0 +1,108 @@
<?php
/**
* An abstract filter class for checking files and folders against exact matches.
*
* Supports both whitelists and blacklists.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Filters;
use PHP_CodeSniffer\Util;
abstract class ExactMatch extends Filter
{
/**
* A list of files to exclude.
*
* @var array
*/
private $blacklist = null;
/**
* A list of files to include.
*
* If the whitelist is empty, only files in the blacklist will be excluded.
*
* @var array
*/
private $whitelist = null;
/**
* Check whether the current element of the iterator is acceptable.
*
* If a file is both blacklisted and whitelisted, it will be deemed unacceptable.
*
* @return bool
*/
public function accept()
{
if (parent::accept() === false) {
return false;
}
if ($this->blacklist === null) {
$this->blacklist = $this->getblacklist();
}
if ($this->whitelist === null) {
$this->whitelist = $this->getwhitelist();
}
$filePath = Util\Common::realpath($this->current());
// If file is both blacklisted and whitelisted, the blacklist takes precedence.
if (isset($this->blacklist[$filePath]) === true) {
return false;
}
if (empty($this->whitelist) === true && empty($this->blacklist) === false) {
// We are only checking a blacklist, so everything else should be whitelisted.
return true;
}
return isset($this->whitelist[$filePath]);
}//end accept()
/**
* Returns an iterator for the current entry.
*
* Ensures that the blacklist and whitelist are preserved so they don't have
* to be generated each time.
*
* @return \RecursiveIterator
*/
public function getChildren()
{
$children = parent::getChildren();
$children->blacklist = $this->blacklist;
$children->whitelist = $this->whitelist;
return $children;
}//end getChildren()
/**
* Get a list of blacklisted file paths.
*
* @return array
*/
abstract protected function getBlacklist();
/**
* Get a list of whitelisted file paths.
*
* @return array
*/
abstract protected function getWhitelist();
}//end class

View file

@ -0,0 +1,285 @@
<?php
/**
* A base filter class for filtering out files and folders during a run.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Filters;
use PHP_CodeSniffer\Util;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Config;
use ReturnTypeWillChange;
class Filter extends \RecursiveFilterIterator
{
/**
* The top-level path we are filtering.
*
* @var string
*/
protected $basedir = null;
/**
* The config data for the run.
*
* @var \PHP_CodeSniffer\Config
*/
protected $config = null;
/**
* The ruleset used for the run.
*
* @var \PHP_CodeSniffer\Ruleset
*/
protected $ruleset = null;
/**
* A list of ignore patterns that apply to directories only.
*
* @var array
*/
protected $ignoreDirPatterns = null;
/**
* A list of ignore patterns that apply to files only.
*
* @var array
*/
protected $ignoreFilePatterns = null;
/**
* A list of file paths we've already accepted.
*
* Used to ensure we aren't following circular symlinks.
*
* @var array
*/
protected $acceptedPaths = [];
/**
* Constructs a filter.
*
* @param \RecursiveIterator $iterator The iterator we are using to get file paths.
* @param string $basedir The top-level path we are filtering.
* @param \PHP_CodeSniffer\Config $config The config data for the run.
* @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
*
* @return void
*/
public function __construct($iterator, $basedir, Config $config, Ruleset $ruleset)
{
parent::__construct($iterator);
$this->basedir = $basedir;
$this->config = $config;
$this->ruleset = $ruleset;
}//end __construct()
/**
* Check whether the current element of the iterator is acceptable.
*
* Files are checked for allowed extensions and ignore patterns.
* Directories are checked for ignore patterns only.
*
* @return bool
*/
#[ReturnTypeWillChange]
public function accept()
{
$filePath = $this->current();
$realPath = Util\Common::realpath($filePath);
if ($realPath !== false) {
// It's a real path somewhere, so record it
// to check for circular symlinks.
if (isset($this->acceptedPaths[$realPath]) === true) {
// We've been here before.
return false;
}
}
$filePath = $this->current();
if (is_dir($filePath) === true) {
if ($this->config->local === true) {
return false;
}
} else if ($this->shouldProcessFile($filePath) === false) {
return false;
}
if ($this->shouldIgnorePath($filePath) === true) {
return false;
}
$this->acceptedPaths[$realPath] = true;
return true;
}//end accept()
/**
* Returns an iterator for the current entry.
*
* Ensures that the ignore patterns are preserved so they don't have
* to be generated each time.
*
* @return \RecursiveIterator
*/
#[ReturnTypeWillChange]
public function getChildren()
{
$filterClass = get_called_class();
$children = new $filterClass(
new \RecursiveDirectoryIterator($this->current(), (\RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS)),
$this->basedir,
$this->config,
$this->ruleset
);
// Set the ignore patterns so we don't have to generate them again.
$children->ignoreDirPatterns = $this->ignoreDirPatterns;
$children->ignoreFilePatterns = $this->ignoreFilePatterns;
$children->acceptedPaths = $this->acceptedPaths;
return $children;
}//end getChildren()
/**
* Checks filtering rules to see if a file should be checked.
*
* Checks both file extension filters and path ignore filters.
*
* @param string $path The path to the file being checked.
*
* @return bool
*/
protected function shouldProcessFile($path)
{
// Check that the file's extension is one we are checking.
// We are strict about checking the extension and we don't
// let files through with no extension or that start with a dot.
$fileName = basename($path);
$fileParts = explode('.', $fileName);
if ($fileParts[0] === $fileName || $fileParts[0] === '') {
return false;
}
// Checking multi-part file extensions, so need to create a
// complete extension list and make sure one is allowed.
$extensions = [];
array_shift($fileParts);
foreach ($fileParts as $part) {
$extensions[implode('.', $fileParts)] = 1;
array_shift($fileParts);
}
$matches = array_intersect_key($extensions, $this->config->extensions);
if (empty($matches) === true) {
return false;
}
return true;
}//end shouldProcessFile()
/**
* Checks filtering rules to see if a path should be ignored.
*
* @param string $path The path to the file or directory being checked.
*
* @return bool
*/
protected function shouldIgnorePath($path)
{
if ($this->ignoreFilePatterns === null) {
$this->ignoreDirPatterns = [];
$this->ignoreFilePatterns = [];
$ignorePatterns = $this->config->ignored;
$rulesetIgnorePatterns = $this->ruleset->getIgnorePatterns();
foreach ($rulesetIgnorePatterns as $pattern => $type) {
// Ignore standard/sniff specific exclude rules.
if (is_array($type) === true) {
continue;
}
$ignorePatterns[$pattern] = $type;
}
foreach ($ignorePatterns as $pattern => $type) {
// If the ignore pattern ends with /* then it is ignoring an entire directory.
if (substr($pattern, -2) === '/*') {
// Need to check this pattern for dirs as well as individual file paths.
$this->ignoreFilePatterns[$pattern] = $type;
$pattern = substr($pattern, 0, -2).'(?=/|$)';
$this->ignoreDirPatterns[$pattern] = $type;
} else {
// This is a file-specific pattern, so only need to check this
// for individual file paths.
$this->ignoreFilePatterns[$pattern] = $type;
}
}
}//end if
$relativePath = $path;
if (strpos($path, $this->basedir) === 0) {
// The +1 cuts off the directory separator as well.
$relativePath = substr($path, (strlen($this->basedir) + 1));
}
if (is_dir($path) === true) {
$ignorePatterns = $this->ignoreDirPatterns;
} else {
$ignorePatterns = $this->ignoreFilePatterns;
}
foreach ($ignorePatterns as $pattern => $type) {
// Maintains backwards compatibility in case the ignore pattern does
// not have a relative/absolute value.
if (is_int($pattern) === true) {
$pattern = $type;
$type = 'absolute';
}
$replacements = [
'\\,' => ',',
'*' => '.*',
];
// We assume a / directory separator, as do the exclude rules
// most developers write, so we need a special case for any system
// that is different.
if (DIRECTORY_SEPARATOR === '\\') {
$replacements['/'] = '\\\\';
}
$pattern = strtr($pattern, $replacements);
if ($type === 'relative') {
$testPath = $relativePath;
} else {
$testPath = $path;
}
$pattern = '`'.$pattern.'`i';
if (preg_match($pattern, $testPath) === 1) {
return true;
}
}//end foreach
return false;
}//end shouldIgnorePath()
}//end class

View file

@ -0,0 +1,66 @@
<?php
/**
* A filter to only include files that have been modified or added in a Git repository.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Filters;
use PHP_CodeSniffer\Util;
class GitModified extends ExactMatch
{
/**
* Get a list of blacklisted file paths.
*
* @return array
*/
protected function getBlacklist()
{
return [];
}//end getBlacklist()
/**
* Get a list of whitelisted file paths.
*
* @return array
*/
protected function getWhitelist()
{
$modified = [];
$cmd = 'git ls-files -o -m --exclude-standard -- '.escapeshellarg($this->basedir);
$output = [];
exec($cmd, $output);
$basedir = $this->basedir;
if (is_dir($basedir) === false) {
$basedir = dirname($basedir);
}
foreach ($output as $path) {
$path = Util\Common::realpath($path);
if ($path === false) {
continue;
}
do {
$modified[$path] = true;
$path = dirname($path);
} while ($path !== $basedir);
}
return $modified;
}//end getWhitelist()
}//end class

View file

@ -0,0 +1,68 @@
<?php
/**
* A filter to only include files that have been staged for commit in a Git repository.
*
* This filter is the ideal companion for your pre-commit git hook.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2018 Juliette Reinders Folmer. All rights reserved.
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Filters;
use PHP_CodeSniffer\Util;
class GitStaged extends ExactMatch
{
/**
* Get a list of blacklisted file paths.
*
* @return array
*/
protected function getBlacklist()
{
return [];
}//end getBlacklist()
/**
* Get a list of whitelisted file paths.
*
* @return array
*/
protected function getWhitelist()
{
$modified = [];
$cmd = 'git diff --cached --name-only -- '.escapeshellarg($this->basedir);
$output = [];
exec($cmd, $output);
$basedir = $this->basedir;
if (is_dir($basedir) === false) {
$basedir = dirname($basedir);
}
foreach ($output as $path) {
$path = Util\Common::realpath($path);
if ($path === false) {
// Skip deleted files.
continue;
}
do {
$modified[$path] = true;
$path = dirname($path);
} while ($path !== $basedir);
}
return $modified;
}//end getWhitelist()
}//end class

View file

@ -0,0 +1,806 @@
<?php
/**
* A helper class for fixing errors.
*
* Provides helper functions that act upon a token array and modify the file
* content.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Common;
class Fixer
{
/**
* Is the fixer enabled and fixing a file?
*
* Sniffs should check this value to ensure they are not
* doing extra processing to prepare for a fix when fixing is
* not required.
*
* @var boolean
*/
public $enabled = false;
/**
* The number of times we have looped over a file.
*
* @var integer
*/
public $loops = 0;
/**
* The file being fixed.
*
* @var \PHP_CodeSniffer\Files\File
*/
private $currentFile = null;
/**
* The list of tokens that make up the file contents.
*
* This is a simplified list which just contains the token content and nothing
* else. This is the array that is updated as fixes are made, not the file's
* token array. Imploding this array will give you the file content back.
*
* @var array<int, string>
*/
private $tokens = [];
/**
* A list of tokens that have already been fixed.
*
* We don't allow the same token to be fixed more than once each time
* through a file as this can easily cause conflicts between sniffs.
*
* @var int[]
*/
private $fixedTokens = [];
/**
* The last value of each fixed token.
*
* If a token is being "fixed" back to its last value, the fix is
* probably conflicting with another.
*
* @var array<int, string>
*/
private $oldTokenValues = [];
/**
* A list of tokens that have been fixed during a changeset.
*
* All changes in changeset must be able to be applied, or else
* the entire changeset is rejected.
*
* @var array
*/
private $changeset = [];
/**
* Is there an open changeset.
*
* @var boolean
*/
private $inChangeset = false;
/**
* Is the current fixing loop in conflict?
*
* @var boolean
*/
private $inConflict = false;
/**
* The number of fixes that have been performed.
*
* @var integer
*/
private $numFixes = 0;
/**
* Starts fixing a new file.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being fixed.
*
* @return void
*/
public function startFile(File $phpcsFile)
{
$this->currentFile = $phpcsFile;
$this->numFixes = 0;
$this->fixedTokens = [];
$tokens = $phpcsFile->getTokens();
$this->tokens = [];
foreach ($tokens as $index => $token) {
if (isset($token['orig_content']) === true) {
$this->tokens[$index] = $token['orig_content'];
} else {
$this->tokens[$index] = $token['content'];
}
}
}//end startFile()
/**
* Attempt to fix the file by processing it until no fixes are made.
*
* @return boolean
*/
public function fixFile()
{
$fixable = $this->currentFile->getFixableCount();
if ($fixable === 0) {
// Nothing to fix.
return false;
}
$this->enabled = true;
$this->loops = 0;
while ($this->loops < 50) {
ob_start();
// Only needed once file content has changed.
$contents = $this->getContents();
if (PHP_CODESNIFFER_VERBOSITY > 2) {
@ob_end_clean();
echo '---START FILE CONTENT---'.PHP_EOL;
$lines = explode($this->currentFile->eolChar, $contents);
$max = strlen(count($lines));
foreach ($lines as $lineNum => $line) {
$lineNum++;
echo str_pad($lineNum, $max, ' ', STR_PAD_LEFT).'|'.$line.PHP_EOL;
}
echo '--- END FILE CONTENT ---'.PHP_EOL;
ob_start();
}
$this->inConflict = false;
$this->currentFile->ruleset->populateTokenListeners();
$this->currentFile->setContent($contents);
$this->currentFile->process();
ob_end_clean();
$this->loops++;
if (PHP_CODESNIFFER_CBF === true && PHP_CODESNIFFER_VERBOSITY > 0) {
echo "\r".str_repeat(' ', 80)."\r";
echo "\t=> Fixing file: $this->numFixes/$fixable violations remaining [made $this->loops pass";
if ($this->loops > 1) {
echo 'es';
}
echo ']... ';
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo PHP_EOL;
}
}
if ($this->numFixes === 0 && $this->inConflict === false) {
// Nothing left to do.
break;
} else if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t* fixed $this->numFixes violations, starting loop ".($this->loops + 1).' *'.PHP_EOL;
}
}//end while
$this->enabled = false;
if ($this->numFixes > 0) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
if (ob_get_level() > 0) {
ob_end_clean();
}
echo "\t*** Reached maximum number of loops with $this->numFixes violations left unfixed ***".PHP_EOL;
ob_start();
}
return false;
}
return true;
}//end fixFile()
/**
* Generates a text diff of the original file and the new content.
*
* @param string $filePath Optional file path to diff the file against.
* If not specified, the original version of the
* file will be used.
* @param boolean $colors Print coloured output or not.
*
* @return string
*/
public function generateDiff($filePath=null, $colors=true)
{
if ($filePath === null) {
$filePath = $this->currentFile->getFilename();
}
$cwd = getcwd().DIRECTORY_SEPARATOR;
if (strpos($filePath, $cwd) === 0) {
$filename = substr($filePath, strlen($cwd));
} else {
$filename = $filePath;
}
$contents = $this->getContents();
$tempName = tempnam(sys_get_temp_dir(), 'phpcs-fixer');
$fixedFile = fopen($tempName, 'w');
fwrite($fixedFile, $contents);
// We must use something like shell_exec() because whitespace at the end
// of lines is critical to diff files.
$filename = escapeshellarg($filename);
$cmd = "diff -u -L$filename -LPHP_CodeSniffer $filename \"$tempName\"";
$diff = shell_exec($cmd);
fclose($fixedFile);
if (is_file($tempName) === true) {
unlink($tempName);
}
if ($diff === null) {
return '';
}
if ($colors === false) {
return $diff;
}
$diffLines = explode(PHP_EOL, $diff);
if (count($diffLines) === 1) {
// Seems to be required for cygwin.
$diffLines = explode("\n", $diff);
}
$diff = [];
foreach ($diffLines as $line) {
if (isset($line[0]) === true) {
switch ($line[0]) {
case '-':
$diff[] = "\033[31m$line\033[0m";
break;
case '+':
$diff[] = "\033[32m$line\033[0m";
break;
default:
$diff[] = $line;
}
}
}
$diff = implode(PHP_EOL, $diff);
return $diff;
}//end generateDiff()
/**
* Get a count of fixes that have been performed on the file.
*
* This value is reset every time a new file is started, or an existing
* file is restarted.
*
* @return int
*/
public function getFixCount()
{
return $this->numFixes;
}//end getFixCount()
/**
* Get the current content of the file, as a string.
*
* @return string
*/
public function getContents()
{
$contents = implode($this->tokens);
return $contents;
}//end getContents()
/**
* Get the current fixed content of a token.
*
* This function takes changesets into account so should be used
* instead of directly accessing the token array.
*
* @param int $stackPtr The position of the token in the token stack.
*
* @return string
*/
public function getTokenContent($stackPtr)
{
if ($this->inChangeset === true
&& isset($this->changeset[$stackPtr]) === true
) {
return $this->changeset[$stackPtr];
} else {
return $this->tokens[$stackPtr];
}
}//end getTokenContent()
/**
* Start recording actions for a changeset.
*
* @return void
*/
public function beginChangeset()
{
if ($this->inConflict === true) {
return false;
}
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
if ($bt[1]['class'] === __CLASS__) {
$sniff = 'Fixer';
} else {
$sniff = Util\Common::getSniffCode($bt[1]['class']);
}
$line = $bt[0]['line'];
@ob_end_clean();
echo "\t=> Changeset started by $sniff:$line".PHP_EOL;
ob_start();
}
$this->changeset = [];
$this->inChangeset = true;
}//end beginChangeset()
/**
* Stop recording actions for a changeset, and apply logged changes.
*
* @return boolean
*/
public function endChangeset()
{
if ($this->inConflict === true) {
return false;
}
$this->inChangeset = false;
$success = true;
$applied = [];
foreach ($this->changeset as $stackPtr => $content) {
$success = $this->replaceToken($stackPtr, $content);
if ($success === false) {
break;
} else {
$applied[] = $stackPtr;
}
}
if ($success === false) {
// Rolling back all changes.
foreach ($applied as $stackPtr) {
$this->revertToken($stackPtr);
}
if (PHP_CODESNIFFER_VERBOSITY > 1) {
@ob_end_clean();
echo "\t=> Changeset failed to apply".PHP_EOL;
ob_start();
}
} else if (PHP_CODESNIFFER_VERBOSITY > 1) {
$fixes = count($this->changeset);
@ob_end_clean();
echo "\t=> Changeset ended: $fixes changes applied".PHP_EOL;
ob_start();
}
$this->changeset = [];
return true;
}//end endChangeset()
/**
* Stop recording actions for a changeset, and discard logged changes.
*
* @return void
*/
public function rollbackChangeset()
{
$this->inChangeset = false;
$this->inConflict = false;
if (empty($this->changeset) === false) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$bt = debug_backtrace();
if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') {
$sniff = $bt[2]['class'];
$line = $bt[1]['line'];
} else {
$sniff = $bt[1]['class'];
$line = $bt[0]['line'];
}
$sniff = Util\Common::getSniffCode($sniff);
$numChanges = count($this->changeset);
@ob_end_clean();
echo "\t\tR: $sniff:$line rolled back the changeset ($numChanges changes)".PHP_EOL;
echo "\t=> Changeset rolled back".PHP_EOL;
ob_start();
}
$this->changeset = [];
}//end if
}//end rollbackChangeset()
/**
* Replace the entire contents of a token.
*
* @param int $stackPtr The position of the token in the token stack.
* @param string $content The new content of the token.
*
* @return bool If the change was accepted.
*/
public function replaceToken($stackPtr, $content)
{
if ($this->inConflict === true) {
return false;
}
if ($this->inChangeset === false
&& isset($this->fixedTokens[$stackPtr]) === true
) {
$indent = "\t";
if (empty($this->changeset) === false) {
$indent .= "\t";
}
if (PHP_CODESNIFFER_VERBOSITY > 1) {
@ob_end_clean();
echo "$indent* token $stackPtr has already been modified, skipping *".PHP_EOL;
ob_start();
}
return false;
}
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') {
$sniff = $bt[2]['class'];
$line = $bt[1]['line'];
} else {
$sniff = $bt[1]['class'];
$line = $bt[0]['line'];
}
$sniff = Util\Common::getSniffCode($sniff);
$tokens = $this->currentFile->getTokens();
$type = $tokens[$stackPtr]['type'];
$tokenLine = $tokens[$stackPtr]['line'];
$oldContent = Common::prepareForOutput($this->tokens[$stackPtr]);
$newContent = Common::prepareForOutput($content);
if (trim($this->tokens[$stackPtr]) === '' && isset($this->tokens[($stackPtr + 1)]) === true) {
// Add some context for whitespace only changes.
$append = Common::prepareForOutput($this->tokens[($stackPtr + 1)]);
$oldContent .= $append;
$newContent .= $append;
}
}//end if
if ($this->inChangeset === true) {
$this->changeset[$stackPtr] = $content;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
@ob_end_clean();
echo "\t\tQ: $sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"".PHP_EOL;
ob_start();
}
return true;
}
if (isset($this->oldTokenValues[$stackPtr]) === false) {
$this->oldTokenValues[$stackPtr] = [
'curr' => $content,
'prev' => $this->tokens[$stackPtr],
'loop' => $this->loops,
];
} else {
if ($this->oldTokenValues[$stackPtr]['prev'] === $content
&& $this->oldTokenValues[$stackPtr]['loop'] === ($this->loops - 1)
) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$indent = "\t";
if (empty($this->changeset) === false) {
$indent .= "\t";
}
$loop = $this->oldTokenValues[$stackPtr]['loop'];
@ob_end_clean();
echo "$indent**** $sniff:$line has possible conflict with another sniff on loop $loop; caused by the following change ****".PHP_EOL;
echo "$indent**** replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\" ****".PHP_EOL;
}
if ($this->oldTokenValues[$stackPtr]['loop'] >= ($this->loops - 1)) {
$this->inConflict = true;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "$indent**** ignoring all changes until next loop ****".PHP_EOL;
}
}
if (PHP_CODESNIFFER_VERBOSITY > 1) {
ob_start();
}
return false;
}//end if
$this->oldTokenValues[$stackPtr]['prev'] = $this->oldTokenValues[$stackPtr]['curr'];
$this->oldTokenValues[$stackPtr]['curr'] = $content;
$this->oldTokenValues[$stackPtr]['loop'] = $this->loops;
}//end if
$this->fixedTokens[$stackPtr] = $this->tokens[$stackPtr];
$this->tokens[$stackPtr] = $content;
$this->numFixes++;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$indent = "\t";
if (empty($this->changeset) === false) {
$indent .= "\tA: ";
}
if (ob_get_level() > 0) {
ob_end_clean();
}
echo "$indent$sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"".PHP_EOL;
ob_start();
}
return true;
}//end replaceToken()
/**
* Reverts the previous fix made to a token.
*
* @param int $stackPtr The position of the token in the token stack.
*
* @return bool If a change was reverted.
*/
public function revertToken($stackPtr)
{
if (isset($this->fixedTokens[$stackPtr]) === false) {
return false;
}
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') {
$sniff = $bt[2]['class'];
$line = $bt[1]['line'];
} else {
$sniff = $bt[1]['class'];
$line = $bt[0]['line'];
}
$sniff = Util\Common::getSniffCode($sniff);
$tokens = $this->currentFile->getTokens();
$type = $tokens[$stackPtr]['type'];
$tokenLine = $tokens[$stackPtr]['line'];
$oldContent = Common::prepareForOutput($this->tokens[$stackPtr]);
$newContent = Common::prepareForOutput($this->fixedTokens[$stackPtr]);
if (trim($this->tokens[$stackPtr]) === '' && isset($tokens[($stackPtr + 1)]) === true) {
// Add some context for whitespace only changes.
$append = Common::prepareForOutput($this->tokens[($stackPtr + 1)]);
$oldContent .= $append;
$newContent .= $append;
}
}//end if
$this->tokens[$stackPtr] = $this->fixedTokens[$stackPtr];
unset($this->fixedTokens[$stackPtr]);
$this->numFixes--;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$indent = "\t";
if (empty($this->changeset) === false) {
$indent .= "\tR: ";
}
@ob_end_clean();
echo "$indent$sniff:$line reverted token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"".PHP_EOL;
ob_start();
}
return true;
}//end revertToken()
/**
* Replace the content of a token with a part of its current content.
*
* @param int $stackPtr The position of the token in the token stack.
* @param int $start The first character to keep.
* @param int $length The number of characters to keep. If NULL, the content of
* the token from $start to the end of the content is kept.
*
* @return bool If the change was accepted.
*/
public function substrToken($stackPtr, $start, $length=null)
{
$current = $this->getTokenContent($stackPtr);
if ($length === null) {
$newContent = substr($current, $start);
} else {
$newContent = substr($current, $start, $length);
}
return $this->replaceToken($stackPtr, $newContent);
}//end substrToken()
/**
* Adds a newline to end of a token's content.
*
* @param int $stackPtr The position of the token in the token stack.
*
* @return bool If the change was accepted.
*/
public function addNewline($stackPtr)
{
$current = $this->getTokenContent($stackPtr);
return $this->replaceToken($stackPtr, $current.$this->currentFile->eolChar);
}//end addNewline()
/**
* Adds a newline to the start of a token's content.
*
* @param int $stackPtr The position of the token in the token stack.
*
* @return bool If the change was accepted.
*/
public function addNewlineBefore($stackPtr)
{
$current = $this->getTokenContent($stackPtr);
return $this->replaceToken($stackPtr, $this->currentFile->eolChar.$current);
}//end addNewlineBefore()
/**
* Adds content to the end of a token's current content.
*
* @param int $stackPtr The position of the token in the token stack.
* @param string $content The content to add.
*
* @return bool If the change was accepted.
*/
public function addContent($stackPtr, $content)
{
$current = $this->getTokenContent($stackPtr);
return $this->replaceToken($stackPtr, $current.$content);
}//end addContent()
/**
* Adds content to the start of a token's current content.
*
* @param int $stackPtr The position of the token in the token stack.
* @param string $content The content to add.
*
* @return bool If the change was accepted.
*/
public function addContentBefore($stackPtr, $content)
{
$current = $this->getTokenContent($stackPtr);
return $this->replaceToken($stackPtr, $content.$current);
}//end addContentBefore()
/**
* Adjust the indent of a code block.
*
* @param int $start The position of the token in the token stack
* to start adjusting the indent from.
* @param int $end The position of the token in the token stack
* to end adjusting the indent.
* @param int $change The number of spaces to adjust the indent by
* (positive or negative).
*
* @return void
*/
public function changeCodeBlockIndent($start, $end, $change)
{
$tokens = $this->currentFile->getTokens();
$baseIndent = '';
if ($change > 0) {
$baseIndent = str_repeat(' ', $change);
}
$useChangeset = false;
if ($this->inChangeset === false) {
$this->beginChangeset();
$useChangeset = true;
}
for ($i = $start; $i <= $end; $i++) {
if ($tokens[$i]['column'] !== 1
|| $tokens[($i + 1)]['line'] !== $tokens[$i]['line']
) {
continue;
}
$length = 0;
if ($tokens[$i]['code'] === T_WHITESPACE
|| $tokens[$i]['code'] === T_DOC_COMMENT_WHITESPACE
) {
$length = $tokens[$i]['length'];
$padding = ($length + $change);
if ($padding > 0) {
$padding = str_repeat(' ', $padding);
} else {
$padding = '';
}
$newContent = $padding.ltrim($tokens[$i]['content']);
} else {
$newContent = $baseIndent.$tokens[$i]['content'];
}
$this->replaceToken($i, $newContent);
}//end for
if ($useChangeset === true) {
$this->endChangeset();
}
}//end changeCodeBlockIndent()
}//end class

View file

@ -0,0 +1,117 @@
<?php
/**
* The base class for all PHP_CodeSniffer documentation generators.
*
* Documentation generators are used to print documentation about code sniffs
* in a standard.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Generators;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Autoload;
abstract class Generator
{
/**
* The ruleset used for the run.
*
* @var \PHP_CodeSniffer\Ruleset
*/
public $ruleset = null;
/**
* XML documentation files used to produce the final output.
*
* @var string[]
*/
public $docFiles = [];
/**
* Constructs a doc generator.
*
* @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
*
* @see generate()
*/
public function __construct(Ruleset $ruleset)
{
$this->ruleset = $ruleset;
foreach ($ruleset->sniffs as $className => $sniffClass) {
$file = Autoload::getLoadedFileName($className);
$docFile = str_replace(
DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR,
DIRECTORY_SEPARATOR.'Docs'.DIRECTORY_SEPARATOR,
$file
);
$docFile = str_replace('Sniff.php', 'Standard.xml', $docFile);
if (is_file($docFile) === true) {
$this->docFiles[] = $docFile;
}
}
}//end __construct()
/**
* Retrieves the title of the sniff from the DOMNode supplied.
*
* @param \DOMNode $doc The DOMNode object for the sniff.
* It represents the "documentation" tag in the XML
* standard file.
*
* @return string
*/
protected function getTitle(\DOMNode $doc)
{
return $doc->getAttribute('title');
}//end getTitle()
/**
* Generates the documentation for a standard.
*
* It's probably wise for doc generators to override this method so they
* have control over how the docs are produced. Otherwise, the processSniff
* method should be overridden to output content for each sniff.
*
* @return void
* @see processSniff()
*/
public function generate()
{
foreach ($this->docFiles as $file) {
$doc = new \DOMDocument();
$doc->load($file);
$documentation = $doc->getElementsByTagName('documentation')->item(0);
$this->processSniff($documentation);
}
}//end generate()
/**
* Process the documentation for a single sniff.
*
* Doc generators must implement this function to produce output.
*
* @param \DOMNode $doc The DOMNode object for the sniff.
* It represents the "documentation" tag in the XML
* standard file.
*
* @return void
* @see generate()
*/
abstract protected function processSniff(\DOMNode $doc);
}//end class

View file

@ -0,0 +1,270 @@
<?php
/**
* A doc generator that outputs documentation in one big HTML file.
*
* Output is in one large HTML file and is designed for you to style with
* your own stylesheet. It contains a table of contents at the top with anchors
* to each sniff.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Generators;
use PHP_CodeSniffer\Config;
class HTML extends Generator
{
/**
* Generates the documentation for a standard.
*
* @return void
* @see processSniff()
*/
public function generate()
{
ob_start();
$this->printHeader();
$this->printToc();
foreach ($this->docFiles as $file) {
$doc = new \DOMDocument();
$doc->load($file);
$documentation = $doc->getElementsByTagName('documentation')->item(0);
$this->processSniff($documentation);
}
$this->printFooter();
$content = ob_get_contents();
ob_end_clean();
echo $content;
}//end generate()
/**
* Print the header of the HTML page.
*
* @return void
*/
protected function printHeader()
{
$standard = $this->ruleset->name;
echo '<html>'.PHP_EOL;
echo ' <head>'.PHP_EOL;
echo " <title>$standard Coding Standards</title>".PHP_EOL;
echo ' <style>
body {
background-color: #FFFFFF;
font-size: 14px;
font-family: Arial, Helvetica, sans-serif;
color: #000000;
}
h1 {
color: #666666;
font-size: 20px;
font-weight: bold;
margin-top: 0px;
background-color: #E6E7E8;
padding: 20px;
border: 1px solid #BBBBBB;
}
h2 {
color: #00A5E3;
font-size: 16px;
font-weight: normal;
margin-top: 50px;
}
.code-comparison {
width: 100%;
}
.code-comparison td {
border: 1px solid #CCCCCC;
}
.code-comparison-title, .code-comparison-code {
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
color: #000000;
vertical-align: top;
padding: 4px;
width: 50%;
background-color: #F1F1F1;
line-height: 15px;
}
.code-comparison-code {
font-family: Courier;
background-color: #F9F9F9;
}
.code-comparison-highlight {
background-color: #DDF1F7;
border: 1px solid #00A5E3;
line-height: 15px;
}
.tag-line {
text-align: center;
width: 100%;
margin-top: 30px;
font-size: 12px;
}
.tag-line a {
color: #000000;
}
</style>'.PHP_EOL;
echo ' </head>'.PHP_EOL;
echo ' <body>'.PHP_EOL;
echo " <h1>$standard Coding Standards</h1>".PHP_EOL;
}//end printHeader()
/**
* Print the table of contents for the standard.
*
* The TOC is just an unordered list of bookmarks to sniffs on the page.
*
* @return void
*/
protected function printToc()
{
echo ' <h2>Table of Contents</h2>'.PHP_EOL;
echo ' <ul class="toc">'.PHP_EOL;
foreach ($this->docFiles as $file) {
$doc = new \DOMDocument();
$doc->load($file);
$documentation = $doc->getElementsByTagName('documentation')->item(0);
$title = $this->getTitle($documentation);
echo ' <li><a href="#'.str_replace(' ', '-', $title)."\">$title</a></li>".PHP_EOL;
}
echo ' </ul>'.PHP_EOL;
}//end printToc()
/**
* Print the footer of the HTML page.
*
* @return void
*/
protected function printFooter()
{
// Turn off errors so we don't get timezone warnings if people
// don't have their timezone set.
$errorLevel = error_reporting(0);
echo ' <div class="tag-line">';
echo 'Documentation generated on '.date('r');
echo ' by <a href="https://github.com/squizlabs/PHP_CodeSniffer">PHP_CodeSniffer '.Config::VERSION.'</a>';
echo '</div>'.PHP_EOL;
error_reporting($errorLevel);
echo ' </body>'.PHP_EOL;
echo '</html>'.PHP_EOL;
}//end printFooter()
/**
* Process the documentation for a single sniff.
*
* @param \DOMNode $doc The DOMNode object for the sniff.
* It represents the "documentation" tag in the XML
* standard file.
*
* @return void
*/
public function processSniff(\DOMNode $doc)
{
$title = $this->getTitle($doc);
echo ' <a name="'.str_replace(' ', '-', $title).'" />'.PHP_EOL;
echo " <h2>$title</h2>".PHP_EOL;
foreach ($doc->childNodes as $node) {
if ($node->nodeName === 'standard') {
$this->printTextBlock($node);
} else if ($node->nodeName === 'code_comparison') {
$this->printCodeComparisonBlock($node);
}
}
}//end processSniff()
/**
* Print a text block found in a standard.
*
* @param \DOMNode $node The DOMNode object for the text block.
*
* @return void
*/
protected function printTextBlock(\DOMNode $node)
{
$content = trim($node->nodeValue);
$content = htmlspecialchars($content);
// Allow em tags only.
$content = str_replace('&lt;em&gt;', '<em>', $content);
$content = str_replace('&lt;/em&gt;', '</em>', $content);
echo " <p class=\"text\">$content</p>".PHP_EOL;
}//end printTextBlock()
/**
* Print a code comparison block found in a standard.
*
* @param \DOMNode $node The DOMNode object for the code comparison block.
*
* @return void
*/
protected function printCodeComparisonBlock(\DOMNode $node)
{
$codeBlocks = $node->getElementsByTagName('code');
$firstTitle = $codeBlocks->item(0)->getAttribute('title');
$first = trim($codeBlocks->item(0)->nodeValue);
$first = str_replace('<?php', '&lt;?php', $first);
$first = str_replace("\n", '</br>', $first);
$first = str_replace(' ', '&nbsp;', $first);
$first = str_replace('<em>', '<span class="code-comparison-highlight">', $first);
$first = str_replace('</em>', '</span>', $first);
$secondTitle = $codeBlocks->item(1)->getAttribute('title');
$second = trim($codeBlocks->item(1)->nodeValue);
$second = str_replace('<?php', '&lt;?php', $second);
$second = str_replace("\n", '</br>', $second);
$second = str_replace(' ', '&nbsp;', $second);
$second = str_replace('<em>', '<span class="code-comparison-highlight">', $second);
$second = str_replace('</em>', '</span>', $second);
echo ' <table class="code-comparison">'.PHP_EOL;
echo ' <tr>'.PHP_EOL;
echo " <td class=\"code-comparison-title\">$firstTitle</td>".PHP_EOL;
echo " <td class=\"code-comparison-title\">$secondTitle</td>".PHP_EOL;
echo ' </tr>'.PHP_EOL;
echo ' <tr>'.PHP_EOL;
echo " <td class=\"code-comparison-code\">$first</td>".PHP_EOL;
echo " <td class=\"code-comparison-code\">$second</td>".PHP_EOL;
echo ' </tr>'.PHP_EOL;
echo ' </table>'.PHP_EOL;
}//end printCodeComparisonBlock()
}//end class

View file

@ -0,0 +1,161 @@
<?php
/**
* A doc generator that outputs documentation in Markdown format.
*
* @author Stefano Kowalke <blueduck@gmx.net>
* @copyright 2014 Arroba IT
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Generators;
use PHP_CodeSniffer\Config;
class Markdown extends Generator
{
/**
* Generates the documentation for a standard.
*
* @return void
* @see processSniff()
*/
public function generate()
{
ob_start();
$this->printHeader();
foreach ($this->docFiles as $file) {
$doc = new \DOMDocument();
$doc->load($file);
$documentation = $doc->getElementsByTagName('documentation')->item(0);
$this->processSniff($documentation);
}
$this->printFooter();
$content = ob_get_contents();
ob_end_clean();
echo $content;
}//end generate()
/**
* Print the markdown header.
*
* @return void
*/
protected function printHeader()
{
$standard = $this->ruleset->name;
echo "# $standard Coding Standard".PHP_EOL;
}//end printHeader()
/**
* Print the markdown footer.
*
* @return void
*/
protected function printFooter()
{
// Turn off errors so we don't get timezone warnings if people
// don't have their timezone set.
error_reporting(0);
echo 'Documentation generated on '.date('r');
echo ' by [PHP_CodeSniffer '.Config::VERSION.'](https://github.com/squizlabs/PHP_CodeSniffer)'.PHP_EOL;
}//end printFooter()
/**
* Process the documentation for a single sniff.
*
* @param \DOMNode $doc The DOMNode object for the sniff.
* It represents the "documentation" tag in the XML
* standard file.
*
* @return void
*/
protected function processSniff(\DOMNode $doc)
{
$title = $this->getTitle($doc);
echo PHP_EOL."## $title".PHP_EOL;
foreach ($doc->childNodes as $node) {
if ($node->nodeName === 'standard') {
$this->printTextBlock($node);
} else if ($node->nodeName === 'code_comparison') {
$this->printCodeComparisonBlock($node);
}
}
}//end processSniff()
/**
* Print a text block found in a standard.
*
* @param \DOMNode $node The DOMNode object for the text block.
*
* @return void
*/
protected function printTextBlock(\DOMNode $node)
{
$content = trim($node->nodeValue);
$content = htmlspecialchars($content);
$content = str_replace('&lt;em&gt;', '*', $content);
$content = str_replace('&lt;/em&gt;', '*', $content);
echo $content.PHP_EOL;
}//end printTextBlock()
/**
* Print a code comparison block found in a standard.
*
* @param \DOMNode $node The DOMNode object for the code comparison block.
*
* @return void
*/
protected function printCodeComparisonBlock(\DOMNode $node)
{
$codeBlocks = $node->getElementsByTagName('code');
$firstTitle = $codeBlocks->item(0)->getAttribute('title');
$first = trim($codeBlocks->item(0)->nodeValue);
$first = str_replace("\n", "\n ", $first);
$first = str_replace('<em>', '', $first);
$first = str_replace('</em>', '', $first);
$secondTitle = $codeBlocks->item(1)->getAttribute('title');
$second = trim($codeBlocks->item(1)->nodeValue);
$second = str_replace("\n", "\n ", $second);
$second = str_replace('<em>', '', $second);
$second = str_replace('</em>', '', $second);
echo ' <table>'.PHP_EOL;
echo ' <tr>'.PHP_EOL;
echo " <th>$firstTitle</th>".PHP_EOL;
echo " <th>$secondTitle</th>".PHP_EOL;
echo ' </tr>'.PHP_EOL;
echo ' <tr>'.PHP_EOL;
echo '<td>'.PHP_EOL.PHP_EOL;
echo " $first".PHP_EOL.PHP_EOL;
echo '</td>'.PHP_EOL;
echo '<td>'.PHP_EOL.PHP_EOL;
echo " $second".PHP_EOL.PHP_EOL;
echo '</td>'.PHP_EOL;
echo ' </tr>'.PHP_EOL;
echo ' </table>'.PHP_EOL;
}//end printCodeComparisonBlock()
}//end class

View file

@ -0,0 +1,253 @@
<?php
/**
* A doc generator that outputs text-based documentation.
*
* Output is designed to be displayed in a terminal and is wrapped to 100 characters.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Generators;
class Text extends Generator
{
/**
* Process the documentation for a single sniff.
*
* @param \DOMNode $doc The DOMNode object for the sniff.
* It represents the "documentation" tag in the XML
* standard file.
*
* @return void
*/
public function processSniff(\DOMNode $doc)
{
$this->printTitle($doc);
foreach ($doc->childNodes as $node) {
if ($node->nodeName === 'standard') {
$this->printTextBlock($node);
} else if ($node->nodeName === 'code_comparison') {
$this->printCodeComparisonBlock($node);
}
}
}//end processSniff()
/**
* Prints the title area for a single sniff.
*
* @param \DOMNode $doc The DOMNode object for the sniff.
* It represents the "documentation" tag in the XML
* standard file.
*
* @return void
*/
protected function printTitle(\DOMNode $doc)
{
$title = $this->getTitle($doc);
$standard = $this->ruleset->name;
echo PHP_EOL;
echo str_repeat('-', (strlen("$standard CODING STANDARD: $title") + 4));
echo strtoupper(PHP_EOL."| $standard CODING STANDARD: $title |".PHP_EOL);
echo str_repeat('-', (strlen("$standard CODING STANDARD: $title") + 4));
echo PHP_EOL.PHP_EOL;
}//end printTitle()
/**
* Print a text block found in a standard.
*
* @param \DOMNode $node The DOMNode object for the text block.
*
* @return void
*/
protected function printTextBlock(\DOMNode $node)
{
$text = trim($node->nodeValue);
$text = str_replace('<em>', '*', $text);
$text = str_replace('</em>', '*', $text);
$nodeLines = explode("\n", $text);
$lines = [];
foreach ($nodeLines as $currentLine) {
$currentLine = trim($currentLine);
if ($currentLine === '') {
// The text contained a blank line. Respect this.
$lines[] = '';
continue;
}
$tempLine = '';
$words = explode(' ', $currentLine);
foreach ($words as $word) {
$currentLength = strlen($tempLine.$word);
if ($currentLength < 99) {
$tempLine .= $word.' ';
continue;
}
if ($currentLength === 99 || $currentLength === 100) {
// We are already at the edge, so we are done.
$lines[] = $tempLine.$word;
$tempLine = '';
} else {
$lines[] = rtrim($tempLine);
$tempLine = $word.' ';
}
}//end foreach
if ($tempLine !== '') {
$lines[] = rtrim($tempLine);
}
}//end foreach
echo implode(PHP_EOL, $lines).PHP_EOL.PHP_EOL;
}//end printTextBlock()
/**
* Print a code comparison block found in a standard.
*
* @param \DOMNode $node The DOMNode object for the code comparison block.
*
* @return void
*/
protected function printCodeComparisonBlock(\DOMNode $node)
{
$codeBlocks = $node->getElementsByTagName('code');
$first = trim($codeBlocks->item(0)->nodeValue);
$firstTitle = $codeBlocks->item(0)->getAttribute('title');
$firstTitleLines = [];
$tempTitle = '';
$words = explode(' ', $firstTitle);
foreach ($words as $word) {
if (strlen($tempTitle.$word) >= 45) {
if (strlen($tempTitle.$word) === 45) {
// Adding the extra space will push us to the edge
// so we are done.
$firstTitleLines[] = $tempTitle.$word;
$tempTitle = '';
} else if (strlen($tempTitle.$word) === 46) {
// We are already at the edge, so we are done.
$firstTitleLines[] = $tempTitle.$word;
$tempTitle = '';
} else {
$firstTitleLines[] = $tempTitle;
$tempTitle = $word.' ';
}
} else {
$tempTitle .= $word.' ';
}
}//end foreach
if ($tempTitle !== '') {
$firstTitleLines[] = $tempTitle;
}
$first = str_replace('<em>', '', $first);
$first = str_replace('</em>', '', $first);
$firstLines = explode("\n", $first);
$second = trim($codeBlocks->item(1)->nodeValue);
$secondTitle = $codeBlocks->item(1)->getAttribute('title');
$secondTitleLines = [];
$tempTitle = '';
$words = explode(' ', $secondTitle);
foreach ($words as $word) {
if (strlen($tempTitle.$word) >= 45) {
if (strlen($tempTitle.$word) === 45) {
// Adding the extra space will push us to the edge
// so we are done.
$secondTitleLines[] = $tempTitle.$word;
$tempTitle = '';
} else if (strlen($tempTitle.$word) === 46) {
// We are already at the edge, so we are done.
$secondTitleLines[] = $tempTitle.$word;
$tempTitle = '';
} else {
$secondTitleLines[] = $tempTitle;
$tempTitle = $word.' ';
}
} else {
$tempTitle .= $word.' ';
}
}//end foreach
if ($tempTitle !== '') {
$secondTitleLines[] = $tempTitle;
}
$second = str_replace('<em>', '', $second);
$second = str_replace('</em>', '', $second);
$secondLines = explode("\n", $second);
$maxCodeLines = max(count($firstLines), count($secondLines));
$maxTitleLines = max(count($firstTitleLines), count($secondTitleLines));
echo str_repeat('-', 41);
echo ' CODE COMPARISON ';
echo str_repeat('-', 42).PHP_EOL;
for ($i = 0; $i < $maxTitleLines; $i++) {
if (isset($firstTitleLines[$i]) === true) {
$firstLineText = $firstTitleLines[$i];
} else {
$firstLineText = '';
}
if (isset($secondTitleLines[$i]) === true) {
$secondLineText = $secondTitleLines[$i];
} else {
$secondLineText = '';
}
echo '| ';
echo $firstLineText.str_repeat(' ', (46 - strlen($firstLineText)));
echo ' | ';
echo $secondLineText.str_repeat(' ', (47 - strlen($secondLineText)));
echo ' |'.PHP_EOL;
}//end for
echo str_repeat('-', 100).PHP_EOL;
for ($i = 0; $i < $maxCodeLines; $i++) {
if (isset($firstLines[$i]) === true) {
$firstLineText = $firstLines[$i];
} else {
$firstLineText = '';
}
if (isset($secondLines[$i]) === true) {
$secondLineText = $secondLines[$i];
} else {
$secondLineText = '';
}
echo '| ';
echo $firstLineText.str_repeat(' ', max(0, (47 - strlen($firstLineText))));
echo '| ';
echo $secondLineText.str_repeat(' ', max(0, (48 - strlen($secondLineText))));
echo '|'.PHP_EOL;
}//end for
echo str_repeat('-', 100).PHP_EOL.PHP_EOL;
}//end printCodeComparisonBlock()
}//end class

View file

@ -0,0 +1,423 @@
<?php
/**
* Manages reporting of errors and warnings.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer;
use PHP_CodeSniffer\Exceptions\DeepExitException;
use PHP_CodeSniffer\Exceptions\RuntimeException;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Reports\Report;
use PHP_CodeSniffer\Util\Common;
class Reporter
{
/**
* The config data for the run.
*
* @var \PHP_CodeSniffer\Config
*/
public $config = null;
/**
* Total number of files that contain errors or warnings.
*
* @var integer
*/
public $totalFiles = 0;
/**
* Total number of errors found during the run.
*
* @var integer
*/
public $totalErrors = 0;
/**
* Total number of warnings found during the run.
*
* @var integer
*/
public $totalWarnings = 0;
/**
* Total number of errors/warnings that can be fixed.
*
* @var integer
*/
public $totalFixable = 0;
/**
* Total number of errors/warnings that were fixed.
*
* @var integer
*/
public $totalFixed = 0;
/**
* When the PHPCS run started.
*
* @var float
*/
public static $startTime = 0;
/**
* A cache of report objects.
*
* @var array
*/
private $reports = [];
/**
* A cache of opened temporary files.
*
* @var array
*/
private $tmpFiles = [];
/**
* Initialise the reporter.
*
* All reports specified in the config will be created and their
* output file (or a temp file if none is specified) initialised by
* clearing the current contents.
*
* @param \PHP_CodeSniffer\Config $config The config data for the run.
*
* @return void
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException If a custom report class could not be found.
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If a report class is incorrectly set up.
*/
public function __construct(Config $config)
{
$this->config = $config;
foreach ($config->reports as $type => $output) {
if ($output === null) {
$output = $config->reportFile;
}
$reportClassName = '';
if (strpos($type, '.') !== false) {
// This is a path to a custom report class.
$filename = realpath($type);
if ($filename === false) {
$error = "ERROR: Custom report \"$type\" not found".PHP_EOL;
throw new DeepExitException($error, 3);
}
$reportClassName = Autoload::loadFile($filename);
} else if (class_exists('PHP_CodeSniffer\Reports\\'.ucfirst($type)) === true) {
// PHPCS native report.
$reportClassName = 'PHP_CodeSniffer\Reports\\'.ucfirst($type);
} else if (class_exists($type) === true) {
// FQN of a custom report.
$reportClassName = $type;
} else {
// OK, so not a FQN, try and find the report using the registered namespaces.
$registeredNamespaces = Autoload::getSearchPaths();
$trimmedType = ltrim($type, '\\');
foreach ($registeredNamespaces as $nsPrefix) {
if ($nsPrefix === '') {
continue;
}
if (class_exists($nsPrefix.'\\'.$trimmedType) === true) {
$reportClassName = $nsPrefix.'\\'.$trimmedType;
break;
}
}
}//end if
if ($reportClassName === '') {
$error = "ERROR: Class file for report \"$type\" not found".PHP_EOL;
throw new DeepExitException($error, 3);
}
$reportClass = new $reportClassName();
if (($reportClass instanceof Report) === false) {
throw new RuntimeException('Class "'.$reportClassName.'" must implement the "PHP_CodeSniffer\Report" interface.');
}
$this->reports[$type] = [
'output' => $output,
'class' => $reportClass,
];
if ($output === null) {
// Using a temp file.
// This needs to be set in the constructor so that all
// child procs use the same report file when running in parallel.
$this->tmpFiles[$type] = tempnam(sys_get_temp_dir(), 'phpcs');
file_put_contents($this->tmpFiles[$type], '');
} else {
file_put_contents($output, '');
}
}//end foreach
}//end __construct()
/**
* Generates and prints final versions of all reports.
*
* Returns TRUE if any of the reports output content to the screen
* or FALSE if all reports were silently printed to a file.
*
* @return bool
*/
public function printReports()
{
$toScreen = false;
foreach ($this->reports as $type => $report) {
if ($report['output'] === null) {
$toScreen = true;
}
$this->printReport($type);
}
return $toScreen;
}//end printReports()
/**
* Generates and prints a single final report.
*
* @param string $report The report type to print.
*
* @return void
*/
public function printReport($report)
{
$reportClass = $this->reports[$report]['class'];
$reportFile = $this->reports[$report]['output'];
if ($reportFile !== null) {
$filename = $reportFile;
$toScreen = false;
} else {
if (isset($this->tmpFiles[$report]) === true) {
$filename = $this->tmpFiles[$report];
} else {
$filename = null;
}
$toScreen = true;
}
$reportCache = '';
if ($filename !== null) {
$reportCache = file_get_contents($filename);
}
ob_start();
$reportClass->generate(
$reportCache,
$this->totalFiles,
$this->totalErrors,
$this->totalWarnings,
$this->totalFixable,
$this->config->showSources,
$this->config->reportWidth,
$this->config->interactive,
$toScreen
);
$generatedReport = ob_get_contents();
ob_end_clean();
if ($this->config->colors !== true || $reportFile !== null) {
$generatedReport = preg_replace('`\033\[[0-9;]+m`', '', $generatedReport);
}
if ($reportFile !== null) {
if (PHP_CODESNIFFER_VERBOSITY > 0) {
echo $generatedReport;
}
file_put_contents($reportFile, $generatedReport.PHP_EOL);
} else {
echo $generatedReport;
if ($filename !== null && file_exists($filename) === true) {
unlink($filename);
unset($this->tmpFiles[$report]);
}
}
}//end printReport()
/**
* Caches the result of a single processed file for all reports.
*
* The report content that is generated is appended to the output file
* assigned to each report. This content may be an intermediate report format
* and not reflect the final report output.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file that has been processed.
*
* @return void
*/
public function cacheFileReport(File $phpcsFile)
{
if (isset($this->config->reports) === false) {
// This happens during unit testing, or any time someone just wants
// the error data and not the printed report.
return;
}
$reportData = $this->prepareFileReport($phpcsFile);
$errorsShown = false;
foreach ($this->reports as $type => $report) {
$reportClass = $report['class'];
ob_start();
$result = $reportClass->generateFileReport($reportData, $phpcsFile, $this->config->showSources, $this->config->reportWidth);
if ($result === true) {
$errorsShown = true;
}
$generatedReport = ob_get_contents();
ob_end_clean();
if ($report['output'] === null) {
// Using a temp file.
if (isset($this->tmpFiles[$type]) === false) {
// When running in interactive mode, the reporter prints the full
// report many times, which will unlink the temp file. So we need
// to create a new one if it doesn't exist.
$this->tmpFiles[$type] = tempnam(sys_get_temp_dir(), 'phpcs');
file_put_contents($this->tmpFiles[$type], '');
}
file_put_contents($this->tmpFiles[$type], $generatedReport, (FILE_APPEND | LOCK_EX));
} else {
file_put_contents($report['output'], $generatedReport, (FILE_APPEND | LOCK_EX));
}//end if
}//end foreach
if ($errorsShown === true || PHP_CODESNIFFER_CBF === true) {
$this->totalFiles++;
$this->totalErrors += $reportData['errors'];
$this->totalWarnings += $reportData['warnings'];
// When PHPCBF is running, we need to use the fixable error values
// after the report has run and fixed what it can.
if (PHP_CODESNIFFER_CBF === true) {
$this->totalFixable += $phpcsFile->getFixableCount();
$this->totalFixed += $phpcsFile->getFixedCount();
} else {
$this->totalFixable += $reportData['fixable'];
}
}
}//end cacheFileReport()
/**
* Generate summary information to be used during report generation.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file that has been processed.
*
* @return array
*/
public function prepareFileReport(File $phpcsFile)
{
$report = [
'filename' => Common::stripBasepath($phpcsFile->getFilename(), $this->config->basepath),
'errors' => $phpcsFile->getErrorCount(),
'warnings' => $phpcsFile->getWarningCount(),
'fixable' => $phpcsFile->getFixableCount(),
'messages' => [],
];
if ($report['errors'] === 0 && $report['warnings'] === 0) {
// Prefect score!
return $report;
}
if ($this->config->recordErrors === false) {
$message = 'Errors are not being recorded but this report requires error messages. ';
$message .= 'This report will not show the correct information.';
$report['messages'][1][1] = [
[
'message' => $message,
'source' => 'Internal.RecordErrors',
'severity' => 5,
'fixable' => false,
'type' => 'ERROR',
],
];
return $report;
}
$errors = [];
// Merge errors and warnings.
foreach ($phpcsFile->getErrors() as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
$newErrors = [];
foreach ($colErrors as $data) {
$newErrors[] = [
'message' => $data['message'],
'source' => $data['source'],
'severity' => $data['severity'],
'fixable' => $data['fixable'],
'type' => 'ERROR',
];
}
$errors[$line][$column] = $newErrors;
}
ksort($errors[$line]);
}//end foreach
foreach ($phpcsFile->getWarnings() as $line => $lineWarnings) {
foreach ($lineWarnings as $column => $colWarnings) {
$newWarnings = [];
foreach ($colWarnings as $data) {
$newWarnings[] = [
'message' => $data['message'],
'source' => $data['source'],
'severity' => $data['severity'],
'fixable' => $data['fixable'],
'type' => 'WARNING',
];
}
if (isset($errors[$line]) === false) {
$errors[$line] = [];
}
if (isset($errors[$line][$column]) === true) {
$errors[$line][$column] = array_merge(
$newWarnings,
$errors[$line][$column]
);
} else {
$errors[$line][$column] = $newWarnings;
}
}//end foreach
ksort($errors[$line]);
}//end foreach
ksort($errors);
$report['messages'] = $errors;
return $report;
}//end prepareFileReport()
}//end class

View file

@ -0,0 +1,253 @@
<?php
/**
* CBF report for PHP_CodeSniffer.
*
* This report implements the various auto-fixing features of the
* PHPCBF script and is not intended (or allowed) to be selected as a
* report from the command line.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Exceptions\DeepExitException;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util;
class Cbf implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
$errors = $phpcsFile->getFixableCount();
if ($errors !== 0) {
if (PHP_CODESNIFFER_VERBOSITY > 0) {
ob_end_clean();
$startTime = microtime(true);
echo "\t=> Fixing file: $errors/$errors violations remaining";
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo PHP_EOL;
}
}
$fixed = $phpcsFile->fixer->fixFile();
}
if ($phpcsFile->config->stdin === true) {
// Replacing STDIN, so output current file to STDOUT
// even if nothing was fixed. Exit here because we
// can't process any more than 1 file in this setup.
$fixedContent = $phpcsFile->fixer->getContents();
throw new DeepExitException($fixedContent, 1);
}
if ($errors === 0) {
return false;
}
if (PHP_CODESNIFFER_VERBOSITY > 0) {
if ($fixed === false) {
echo 'ERROR';
} else {
echo 'DONE';
}
$timeTaken = ((microtime(true) - $startTime) * 1000);
if ($timeTaken < 1000) {
$timeTaken = round($timeTaken);
echo " in {$timeTaken}ms".PHP_EOL;
} else {
$timeTaken = round(($timeTaken / 1000), 2);
echo " in $timeTaken secs".PHP_EOL;
}
}
if ($fixed === true) {
// The filename in the report may be truncated due to a basepath setting
// but we are using it for writing here and not display,
// so find the correct path if basepath is in use.
$newFilename = $report['filename'].$phpcsFile->config->suffix;
if ($phpcsFile->config->basepath !== null) {
$newFilename = $phpcsFile->config->basepath.DIRECTORY_SEPARATOR.$newFilename;
}
$newContent = $phpcsFile->fixer->getContents();
file_put_contents($newFilename, $newContent);
if (PHP_CODESNIFFER_VERBOSITY > 0) {
if ($newFilename === $report['filename']) {
echo "\t=> File was overwritten".PHP_EOL;
} else {
echo "\t=> Fixed file written to ".basename($newFilename).PHP_EOL;
}
}
}
if (PHP_CODESNIFFER_VERBOSITY > 0) {
ob_start();
}
$errorCount = $phpcsFile->getErrorCount();
$warningCount = $phpcsFile->getWarningCount();
$fixableCount = $phpcsFile->getFixableCount();
$fixedCount = ($errors - $fixableCount);
echo $report['filename'].">>$errorCount>>$warningCount>>$fixableCount>>$fixedCount".PHP_EOL;
return $fixed;
}//end generateFileReport()
/**
* Prints a summary of fixed files.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
$lines = explode(PHP_EOL, $cachedData);
array_pop($lines);
if (empty($lines) === true) {
echo PHP_EOL.'No fixable errors were found'.PHP_EOL;
return;
}
$reportFiles = [];
$maxLength = 0;
$totalFixed = 0;
$failures = 0;
foreach ($lines as $line) {
$parts = explode('>>', $line);
$fileLen = strlen($parts[0]);
$reportFiles[$parts[0]] = [
'errors' => $parts[1],
'warnings' => $parts[2],
'fixable' => $parts[3],
'fixed' => $parts[4],
'strlen' => $fileLen,
];
$maxLength = max($maxLength, $fileLen);
$totalFixed += $parts[4];
if ($parts[3] > 0) {
$failures++;
}
}
$width = min($width, ($maxLength + 21));
$width = max($width, 70);
echo PHP_EOL."\033[1m".'PHPCBF RESULT SUMMARY'."\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
echo "\033[1m".'FILE'.str_repeat(' ', ($width - 20)).'FIXED REMAINING'."\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
foreach ($reportFiles as $file => $data) {
$padding = ($width - 18 - $data['strlen']);
if ($padding < 0) {
$file = '...'.substr($file, (($padding * -1) + 3));
$padding = 0;
}
echo $file.str_repeat(' ', $padding).' ';
if ($data['fixable'] > 0) {
echo "\033[31mFAILED TO FIX\033[0m".PHP_EOL;
continue;
}
$remaining = ($data['errors'] + $data['warnings']);
if ($data['fixed'] !== 0) {
echo $data['fixed'];
echo str_repeat(' ', (7 - strlen((string) $data['fixed'])));
} else {
echo '0 ';
}
if ($remaining !== 0) {
echo $remaining;
} else {
echo '0';
}
echo PHP_EOL;
}//end foreach
echo str_repeat('-', $width).PHP_EOL;
echo "\033[1mA TOTAL OF $totalFixed ERROR";
if ($totalFixed !== 1) {
echo 'S';
}
$numFiles = count($reportFiles);
echo ' WERE FIXED IN '.$numFiles.' FILE';
if ($numFiles !== 1) {
echo 'S';
}
echo "\033[0m";
if ($failures > 0) {
echo PHP_EOL.str_repeat('-', $width).PHP_EOL;
echo "\033[1mPHPCBF FAILED TO FIX $failures FILE";
if ($failures !== 1) {
echo 'S';
}
echo "\033[0m";
}
echo PHP_EOL.str_repeat('-', $width).PHP_EOL.PHP_EOL;
if ($toScreen === true && $interactive === false) {
Util\Timing::printRunTime();
}
}//end generate()
}//end class

View file

@ -0,0 +1,109 @@
<?php
/**
* Checkstyle report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Files\File;
class Checkstyle implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
$out = new \XMLWriter;
$out->openMemory();
$out->setIndent(true);
if ($report['errors'] === 0 && $report['warnings'] === 0) {
// Nothing to print.
return false;
}
$out->startElement('file');
$out->writeAttribute('name', $report['filename']);
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$error['type'] = strtolower($error['type']);
if ($phpcsFile->config->encoding !== 'utf-8') {
$error['message'] = iconv($phpcsFile->config->encoding, 'utf-8', $error['message']);
}
$out->startElement('error');
$out->writeAttribute('line', $line);
$out->writeAttribute('column', $column);
$out->writeAttribute('severity', $error['type']);
$out->writeAttribute('message', $error['message']);
$out->writeAttribute('source', $error['source']);
$out->endElement();
}
}
}//end foreach
$out->endElement();
echo $out->flush();
return true;
}//end generateFileReport()
/**
* Prints all violations for processed files, in a Checkstyle format.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
echo '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
echo '<checkstyle version="'.Config::VERSION.'">'.PHP_EOL;
echo $cachedData;
echo '</checkstyle>'.PHP_EOL;
}//end generate()
}//end class

View file

@ -0,0 +1,362 @@
<?php
/**
* Full report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util;
class Code implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
if ($report['errors'] === 0 && $report['warnings'] === 0) {
// Nothing to print.
return false;
}
// How many lines to show about and below the error line.
$surroundingLines = 2;
$file = $report['filename'];
$tokens = $phpcsFile->getTokens();
if (empty($tokens) === true) {
if (PHP_CODESNIFFER_VERBOSITY === 1) {
$startTime = microtime(true);
echo 'CODE report is parsing '.basename($file).' ';
} else if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "CODE report is forcing parse of $file".PHP_EOL;
}
try {
$phpcsFile->parse();
} catch (\Exception $e) {
// This is a second parse, so ignore exceptions.
// They would have been added to the file's error list already.
}
if (PHP_CODESNIFFER_VERBOSITY === 1) {
$timeTaken = ((microtime(true) - $startTime) * 1000);
if ($timeTaken < 1000) {
$timeTaken = round($timeTaken);
echo "DONE in {$timeTaken}ms";
} else {
$timeTaken = round(($timeTaken / 1000), 2);
echo "DONE in $timeTaken secs";
}
echo PHP_EOL;
}
$tokens = $phpcsFile->getTokens();
}//end if
// Create an array that maps lines to the first token on the line.
$lineTokens = [];
$lastLine = 0;
$stackPtr = 0;
foreach ($tokens as $stackPtr => $token) {
if ($token['line'] !== $lastLine) {
if ($lastLine > 0) {
$lineTokens[$lastLine]['end'] = ($stackPtr - 1);
}
$lastLine++;
$lineTokens[$lastLine] = [
'start' => $stackPtr,
'end' => null,
];
}
}
// Make sure the last token in the file sits on an imaginary
// last line so it is easier to generate code snippets at the
// end of the file.
$lineTokens[$lastLine]['end'] = $stackPtr;
// Determine the longest code line we will be showing.
$maxSnippetLength = 0;
$eolLen = strlen($phpcsFile->eolChar);
foreach ($report['messages'] as $line => $lineErrors) {
$startLine = max(($line - $surroundingLines), 1);
$endLine = min(($line + $surroundingLines), $lastLine);
$maxLineNumLength = strlen($endLine);
for ($i = $startLine; $i <= $endLine; $i++) {
if ($i === 1) {
continue;
}
$lineLength = ($tokens[($lineTokens[$i]['start'] - 1)]['column'] + $tokens[($lineTokens[$i]['start'] - 1)]['length'] - $eolLen);
$maxSnippetLength = max($lineLength, $maxSnippetLength);
}
}
$maxSnippetLength += ($maxLineNumLength + 8);
// Determine the longest error message we will be showing.
$maxErrorLength = 0;
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$length = strlen($error['message']);
if ($showSources === true) {
$length += (strlen($error['source']) + 3);
}
$maxErrorLength = max($maxErrorLength, ($length + 1));
}
}
}
// The padding that all lines will require that are printing an error message overflow.
if ($report['warnings'] > 0) {
$typeLength = 7;
} else {
$typeLength = 5;
}
$errorPadding = str_repeat(' ', ($maxLineNumLength + 7));
$errorPadding .= str_repeat(' ', $typeLength);
$errorPadding .= ' ';
if ($report['fixable'] > 0) {
$errorPadding .= ' ';
}
$errorPaddingLength = strlen($errorPadding);
// The maximum amount of space an error message can use.
$maxErrorSpace = ($width - $errorPaddingLength);
if ($showSources === true) {
// Account for the chars used to print colors.
$maxErrorSpace += 8;
}
// Figure out the max report width we need and can use.
$fileLength = strlen($file);
$maxWidth = max(($fileLength + 6), ($maxErrorLength + $errorPaddingLength));
$width = max(min($width, $maxWidth), $maxSnippetLength);
if ($width < 70) {
$width = 70;
}
// Print the file header.
echo PHP_EOL."\033[1mFILE: ";
if ($fileLength <= ($width - 6)) {
echo $file;
} else {
echo '...'.substr($file, ($fileLength - ($width - 6)));
}
echo "\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
echo "\033[1m".'FOUND '.$report['errors'].' ERROR';
if ($report['errors'] !== 1) {
echo 'S';
}
if ($report['warnings'] > 0) {
echo ' AND '.$report['warnings'].' WARNING';
if ($report['warnings'] !== 1) {
echo 'S';
}
}
echo ' AFFECTING '.count($report['messages']).' LINE';
if (count($report['messages']) !== 1) {
echo 'S';
}
echo "\033[0m".PHP_EOL;
foreach ($report['messages'] as $line => $lineErrors) {
$startLine = max(($line - $surroundingLines), 1);
$endLine = min(($line + $surroundingLines), $lastLine);
$snippet = '';
if (isset($lineTokens[$startLine]) === true) {
for ($i = $lineTokens[$startLine]['start']; $i <= $lineTokens[$endLine]['end']; $i++) {
$snippetLine = $tokens[$i]['line'];
if ($lineTokens[$snippetLine]['start'] === $i) {
// Starting a new line.
if ($snippetLine === $line) {
$snippet .= "\033[1m".'>> ';
} else {
$snippet .= ' ';
}
$snippet .= str_repeat(' ', ($maxLineNumLength - strlen($snippetLine)));
$snippet .= $snippetLine.': ';
if ($snippetLine === $line) {
$snippet .= "\033[0m";
}
}
if (isset($tokens[$i]['orig_content']) === true) {
$tokenContent = $tokens[$i]['orig_content'];
} else {
$tokenContent = $tokens[$i]['content'];
}
if (strpos($tokenContent, "\t") !== false) {
$token = $tokens[$i];
$token['content'] = $tokenContent;
if (stripos(PHP_OS, 'WIN') === 0) {
$tab = "\000";
} else {
$tab = "\033[30;1m»\033[0m";
}
$phpcsFile->tokenizer->replaceTabsInToken($token, $tab, "\000");
$tokenContent = $token['content'];
}
$tokenContent = Util\Common::prepareForOutput($tokenContent, ["\r", "\n", "\t"]);
$tokenContent = str_replace("\000", ' ', $tokenContent);
$underline = false;
if ($snippetLine === $line && isset($lineErrors[$tokens[$i]['column']]) === true) {
$underline = true;
}
// Underline invisible characters as well.
if ($underline === true && trim($tokenContent) === '') {
$snippet .= "\033[4m".' '."\033[0m".$tokenContent;
} else {
if ($underline === true) {
$snippet .= "\033[4m";
}
$snippet .= $tokenContent;
if ($underline === true) {
$snippet .= "\033[0m";
}
}
}//end for
}//end if
echo str_repeat('-', $width).PHP_EOL;
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$padding = ($maxLineNumLength - strlen($line));
echo 'LINE '.str_repeat(' ', $padding).$line.': ';
if ($error['type'] === 'ERROR') {
echo "\033[31mERROR\033[0m";
if ($report['warnings'] > 0) {
echo ' ';
}
} else {
echo "\033[33mWARNING\033[0m";
}
echo ' ';
if ($report['fixable'] > 0) {
echo '[';
if ($error['fixable'] === true) {
echo 'x';
} else {
echo ' ';
}
echo '] ';
}
$message = $error['message'];
$message = str_replace("\n", "\n".$errorPadding, $message);
if ($showSources === true) {
$message = "\033[1m".$message."\033[0m".' ('.$error['source'].')';
}
$errorMsg = wordwrap(
$message,
$maxErrorSpace,
PHP_EOL.$errorPadding
);
echo $errorMsg.PHP_EOL;
}//end foreach
}//end foreach
echo str_repeat('-', $width).PHP_EOL;
echo rtrim($snippet).PHP_EOL;
}//end foreach
echo str_repeat('-', $width).PHP_EOL;
if ($report['fixable'] > 0) {
echo "\033[1m".'PHPCBF CAN FIX THE '.$report['fixable'].' MARKED SNIFF VIOLATIONS AUTOMATICALLY'."\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
}
return true;
}//end generateFileReport()
/**
* Prints all errors and warnings for each file processed.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
if ($cachedData === '') {
return;
}
echo $cachedData;
if ($toScreen === true && $interactive === false) {
Util\Timing::printRunTime();
}
}//end generate()
}//end class

View file

@ -0,0 +1,91 @@
<?php
/**
* CSV report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
class Csv implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
if ($report['errors'] === 0 && $report['warnings'] === 0) {
// Nothing to print.
return false;
}
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$filename = str_replace('"', '\"', $report['filename']);
$message = str_replace('"', '\"', $error['message']);
$type = strtolower($error['type']);
$source = $error['source'];
$severity = $error['severity'];
$fixable = (int) $error['fixable'];
echo "\"$filename\",$line,$column,$type,\"$message\",$source,$severity,$fixable".PHP_EOL;
}
}
}
return true;
}//end generateFileReport()
/**
* Generates a csv report.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
echo 'File,Line,Column,Type,Message,Source,Severity,Fixable'.PHP_EOL;
echo $cachedData;
}//end generate()
}//end class

View file

@ -0,0 +1,130 @@
<?php
/**
* Diff report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
class Diff implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
$errors = $phpcsFile->getFixableCount();
if ($errors === 0) {
return false;
}
$phpcsFile->disableCaching();
$tokens = $phpcsFile->getTokens();
if (empty($tokens) === true) {
if (PHP_CODESNIFFER_VERBOSITY === 1) {
$startTime = microtime(true);
echo 'DIFF report is parsing '.basename($report['filename']).' ';
} else if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo 'DIFF report is forcing parse of '.$report['filename'].PHP_EOL;
}
$phpcsFile->parse();
if (PHP_CODESNIFFER_VERBOSITY === 1) {
$timeTaken = ((microtime(true) - $startTime) * 1000);
if ($timeTaken < 1000) {
$timeTaken = round($timeTaken);
echo "DONE in {$timeTaken}ms";
} else {
$timeTaken = round(($timeTaken / 1000), 2);
echo "DONE in $timeTaken secs";
}
echo PHP_EOL;
}
$phpcsFile->fixer->startFile($phpcsFile);
}//end if
if (PHP_CODESNIFFER_VERBOSITY > 1) {
ob_end_clean();
echo "\t*** START FILE FIXING ***".PHP_EOL;
}
$fixed = $phpcsFile->fixer->fixFile();
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "\t*** END FILE FIXING ***".PHP_EOL;
ob_start();
}
if ($fixed === false) {
return false;
}
$diff = $phpcsFile->fixer->generateDiff();
if ($diff === '') {
// Nothing to print.
return false;
}
echo $diff.PHP_EOL;
return true;
}//end generateFileReport()
/**
* Prints all errors and warnings for each file processed.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
echo $cachedData;
if ($toScreen === true && $cachedData !== '') {
echo PHP_EOL;
}
}//end generate()
}//end class

View file

@ -0,0 +1,90 @@
<?php
/**
* Emacs report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
class Emacs implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
if ($report['errors'] === 0 && $report['warnings'] === 0) {
// Nothing to print.
return false;
}
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$message = $error['message'];
if ($showSources === true) {
$message .= ' ('.$error['source'].')';
}
$type = strtolower($error['type']);
echo $report['filename'].':'.$line.':'.$column.': '.$type.' - '.$message.PHP_EOL;
}
}
}
return true;
}//end generateFileReport()
/**
* Generates an emacs report.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
echo $cachedData;
}//end generate()
}//end class

View file

@ -0,0 +1,231 @@
<?php
/**
* Full report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util;
class Full implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
if ($report['errors'] === 0 && $report['warnings'] === 0) {
// Nothing to print.
return false;
}
// The length of the word ERROR or WARNING; used for padding.
if ($report['warnings'] > 0) {
$typeLength = 7;
} else {
$typeLength = 5;
}
// Work out the max line number length for formatting.
$maxLineNumLength = max(array_map('strlen', array_keys($report['messages'])));
// The padding that all lines will require that are
// printing an error message overflow.
$paddingLine2 = str_repeat(' ', ($maxLineNumLength + 1));
$paddingLine2 .= ' | ';
$paddingLine2 .= str_repeat(' ', $typeLength);
$paddingLine2 .= ' | ';
if ($report['fixable'] > 0) {
$paddingLine2 .= ' ';
}
$paddingLength = strlen($paddingLine2);
// Make sure the report width isn't too big.
$maxErrorLength = 0;
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$length = strlen($error['message']);
if ($showSources === true) {
$length += (strlen($error['source']) + 3);
}
$maxErrorLength = max($maxErrorLength, ($length + 1));
}
}
}
$file = $report['filename'];
$fileLength = strlen($file);
$maxWidth = max(($fileLength + 6), ($maxErrorLength + $paddingLength));
$width = min($width, $maxWidth);
if ($width < 70) {
$width = 70;
}
echo PHP_EOL."\033[1mFILE: ";
if ($fileLength <= ($width - 6)) {
echo $file;
} else {
echo '...'.substr($file, ($fileLength - ($width - 6)));
}
echo "\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
echo "\033[1m".'FOUND '.$report['errors'].' ERROR';
if ($report['errors'] !== 1) {
echo 'S';
}
if ($report['warnings'] > 0) {
echo ' AND '.$report['warnings'].' WARNING';
if ($report['warnings'] !== 1) {
echo 'S';
}
}
echo ' AFFECTING '.count($report['messages']).' LINE';
if (count($report['messages']) !== 1) {
echo 'S';
}
echo "\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
// The maximum amount of space an error message can use.
$maxErrorSpace = ($width - $paddingLength - 1);
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$message = $error['message'];
$msgLines = [$message];
if (strpos($message, "\n") !== false) {
$msgLines = explode("\n", $message);
}
$errorMsg = '';
$lastLine = (count($msgLines) - 1);
foreach ($msgLines as $k => $msgLine) {
if ($k === 0) {
if ($showSources === true) {
$errorMsg .= "\033[1m";
}
} else {
$errorMsg .= PHP_EOL.$paddingLine2;
}
if ($k === $lastLine && $showSources === true) {
$msgLine .= "\033[0m".' ('.$error['source'].')';
}
$errorMsg .= wordwrap(
$msgLine,
$maxErrorSpace,
PHP_EOL.$paddingLine2
);
}
// The padding that goes on the front of the line.
$padding = ($maxLineNumLength - strlen($line));
echo ' '.str_repeat(' ', $padding).$line.' | ';
if ($error['type'] === 'ERROR') {
echo "\033[31mERROR\033[0m";
if ($report['warnings'] > 0) {
echo ' ';
}
} else {
echo "\033[33mWARNING\033[0m";
}
echo ' | ';
if ($report['fixable'] > 0) {
echo '[';
if ($error['fixable'] === true) {
echo 'x';
} else {
echo ' ';
}
echo '] ';
}
echo $errorMsg.PHP_EOL;
}//end foreach
}//end foreach
}//end foreach
echo str_repeat('-', $width).PHP_EOL;
if ($report['fixable'] > 0) {
echo "\033[1m".'PHPCBF CAN FIX THE '.$report['fixable'].' MARKED SNIFF VIOLATIONS AUTOMATICALLY'."\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
}
echo PHP_EOL;
return true;
}//end generateFileReport()
/**
* Prints all errors and warnings for each file processed.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
if ($cachedData === '') {
return;
}
echo $cachedData;
if ($toScreen === true && $interactive === false) {
Util\Timing::printRunTime();
}
}//end generate()
}//end class

View file

@ -0,0 +1,91 @@
<?php
/**
* Git blame report for PHP_CodeSniffer.
*
* @author Ben Selby <benmatselby@gmail.com>
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Exceptions\DeepExitException;
class Gitblame extends VersionControl
{
/**
* The name of the report we want in the output
*
* @var string
*/
protected $reportName = 'GIT';
/**
* Extract the author from a blame line.
*
* @param string $line Line to parse.
*
* @return mixed string or false if impossible to recover.
*/
protected function getAuthor($line)
{
$blameParts = [];
$line = preg_replace('|\s+|', ' ', $line);
preg_match(
'|\(.+[0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]+\)|',
$line,
$blameParts
);
if (isset($blameParts[0]) === false) {
return false;
}
$parts = explode(' ', $blameParts[0]);
if (count($parts) < 2) {
return false;
}
$parts = array_slice($parts, 0, (count($parts) - 2));
$author = preg_replace('|\(|', '', implode(' ', $parts));
return $author;
}//end getAuthor()
/**
* Gets the blame output.
*
* @param string $filename File to blame.
*
* @return array
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException
*/
protected function getBlameContent($filename)
{
$cwd = getcwd();
chdir(dirname($filename));
$command = 'git blame --date=short "'.$filename.'" 2>&1';
$handle = popen($command, 'r');
if ($handle === false) {
$error = 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL;
throw new DeepExitException($error, 3);
}
$rawContent = stream_get_contents($handle);
pclose($handle);
$blames = explode("\n", $rawContent);
chdir($cwd);
return $blames;
}//end getBlameContent()
}//end class

View file

@ -0,0 +1,110 @@
<?php
/**
* Mercurial blame report for PHP_CodeSniffer.
*
* @author Ben Selby <benmatselby@gmail.com>
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Exceptions\DeepExitException;
class Hgblame extends VersionControl
{
/**
* The name of the report we want in the output
*
* @var string
*/
protected $reportName = 'MERCURIAL';
/**
* Extract the author from a blame line.
*
* @param string $line Line to parse.
*
* @return mixed string or false if impossible to recover.
*/
protected function getAuthor($line)
{
$blameParts = [];
$line = preg_replace('|\s+|', ' ', $line);
preg_match(
'|(.+[0-9]{2}:[0-9]{2}:[0-9]{2}\s[0-9]{4}\s.[0-9]{4}:)|',
$line,
$blameParts
);
if (isset($blameParts[0]) === false) {
return false;
}
$parts = explode(' ', $blameParts[0]);
if (count($parts) < 6) {
return false;
}
$parts = array_slice($parts, 0, (count($parts) - 6));
return trim(preg_replace('|<.+>|', '', implode(' ', $parts)));
}//end getAuthor()
/**
* Gets the blame output.
*
* @param string $filename File to blame.
*
* @return array
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException
*/
protected function getBlameContent($filename)
{
$cwd = getcwd();
$fileParts = explode(DIRECTORY_SEPARATOR, $filename);
$found = false;
$location = '';
while (empty($fileParts) === false) {
array_pop($fileParts);
$location = implode(DIRECTORY_SEPARATOR, $fileParts);
if (is_dir($location.DIRECTORY_SEPARATOR.'.hg') === true) {
$found = true;
break;
}
}
if ($found === true) {
chdir($location);
} else {
$error = 'ERROR: Could not locate .hg directory '.PHP_EOL.PHP_EOL;
throw new DeepExitException($error, 3);
}
$command = 'hg blame -u -d -v "'.$filename.'" 2>&1';
$handle = popen($command, 'r');
if ($handle === false) {
$error = 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL;
throw new DeepExitException($error, 3);
}
$rawContent = stream_get_contents($handle);
pclose($handle);
$blames = explode("\n", $rawContent);
chdir($cwd);
return $blames;
}//end getBlameContent()
}//end class

View file

@ -0,0 +1,172 @@
<?php
/**
* Info report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Timing;
class Info implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
$metrics = $phpcsFile->getMetrics();
foreach ($metrics as $metric => $data) {
foreach ($data['values'] as $value => $count) {
echo "$metric>>$value>>$count".PHP_EOL;
}
}
return true;
}//end generateFileReport()
/**
* Prints the source of all errors and warnings.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
$lines = explode(PHP_EOL, $cachedData);
array_pop($lines);
if (empty($lines) === true) {
return;
}
$metrics = [];
foreach ($lines as $line) {
$parts = explode('>>', $line);
$metric = $parts[0];
$value = $parts[1];
$count = $parts[2];
if (isset($metrics[$metric]) === false) {
$metrics[$metric] = [];
}
if (isset($metrics[$metric][$value]) === false) {
$metrics[$metric][$value] = $count;
} else {
$metrics[$metric][$value] += $count;
}
}
ksort($metrics);
echo PHP_EOL."\033[1m".'PHP CODE SNIFFER INFORMATION REPORT'."\033[0m".PHP_EOL;
echo str_repeat('-', 70).PHP_EOL;
foreach ($metrics as $metric => $values) {
if (count($values) === 1) {
$count = reset($values);
$value = key($values);
echo "$metric: \033[4m$value\033[0m [$count/$count, 100%]".PHP_EOL;
} else {
$totalCount = 0;
$valueWidth = 0;
foreach ($values as $value => $count) {
$totalCount += $count;
$valueWidth = max($valueWidth, strlen($value));
}
// Length of the total string, plus however many
// thousands separators there are.
$countWidth = strlen($totalCount);
$thousandSeparatorCount = floor($countWidth / 3);
$countWidth += $thousandSeparatorCount;
// Account for 'total' line.
$valueWidth = max(5, $valueWidth);
echo "$metric:".PHP_EOL;
ksort($values, SORT_NATURAL);
arsort($values);
$percentPrefixWidth = 0;
$percentWidth = 6;
foreach ($values as $value => $count) {
$percent = round(($count / $totalCount * 100), 2);
$percentPrefix = '';
if ($percent === 0.00) {
$percent = 0.01;
$percentPrefix = '<';
$percentPrefixWidth = 2;
$percentWidth = 4;
}
printf(
"\t%-{$valueWidth}s => %{$countWidth}s (%{$percentPrefixWidth}s%{$percentWidth}.2f%%)".PHP_EOL,
$value,
number_format($count),
$percentPrefix,
$percent
);
}
echo "\t".str_repeat('-', ($valueWidth + $countWidth + 15)).PHP_EOL;
printf(
"\t%-{$valueWidth}s => %{$countWidth}s (100.00%%)".PHP_EOL,
'total',
number_format($totalCount)
);
}//end if
echo PHP_EOL;
}//end foreach
echo str_repeat('-', 70).PHP_EOL;
if ($toScreen === true && $interactive === false) {
Timing::printRunTime();
}
}//end generate()
}//end class

View file

@ -0,0 +1,106 @@
<?php
/**
* JSON report for PHP_CodeSniffer.
*
* @author Jeffrey Fisher <jeffslofish@gmail.com>
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
class Json implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
$filename = str_replace('\\', '\\\\', $report['filename']);
$filename = str_replace('"', '\"', $filename);
$filename = str_replace('/', '\/', $filename);
echo '"'.$filename.'":{';
echo '"errors":'.$report['errors'].',"warnings":'.$report['warnings'].',"messages":[';
$messages = '';
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$error['message'] = str_replace("\n", '\n', $error['message']);
$error['message'] = str_replace("\r", '\r', $error['message']);
$error['message'] = str_replace("\t", '\t', $error['message']);
$fixable = false;
if ($error['fixable'] === true) {
$fixable = true;
}
$messagesObject = (object) $error;
$messagesObject->line = $line;
$messagesObject->column = $column;
$messagesObject->fixable = $fixable;
$messages .= json_encode($messagesObject).",";
}
}
}//end foreach
echo rtrim($messages, ',');
echo ']},';
return true;
}//end generateFileReport()
/**
* Generates a JSON report.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
echo '{"totals":{"errors":'.$totalErrors.',"warnings":'.$totalWarnings.',"fixable":'.$totalFixable.'},"files":{';
echo rtrim($cachedData, ',');
echo "}}".PHP_EOL;
}//end generate()
}//end class

View file

@ -0,0 +1,131 @@
<?php
/**
* JUnit report for PHP_CodeSniffer.
*
* @author Oleg Lobach <oleg@lobach.info>
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Files\File;
class Junit implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
$out = new \XMLWriter;
$out->openMemory();
$out->setIndent(true);
$out->startElement('testsuite');
$out->writeAttribute('name', $report['filename']);
$out->writeAttribute('errors', 0);
if (count($report['messages']) === 0) {
$out->writeAttribute('tests', 1);
$out->writeAttribute('failures', 0);
$out->startElement('testcase');
$out->writeAttribute('name', $report['filename']);
$out->endElement();
} else {
$failures = ($report['errors'] + $report['warnings']);
$out->writeAttribute('tests', $failures);
$out->writeAttribute('failures', $failures);
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$out->startElement('testcase');
$out->writeAttribute('name', $error['source'].' at '.$report['filename']." ($line:$column)");
$error['type'] = strtolower($error['type']);
if ($phpcsFile->config->encoding !== 'utf-8') {
$error['message'] = iconv($phpcsFile->config->encoding, 'utf-8', $error['message']);
}
$out->startElement('failure');
$out->writeAttribute('type', $error['type']);
$out->writeAttribute('message', $error['message']);
$out->endElement();
$out->endElement();
}
}
}
}//end if
$out->endElement();
echo $out->flush();
return true;
}//end generateFileReport()
/**
* Prints all violations for processed files, in a proprietary XML format.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
// Figure out the total number of tests.
$tests = 0;
$matches = [];
preg_match_all('/tests="([0-9]+)"/', $cachedData, $matches);
if (isset($matches[1]) === true) {
foreach ($matches[1] as $match) {
$tests += $match;
}
}
$failures = ($totalErrors + $totalWarnings);
echo '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
echo '<testsuites name="PHP_CodeSniffer '.Config::VERSION.'" errors="0" tests="'.$tests.'" failures="'.$failures.'">'.PHP_EOL;
echo $cachedData;
echo '</testsuites>'.PHP_EOL;
}//end generate()
}//end class

View file

@ -0,0 +1,242 @@
<?php
/**
* Notify-send report for PHP_CodeSniffer.
*
* Supported configuration parameters:
* - notifysend_path - Full path to notify-send cli command
* - notifysend_timeout - Timeout in milliseconds
* - notifysend_showok - Show "ok, all fine" messages (0/1)
*
* @author Christian Weiske <christian.weiske@netresearch.de>
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2012-2014 Christian Weiske
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Common;
class Notifysend implements Report
{
/**
* Notification timeout in milliseconds.
*
* @var integer
*/
protected $timeout = 3000;
/**
* Path to notify-send command.
*
* @var string
*/
protected $path = 'notify-send';
/**
* Show "ok, all fine" messages.
*
* @var boolean
*/
protected $showOk = true;
/**
* Version of installed notify-send executable.
*
* @var string
*/
protected $version = null;
/**
* Load configuration data.
*/
public function __construct()
{
$path = Config::getExecutablePath('notifysend');
if ($path !== null) {
$this->path = Common::escapeshellcmd($path);
}
$timeout = Config::getConfigData('notifysend_timeout');
if ($timeout !== null) {
$this->timeout = (int) $timeout;
}
$showOk = Config::getConfigData('notifysend_showok');
if ($showOk !== null) {
$this->showOk = (bool) $showOk;
}
$this->version = str_replace(
'notify-send ',
'',
exec($this->path.' --version')
);
}//end __construct()
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
echo $report['filename'].PHP_EOL;
// We want this file counted in the total number
// of checked files even if it has no errors.
return true;
}//end generateFileReport()
/**
* Generates a summary of errors and warnings for each file processed.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
$checkedFiles = explode(PHP_EOL, trim($cachedData));
$msg = $this->generateMessage($checkedFiles, $totalErrors, $totalWarnings);
if ($msg === null) {
if ($this->showOk === true) {
$this->notifyAllFine();
}
} else {
$this->notifyErrors($msg);
}
}//end generate()
/**
* Generate the error message to show to the user.
*
* @param string[] $checkedFiles The files checked during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
*
* @return string Error message or NULL if no error/warning found.
*/
protected function generateMessage($checkedFiles, $totalErrors, $totalWarnings)
{
if ($totalErrors === 0 && $totalWarnings === 0) {
// Nothing to print.
return null;
}
$totalFiles = count($checkedFiles);
$msg = '';
if ($totalFiles > 1) {
$msg .= 'Checked '.$totalFiles.' files'.PHP_EOL;
} else {
$msg .= $checkedFiles[0].PHP_EOL;
}
if ($totalWarnings > 0) {
$msg .= $totalWarnings.' warnings'.PHP_EOL;
}
if ($totalErrors > 0) {
$msg .= $totalErrors.' errors'.PHP_EOL;
}
return $msg;
}//end generateMessage()
/**
* Tell the user that all is fine and no error/warning has been found.
*
* @return void
*/
protected function notifyAllFine()
{
$cmd = $this->getBasicCommand();
$cmd .= ' -i info';
$cmd .= ' "PHP CodeSniffer: Ok"';
$cmd .= ' "All fine"';
exec($cmd);
}//end notifyAllFine()
/**
* Tell the user that errors/warnings have been found.
*
* @param string $msg Message to display.
*
* @return void
*/
protected function notifyErrors($msg)
{
$cmd = $this->getBasicCommand();
$cmd .= ' -i error';
$cmd .= ' "PHP CodeSniffer: Error"';
$cmd .= ' '.escapeshellarg(trim($msg));
exec($cmd);
}//end notifyErrors()
/**
* Generate and return the basic notify-send command string to execute.
*
* @return string Shell command with common parameters.
*/
protected function getBasicCommand()
{
$cmd = $this->path;
$cmd .= ' --category dev.validate';
$cmd .= ' -h int:transient:1';
$cmd .= ' -t '.(int) $this->timeout;
if (version_compare($this->version, '0.7.3', '>=') === true) {
$cmd .= ' -a phpcs';
}
return $cmd;
}//end getBasicCommand()
}//end class

View file

@ -0,0 +1,64 @@
<?php
/**
* An interface that PHP_CodeSniffer reports must implement.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
interface Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80);
/**
* Generate the actual report.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
);
}//end interface

View file

@ -0,0 +1,336 @@
<?php
/**
* Source report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Timing;
class Source implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
if ($report['errors'] === 0 && $report['warnings'] === 0) {
// Nothing to print.
return false;
}
$sources = [];
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$src = $error['source'];
if (isset($sources[$src]) === false) {
$sources[$src] = [
'fixable' => (int) $error['fixable'],
'count' => 1,
];
} else {
$sources[$src]['count']++;
}
}
}
}
foreach ($sources as $source => $data) {
echo $source.'>>'.$data['fixable'].'>>'.$data['count'].PHP_EOL;
}
return true;
}//end generateFileReport()
/**
* Prints the source of all errors and warnings.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
$lines = explode(PHP_EOL, $cachedData);
array_pop($lines);
if (empty($lines) === true) {
return;
}
$sources = [];
$maxLength = 0;
foreach ($lines as $line) {
$parts = explode('>>', $line);
$source = $parts[0];
$fixable = (bool) $parts[1];
$count = $parts[2];
if (isset($sources[$source]) === false) {
if ($showSources === true) {
$parts = null;
$sniff = $source;
} else {
$parts = explode('.', $source);
if ($parts[0] === 'Internal') {
$parts[2] = $parts[1];
$parts[1] = '';
}
$parts[1] = $this->makeFriendlyName($parts[1]);
$sniff = $this->makeFriendlyName($parts[2]);
if (isset($parts[3]) === true) {
$name = $this->makeFriendlyName($parts[3]);
$name[0] = strtolower($name[0]);
$sniff .= ' '.$name;
unset($parts[3]);
}
$parts[2] = $sniff;
}//end if
$maxLength = max($maxLength, strlen($sniff));
$sources[$source] = [
'count' => $count,
'fixable' => $fixable,
'parts' => $parts,
];
} else {
$sources[$source]['count'] += $count;
}//end if
}//end foreach
if ($showSources === true) {
$width = min($width, ($maxLength + 11));
} else {
$width = min($width, ($maxLength + 41));
}
$width = max($width, 70);
// Sort the data based on counts and source code.
$sourceCodes = array_keys($sources);
$counts = [];
foreach ($sources as $source => $data) {
$counts[$source] = $data['count'];
}
array_multisort($counts, SORT_DESC, $sourceCodes, SORT_ASC, SORT_NATURAL, $sources);
echo PHP_EOL."\033[1mPHP CODE SNIFFER VIOLATION SOURCE SUMMARY\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL."\033[1m";
if ($showSources === true) {
if ($totalFixable > 0) {
echo ' SOURCE'.str_repeat(' ', ($width - 15)).'COUNT'.PHP_EOL;
} else {
echo 'SOURCE'.str_repeat(' ', ($width - 11)).'COUNT'.PHP_EOL;
}
} else {
if ($totalFixable > 0) {
echo ' STANDARD CATEGORY SNIFF'.str_repeat(' ', ($width - 44)).'COUNT'.PHP_EOL;
} else {
echo 'STANDARD CATEGORY SNIFF'.str_repeat(' ', ($width - 40)).'COUNT'.PHP_EOL;
}
}
echo "\033[0m".str_repeat('-', $width).PHP_EOL;
$fixableSources = 0;
if ($showSources === true) {
$maxSniffWidth = ($width - 7);
} else {
$maxSniffWidth = ($width - 37);
}
if ($totalFixable > 0) {
$maxSniffWidth -= 4;
}
foreach ($sources as $source => $sourceData) {
if ($totalFixable > 0) {
echo '[';
if ($sourceData['fixable'] === true) {
echo 'x';
$fixableSources++;
} else {
echo ' ';
}
echo '] ';
}
if ($showSources === true) {
if (strlen($source) > $maxSniffWidth) {
$source = substr($source, 0, $maxSniffWidth);
}
echo $source;
if ($totalFixable > 0) {
echo str_repeat(' ', ($width - 9 - strlen($source)));
} else {
echo str_repeat(' ', ($width - 5 - strlen($source)));
}
} else {
$parts = $sourceData['parts'];
if (strlen($parts[0]) > 8) {
$parts[0] = substr($parts[0], 0, ((strlen($parts[0]) - 8) * -1));
}
echo $parts[0].str_repeat(' ', (10 - strlen($parts[0])));
$category = $parts[1];
if (strlen($category) > 18) {
$category = substr($category, 0, ((strlen($category) - 18) * -1));
}
echo $category.str_repeat(' ', (20 - strlen($category)));
$sniff = $parts[2];
if (strlen($sniff) > $maxSniffWidth) {
$sniff = substr($sniff, 0, $maxSniffWidth);
}
if ($totalFixable > 0) {
echo $sniff.str_repeat(' ', ($width - 39 - strlen($sniff)));
} else {
echo $sniff.str_repeat(' ', ($width - 35 - strlen($sniff)));
}
}//end if
echo $sourceData['count'].PHP_EOL;
}//end foreach
echo str_repeat('-', $width).PHP_EOL;
echo "\033[1m".'A TOTAL OF '.($totalErrors + $totalWarnings).' SNIFF VIOLATION';
if (($totalErrors + $totalWarnings) > 1) {
echo 'S';
}
echo ' WERE FOUND IN '.count($sources).' SOURCE';
if (count($sources) !== 1) {
echo 'S';
}
echo "\033[0m";
if ($totalFixable > 0) {
echo PHP_EOL.str_repeat('-', $width).PHP_EOL;
echo "\033[1mPHPCBF CAN FIX THE $fixableSources MARKED SOURCES AUTOMATICALLY ($totalFixable VIOLATIONS IN TOTAL)\033[0m";
}
echo PHP_EOL.str_repeat('-', $width).PHP_EOL.PHP_EOL;
if ($toScreen === true && $interactive === false) {
Timing::printRunTime();
}
}//end generate()
/**
* Converts a camel caps name into a readable string.
*
* @param string $name The camel caps name to convert.
*
* @return string
*/
public function makeFriendlyName($name)
{
if (trim($name) === '') {
return '';
}
$friendlyName = '';
$length = strlen($name);
$lastWasUpper = false;
$lastWasNumeric = false;
for ($i = 0; $i < $length; $i++) {
if (is_numeric($name[$i]) === true) {
if ($lastWasNumeric === false) {
$friendlyName .= ' ';
}
$lastWasUpper = false;
$lastWasNumeric = true;
} else {
$lastWasNumeric = false;
$char = strtolower($name[$i]);
if ($char === $name[$i]) {
// Lowercase.
$lastWasUpper = false;
} else {
// Uppercase.
if ($lastWasUpper === false) {
$friendlyName .= ' ';
if ($i < ($length - 1)) {
$next = $name[($i + 1)];
if (strtolower($next) === $next) {
// Next char is lowercase so it is a word boundary.
$name[$i] = strtolower($name[$i]);
}
}
}
$lastWasUpper = true;
}
}//end if
$friendlyName .= $name[$i];
}//end for
$friendlyName = trim($friendlyName);
$friendlyName[0] = strtoupper($friendlyName[0]);
return $friendlyName;
}//end makeFriendlyName()
}//end class

View file

@ -0,0 +1,183 @@
<?php
/**
* Summary report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util;
class Summary implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
if (PHP_CODESNIFFER_VERBOSITY === 0
&& $report['errors'] === 0
&& $report['warnings'] === 0
) {
// Nothing to print.
return false;
}
echo $report['filename'].'>>'.$report['errors'].'>>'.$report['warnings'].PHP_EOL;
return true;
}//end generateFileReport()
/**
* Generates a summary of errors and warnings for each file processed.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
$lines = explode(PHP_EOL, $cachedData);
array_pop($lines);
if (empty($lines) === true) {
return;
}
$reportFiles = [];
$maxLength = 0;
foreach ($lines as $line) {
$parts = explode('>>', $line);
$fileLen = strlen($parts[0]);
$reportFiles[$parts[0]] = [
'errors' => $parts[1],
'warnings' => $parts[2],
'strlen' => $fileLen,
];
$maxLength = max($maxLength, $fileLen);
}
uksort(
$reportFiles,
function ($keyA, $keyB) {
$pathPartsA = explode(DIRECTORY_SEPARATOR, $keyA);
$pathPartsB = explode(DIRECTORY_SEPARATOR, $keyB);
do {
$partA = array_shift($pathPartsA);
$partB = array_shift($pathPartsB);
} while ($partA === $partB && empty($pathPartsA) === false && empty($pathPartsB) === false);
if (empty($pathPartsA) === false && empty($pathPartsB) === true) {
return 1;
} else if (empty($pathPartsA) === true && empty($pathPartsB) === false) {
return -1;
} else {
return strcasecmp($partA, $partB);
}
}
);
$width = min($width, ($maxLength + 21));
$width = max($width, 70);
echo PHP_EOL."\033[1m".'PHP CODE SNIFFER REPORT SUMMARY'."\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
echo "\033[1m".'FILE'.str_repeat(' ', ($width - 20)).'ERRORS WARNINGS'."\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
foreach ($reportFiles as $file => $data) {
$padding = ($width - 18 - $data['strlen']);
if ($padding < 0) {
$file = '...'.substr($file, (($padding * -1) + 3));
$padding = 0;
}
echo $file.str_repeat(' ', $padding).' ';
if ($data['errors'] !== 0) {
echo "\033[31m".$data['errors']."\033[0m";
echo str_repeat(' ', (8 - strlen((string) $data['errors'])));
} else {
echo '0 ';
}
if ($data['warnings'] !== 0) {
echo "\033[33m".$data['warnings']."\033[0m";
} else {
echo '0';
}
echo PHP_EOL;
}//end foreach
echo str_repeat('-', $width).PHP_EOL;
echo "\033[1mA TOTAL OF $totalErrors ERROR";
if ($totalErrors !== 1) {
echo 'S';
}
echo ' AND '.$totalWarnings.' WARNING';
if ($totalWarnings !== 1) {
echo 'S';
}
echo ' WERE FOUND IN '.$totalFiles.' FILE';
if ($totalFiles !== 1) {
echo 'S';
}
echo "\033[0m";
if ($totalFixable > 0) {
echo PHP_EOL.str_repeat('-', $width).PHP_EOL;
echo "\033[1mPHPCBF CAN FIX $totalFixable OF THESE SNIFF VIOLATIONS AUTOMATICALLY\033[0m";
}
echo PHP_EOL.str_repeat('-', $width).PHP_EOL.PHP_EOL;
if ($toScreen === true && $interactive === false) {
Util\Timing::printRunTime();
}
}//end generate()
}//end class

View file

@ -0,0 +1,73 @@
<?php
/**
* SVN blame report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Exceptions\DeepExitException;
class Svnblame extends VersionControl
{
/**
* The name of the report we want in the output
*
* @var string
*/
protected $reportName = 'SVN';
/**
* Extract the author from a blame line.
*
* @param string $line Line to parse.
*
* @return mixed string or false if impossible to recover.
*/
protected function getAuthor($line)
{
$blameParts = [];
preg_match('|\s*([^\s]+)\s+([^\s]+)|', $line, $blameParts);
if (isset($blameParts[2]) === false) {
return false;
}
return $blameParts[2];
}//end getAuthor()
/**
* Gets the blame output.
*
* @param string $filename File to blame.
*
* @return array
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException
*/
protected function getBlameContent($filename)
{
$command = 'svn blame "'.$filename.'" 2>&1';
$handle = popen($command, 'r');
if ($handle === false) {
$error = 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL;
throw new DeepExitException($error, 3);
}
$rawContent = stream_get_contents($handle);
pclose($handle);
$blames = explode("\n", $rawContent);
return $blames;
}//end getBlameContent()
}//end class

View file

@ -0,0 +1,376 @@
<?php
/**
* Version control report base class for PHP_CodeSniffer.
*
* @author Ben Selby <benmatselby@gmail.com>
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Timing;
abstract class VersionControl implements Report
{
/**
* The name of the report we want in the output.
*
* @var string
*/
protected $reportName = 'VERSION CONTROL';
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
$blames = $this->getBlameContent($report['filename']);
$authorCache = [];
$praiseCache = [];
$sourceCache = [];
foreach ($report['messages'] as $line => $lineErrors) {
$author = 'Unknown';
if (isset($blames[($line - 1)]) === true) {
$blameAuthor = $this->getAuthor($blames[($line - 1)]);
if ($blameAuthor !== false) {
$author = $blameAuthor;
}
}
if (isset($authorCache[$author]) === false) {
$authorCache[$author] = 0;
$praiseCache[$author] = [
'good' => 0,
'bad' => 0,
];
}
$praiseCache[$author]['bad']++;
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$authorCache[$author]++;
if ($showSources === true) {
$source = $error['source'];
if (isset($sourceCache[$author][$source]) === false) {
$sourceCache[$author][$source] = [
'count' => 1,
'fixable' => $error['fixable'],
];
} else {
$sourceCache[$author][$source]['count']++;
}
}
}
}
unset($blames[($line - 1)]);
}//end foreach
// Now go through and give the authors some credit for
// all the lines that do not have errors.
foreach ($blames as $line) {
$author = $this->getAuthor($line);
if ($author === false) {
$author = 'Unknown';
}
if (isset($authorCache[$author]) === false) {
// This author doesn't have any errors.
if (PHP_CODESNIFFER_VERBOSITY === 0) {
continue;
}
$authorCache[$author] = 0;
$praiseCache[$author] = [
'good' => 0,
'bad' => 0,
];
}
$praiseCache[$author]['good']++;
}//end foreach
foreach ($authorCache as $author => $errors) {
echo "AUTHOR>>$author>>$errors".PHP_EOL;
}
foreach ($praiseCache as $author => $praise) {
echo "PRAISE>>$author>>".$praise['good'].'>>'.$praise['bad'].PHP_EOL;
}
foreach ($sourceCache as $author => $sources) {
foreach ($sources as $source => $sourceData) {
$count = $sourceData['count'];
$fixable = (int) $sourceData['fixable'];
echo "SOURCE>>$author>>$source>>$count>>$fixable".PHP_EOL;
}
}
return true;
}//end generateFileReport()
/**
* Prints the author of all errors and warnings, as given by "version control blame".
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
$errorsShown = ($totalErrors + $totalWarnings);
if ($errorsShown === 0) {
// Nothing to show.
return;
}
$lines = explode(PHP_EOL, $cachedData);
array_pop($lines);
if (empty($lines) === true) {
return;
}
$authorCache = [];
$praiseCache = [];
$sourceCache = [];
foreach ($lines as $line) {
$parts = explode('>>', $line);
switch ($parts[0]) {
case 'AUTHOR':
if (isset($authorCache[$parts[1]]) === false) {
$authorCache[$parts[1]] = $parts[2];
} else {
$authorCache[$parts[1]] += $parts[2];
}
break;
case 'PRAISE':
if (isset($praiseCache[$parts[1]]) === false) {
$praiseCache[$parts[1]] = [
'good' => $parts[2],
'bad' => $parts[3],
];
} else {
$praiseCache[$parts[1]]['good'] += $parts[2];
$praiseCache[$parts[1]]['bad'] += $parts[3];
}
break;
case 'SOURCE':
if (isset($praiseCache[$parts[1]]) === false) {
$praiseCache[$parts[1]] = [];
}
if (isset($sourceCache[$parts[1]][$parts[2]]) === false) {
$sourceCache[$parts[1]][$parts[2]] = [
'count' => $parts[3],
'fixable' => (bool) $parts[4],
];
} else {
$sourceCache[$parts[1]][$parts[2]]['count'] += $parts[3];
}
break;
default:
break;
}//end switch
}//end foreach
// Make sure the report width isn't too big.
$maxLength = 0;
foreach ($authorCache as $author => $count) {
$maxLength = max($maxLength, strlen($author));
if ($showSources === true && isset($sourceCache[$author]) === true) {
foreach ($sourceCache[$author] as $source => $sourceData) {
if ($source === 'count') {
continue;
}
$maxLength = max($maxLength, (strlen($source) + 9));
}
}
}
$width = min($width, ($maxLength + 30));
$width = max($width, 70);
arsort($authorCache);
echo PHP_EOL."\033[1m".'PHP CODE SNIFFER '.$this->reportName.' BLAME SUMMARY'."\033[0m".PHP_EOL;
echo str_repeat('-', $width).PHP_EOL."\033[1m";
if ($showSources === true) {
echo 'AUTHOR SOURCE'.str_repeat(' ', ($width - 43)).'(Author %) (Overall %) COUNT'.PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
} else {
echo 'AUTHOR'.str_repeat(' ', ($width - 34)).'(Author %) (Overall %) COUNT'.PHP_EOL;
echo str_repeat('-', $width).PHP_EOL;
}
echo "\033[0m";
if ($showSources === true) {
$maxSniffWidth = ($width - 15);
if ($totalFixable > 0) {
$maxSniffWidth -= 4;
}
}
$fixableSources = 0;
foreach ($authorCache as $author => $count) {
if ($praiseCache[$author]['good'] === 0) {
$percent = 0;
} else {
$total = ($praiseCache[$author]['bad'] + $praiseCache[$author]['good']);
$percent = round(($praiseCache[$author]['bad'] / $total * 100), 2);
}
$overallPercent = '('.round((($count / $errorsShown) * 100), 2).')';
$authorPercent = '('.$percent.')';
$line = str_repeat(' ', (6 - strlen($count))).$count;
$line = str_repeat(' ', (12 - strlen($overallPercent))).$overallPercent.$line;
$line = str_repeat(' ', (11 - strlen($authorPercent))).$authorPercent.$line;
$line = $author.str_repeat(' ', ($width - strlen($author) - strlen($line))).$line;
if ($showSources === true) {
$line = "\033[1m$line\033[0m";
}
echo $line.PHP_EOL;
if ($showSources === true && isset($sourceCache[$author]) === true) {
$errors = $sourceCache[$author];
asort($errors);
$errors = array_reverse($errors);
foreach ($errors as $source => $sourceData) {
if ($source === 'count') {
continue;
}
$count = $sourceData['count'];
$srcLength = strlen($source);
if ($srcLength > $maxSniffWidth) {
$source = substr($source, 0, $maxSniffWidth);
}
$line = str_repeat(' ', (5 - strlen($count))).$count;
echo ' ';
if ($totalFixable > 0) {
echo '[';
if ($sourceData['fixable'] === true) {
echo 'x';
$fixableSources++;
} else {
echo ' ';
}
echo '] ';
}
echo $source;
if ($totalFixable > 0) {
echo str_repeat(' ', ($width - 18 - strlen($source)));
} else {
echo str_repeat(' ', ($width - 14 - strlen($source)));
}
echo $line.PHP_EOL;
}//end foreach
}//end if
}//end foreach
echo str_repeat('-', $width).PHP_EOL;
echo "\033[1m".'A TOTAL OF '.$errorsShown.' SNIFF VIOLATION';
if ($errorsShown !== 1) {
echo 'S';
}
echo ' WERE COMMITTED BY '.count($authorCache).' AUTHOR';
if (count($authorCache) !== 1) {
echo 'S';
}
echo "\033[0m";
if ($totalFixable > 0) {
if ($showSources === true) {
echo PHP_EOL.str_repeat('-', $width).PHP_EOL;
echo "\033[1mPHPCBF CAN FIX THE $fixableSources MARKED SOURCES AUTOMATICALLY ($totalFixable VIOLATIONS IN TOTAL)\033[0m";
} else {
echo PHP_EOL.str_repeat('-', $width).PHP_EOL;
echo "\033[1mPHPCBF CAN FIX $totalFixable OF THESE SNIFF VIOLATIONS AUTOMATICALLY\033[0m";
}
}
echo PHP_EOL.str_repeat('-', $width).PHP_EOL.PHP_EOL;
if ($toScreen === true && $interactive === false) {
Timing::printRunTime();
}
}//end generate()
/**
* Extract the author from a blame line.
*
* @param string $line Line to parse.
*
* @return mixed string or false if impossible to recover.
*/
abstract protected function getAuthor($line);
/**
* Gets the blame output.
*
* @param string $filename File to blame.
*
* @return array
*/
abstract protected function getBlameContent($filename);
}//end class

View file

@ -0,0 +1,126 @@
<?php
/**
* XML report for PHP_CodeSniffer.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Reports;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Files\File;
class Xml implements Report
{
/**
* Generate a partial report for a single processed file.
*
* Function should return TRUE if it printed or stored data about the file
* and FALSE if it ignored the file. Returning TRUE indicates that the file and
* its data should be counted in the grand totals.
*
* @param array $report Prepared report data.
* @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
*
* @return bool
*/
public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80)
{
$out = new \XMLWriter;
$out->openMemory();
$out->setIndent(true);
$out->setIndentString(' ');
$out->startDocument('1.0', 'UTF-8');
if ($report['errors'] === 0 && $report['warnings'] === 0) {
// Nothing to print.
return false;
}
$out->startElement('file');
$out->writeAttribute('name', $report['filename']);
$out->writeAttribute('errors', $report['errors']);
$out->writeAttribute('warnings', $report['warnings']);
$out->writeAttribute('fixable', $report['fixable']);
foreach ($report['messages'] as $line => $lineErrors) {
foreach ($lineErrors as $column => $colErrors) {
foreach ($colErrors as $error) {
$error['type'] = strtolower($error['type']);
if ($phpcsFile->config->encoding !== 'utf-8') {
$error['message'] = iconv($phpcsFile->config->encoding, 'utf-8', $error['message']);
}
$out->startElement($error['type']);
$out->writeAttribute('line', $line);
$out->writeAttribute('column', $column);
$out->writeAttribute('source', $error['source']);
$out->writeAttribute('severity', $error['severity']);
$out->writeAttribute('fixable', (int) $error['fixable']);
$out->text($error['message']);
$out->endElement();
}
}
}//end foreach
$out->endElement();
// Remove the start of the document because we will
// add that manually later. We only have it in here to
// properly set the encoding.
$content = $out->flush();
if (strpos($content, PHP_EOL) !== false) {
$content = substr($content, (strpos($content, PHP_EOL) + strlen(PHP_EOL)));
} else if (strpos($content, "\n") !== false) {
$content = substr($content, (strpos($content, "\n") + 1));
}
echo $content;
return true;
}//end generateFileReport()
/**
* Prints all violations for processed files, in a proprietary XML format.
*
* @param string $cachedData Any partial report data that was returned from
* generateFileReport during the run.
* @param int $totalFiles Total number of files processed during the run.
* @param int $totalErrors Total number of errors found during the run.
* @param int $totalWarnings Total number of warnings found during the run.
* @param int $totalFixable Total number of problems that can be fixed.
* @param bool $showSources Show sources?
* @param int $width Maximum allowed line width.
* @param bool $interactive Are we running in interactive mode?
* @param bool $toScreen Is the report being printed to screen?
*
* @return void
*/
public function generate(
$cachedData,
$totalFiles,
$totalErrors,
$totalWarnings,
$totalFixable,
$showSources=false,
$width=80,
$interactive=false,
$toScreen=true
) {
echo '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
echo '<phpcs version="'.Config::VERSION.'">'.PHP_EOL;
echo $cachedData;
echo '</phpcs>'.PHP_EOL;
}//end generate()
}//end class

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,889 @@
<?php
/**
* Responsible for running PHPCS and PHPCBF.
*
* After creating an object of this class, you probably just want to
* call runPHPCS() or runPHPCBF().
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer;
use PHP_CodeSniffer\Exceptions\DeepExitException;
use PHP_CodeSniffer\Exceptions\RuntimeException;
use PHP_CodeSniffer\Files\DummyFile;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Files\FileList;
use PHP_CodeSniffer\Util\Cache;
use PHP_CodeSniffer\Util\Common;
use PHP_CodeSniffer\Util\Standards;
class Runner
{
/**
* The config data for the run.
*
* @var \PHP_CodeSniffer\Config
*/
public $config = null;
/**
* The ruleset used for the run.
*
* @var \PHP_CodeSniffer\Ruleset
*/
public $ruleset = null;
/**
* The reporter used for generating reports after the run.
*
* @var \PHP_CodeSniffer\Reporter
*/
public $reporter = null;
/**
* Run the PHPCS script.
*
* @return array
*/
public function runPHPCS()
{
try {
Util\Timing::startTiming();
Runner::checkRequirements();
if (defined('PHP_CODESNIFFER_CBF') === false) {
define('PHP_CODESNIFFER_CBF', false);
}
// Creating the Config object populates it with all required settings
// based on the CLI arguments provided to the script and any config
// values the user has set.
$this->config = new Config();
// Init the run and load the rulesets to set additional config vars.
$this->init();
// Print a list of sniffs in each of the supplied standards.
// We fudge the config here so that each standard is explained in isolation.
if ($this->config->explain === true) {
$standards = $this->config->standards;
foreach ($standards as $standard) {
$this->config->standards = [$standard];
$ruleset = new Ruleset($this->config);
$ruleset->explain();
}
return 0;
}
// Generate documentation for each of the supplied standards.
if ($this->config->generator !== null) {
$standards = $this->config->standards;
foreach ($standards as $standard) {
$this->config->standards = [$standard];
$ruleset = new Ruleset($this->config);
$class = 'PHP_CodeSniffer\Generators\\'.$this->config->generator;
$generator = new $class($ruleset);
$generator->generate();
}
return 0;
}
// Other report formats don't really make sense in interactive mode
// so we hard-code the full report here and when outputting.
// We also ensure parallel processing is off because we need to do one file at a time.
if ($this->config->interactive === true) {
$this->config->reports = ['full' => null];
$this->config->parallel = 1;
$this->config->showProgress = false;
}
// Disable caching if we are processing STDIN as we can't be 100%
// sure where the file came from or if it will change in the future.
if ($this->config->stdin === true) {
$this->config->cache = false;
}
$numErrors = $this->run();
// Print all the reports for this run.
$toScreen = $this->reporter->printReports();
// Only print timer output if no reports were
// printed to the screen so we don't put additional output
// in something like an XML report. If we are printing to screen,
// the report types would have already worked out who should
// print the timer info.
if ($this->config->interactive === false
&& ($toScreen === false
|| (($this->reporter->totalErrors + $this->reporter->totalWarnings) === 0 && $this->config->showProgress === true))
) {
Util\Timing::printRunTime();
}
} catch (DeepExitException $e) {
echo $e->getMessage();
return $e->getCode();
}//end try
if ($numErrors === 0) {
// No errors found.
return 0;
} else if ($this->reporter->totalFixable === 0) {
// Errors found, but none of them can be fixed by PHPCBF.
return 1;
} else {
// Errors found, and some can be fixed by PHPCBF.
return 2;
}
}//end runPHPCS()
/**
* Run the PHPCBF script.
*
* @return array
*/
public function runPHPCBF()
{
if (defined('PHP_CODESNIFFER_CBF') === false) {
define('PHP_CODESNIFFER_CBF', true);
}
try {
Util\Timing::startTiming();
Runner::checkRequirements();
// Creating the Config object populates it with all required settings
// based on the CLI arguments provided to the script and any config
// values the user has set.
$this->config = new Config();
// When processing STDIN, we can't output anything to the screen
// or it will end up mixed in with the file output.
if ($this->config->stdin === true) {
$this->config->verbosity = 0;
}
// Init the run and load the rulesets to set additional config vars.
$this->init();
// When processing STDIN, we only process one file at a time and
// we don't process all the way through, so we can't use the parallel
// running system.
if ($this->config->stdin === true) {
$this->config->parallel = 1;
}
// Override some of the command line settings that might break the fixes.
$this->config->generator = null;
$this->config->explain = false;
$this->config->interactive = false;
$this->config->cache = false;
$this->config->showSources = false;
$this->config->recordErrors = false;
$this->config->reportFile = null;
$this->config->reports = ['cbf' => null];
// If a standard tries to set command line arguments itself, some
// may be blocked because PHPCBF is running, so stop the script
// dying if any are found.
$this->config->dieOnUnknownArg = false;
$this->run();
$this->reporter->printReports();
echo PHP_EOL;
Util\Timing::printRunTime();
} catch (DeepExitException $e) {
echo $e->getMessage();
return $e->getCode();
}//end try
if ($this->reporter->totalFixed === 0) {
// Nothing was fixed by PHPCBF.
if ($this->reporter->totalFixable === 0) {
// Nothing found that could be fixed.
return 0;
} else {
// Something failed to fix.
return 2;
}
}
if ($this->reporter->totalFixable === 0) {
// PHPCBF fixed all fixable errors.
return 1;
}
// PHPCBF fixed some fixable errors, but others failed to fix.
return 2;
}//end runPHPCBF()
/**
* Exits if the minimum requirements of PHP_CodeSniffer are not met.
*
* @return void
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException If the requirements are not met.
*/
public function checkRequirements()
{
// Check the PHP version.
if (PHP_VERSION_ID < 50400) {
$error = 'ERROR: PHP_CodeSniffer requires PHP version 5.4.0 or greater.'.PHP_EOL;
throw new DeepExitException($error, 3);
}
$requiredExtensions = [
'tokenizer',
'xmlwriter',
'SimpleXML',
];
$missingExtensions = [];
foreach ($requiredExtensions as $extension) {
if (extension_loaded($extension) === false) {
$missingExtensions[] = $extension;
}
}
if (empty($missingExtensions) === false) {
$last = array_pop($requiredExtensions);
$required = implode(', ', $requiredExtensions);
$required .= ' and '.$last;
if (count($missingExtensions) === 1) {
$missing = $missingExtensions[0];
} else {
$last = array_pop($missingExtensions);
$missing = implode(', ', $missingExtensions);
$missing .= ' and '.$last;
}
$error = 'ERROR: PHP_CodeSniffer requires the %s extensions to be enabled. Please enable %s.'.PHP_EOL;
$error = sprintf($error, $required, $missing);
throw new DeepExitException($error, 3);
}
}//end checkRequirements()
/**
* Init the rulesets and other high-level settings.
*
* @return void
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException If a referenced standard is not installed.
*/
public function init()
{
if (defined('PHP_CODESNIFFER_CBF') === false) {
define('PHP_CODESNIFFER_CBF', false);
}
// Ensure this option is enabled or else line endings will not always
// be detected properly for files created on a Mac with the /r line ending.
@ini_set('auto_detect_line_endings', true);
// Disable the PCRE JIT as this caused issues with parallel running.
ini_set('pcre.jit', false);
// Check that the standards are valid.
foreach ($this->config->standards as $standard) {
if (Util\Standards::isInstalledStandard($standard) === false) {
// They didn't select a valid coding standard, so help them
// out by letting them know which standards are installed.
$error = 'ERROR: the "'.$standard.'" coding standard is not installed. ';
ob_start();
Util\Standards::printInstalledStandards();
$error .= ob_get_contents();
ob_end_clean();
throw new DeepExitException($error, 3);
}
}
// Saves passing the Config object into other objects that only need
// the verbosity flag for debug output.
if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
define('PHP_CODESNIFFER_VERBOSITY', $this->config->verbosity);
}
// Create this class so it is autoloaded and sets up a bunch
// of PHP_CodeSniffer-specific token type constants.
$tokens = new Util\Tokens();
// Allow autoloading of custom files inside installed standards.
$installedStandards = Standards::getInstalledStandardDetails();
foreach ($installedStandards as $name => $details) {
Autoload::addSearchPath($details['path'], $details['namespace']);
}
// The ruleset contains all the information about how the files
// should be checked and/or fixed.
try {
$this->ruleset = new Ruleset($this->config);
} catch (RuntimeException $e) {
$error = 'ERROR: '.$e->getMessage().PHP_EOL.PHP_EOL;
$error .= $this->config->printShortUsage(true);
throw new DeepExitException($error, 3);
}
}//end init()
/**
* Performs the run.
*
* @return int The number of errors and warnings found.
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException
*/
private function run()
{
// The class that manages all reporters for the run.
$this->reporter = new Reporter($this->config);
// Include bootstrap files.
foreach ($this->config->bootstrap as $bootstrap) {
include $bootstrap;
}
if ($this->config->stdin === true) {
$fileContents = $this->config->stdinContent;
if ($fileContents === null) {
$handle = fopen('php://stdin', 'r');
stream_set_blocking($handle, true);
$fileContents = stream_get_contents($handle);
fclose($handle);
}
$todo = new FileList($this->config, $this->ruleset);
$dummy = new DummyFile($fileContents, $this->ruleset, $this->config);
$todo->addFile($dummy->path, $dummy);
} else {
if (empty($this->config->files) === true) {
$error = 'ERROR: You must supply at least one file or directory to process.'.PHP_EOL.PHP_EOL;
$error .= $this->config->printShortUsage(true);
throw new DeepExitException($error, 3);
}
if (PHP_CODESNIFFER_VERBOSITY > 0) {
echo 'Creating file list... ';
}
$todo = new FileList($this->config, $this->ruleset);
if (PHP_CODESNIFFER_VERBOSITY > 0) {
$numFiles = count($todo);
echo "DONE ($numFiles files in queue)".PHP_EOL;
}
if ($this->config->cache === true) {
if (PHP_CODESNIFFER_VERBOSITY > 0) {
echo 'Loading cache... ';
}
Cache::load($this->ruleset, $this->config);
if (PHP_CODESNIFFER_VERBOSITY > 0) {
$size = Cache::getSize();
echo "DONE ($size files in cache)".PHP_EOL;
}
}
}//end if
// Turn all sniff errors into exceptions.
set_error_handler([$this, 'handleErrors']);
// If verbosity is too high, turn off parallelism so the
// debug output is clean.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$this->config->parallel = 1;
}
// If the PCNTL extension isn't installed, we can't fork.
if (function_exists('pcntl_fork') === false) {
$this->config->parallel = 1;
}
$lastDir = '';
$numFiles = count($todo);
if ($this->config->parallel === 1) {
// Running normally.
$numProcessed = 0;
foreach ($todo as $path => $file) {
if ($file->ignored === false) {
$currDir = dirname($path);
if ($lastDir !== $currDir) {
if (PHP_CODESNIFFER_VERBOSITY > 0) {
echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
}
$lastDir = $currDir;
}
$this->processFile($file);
} else if (PHP_CODESNIFFER_VERBOSITY > 0) {
echo 'Skipping '.basename($file->path).PHP_EOL;
}
$numProcessed++;
$this->printProgress($file, $numFiles, $numProcessed);
}
} else {
// Batching and forking.
$childProcs = [];
$numPerBatch = ceil($numFiles / $this->config->parallel);
for ($batch = 0; $batch < $this->config->parallel; $batch++) {
$startAt = ($batch * $numPerBatch);
if ($startAt >= $numFiles) {
break;
}
$endAt = ($startAt + $numPerBatch);
if ($endAt > $numFiles) {
$endAt = $numFiles;
}
$childOutFilename = tempnam(sys_get_temp_dir(), 'phpcs-child');
$pid = pcntl_fork();
if ($pid === -1) {
throw new RuntimeException('Failed to create child process');
} else if ($pid !== 0) {
$childProcs[$pid] = $childOutFilename;
} else {
// Move forward to the start of the batch.
$todo->rewind();
for ($i = 0; $i < $startAt; $i++) {
$todo->next();
}
// Reset the reporter to make sure only figures from this
// file batch are recorded.
$this->reporter->totalFiles = 0;
$this->reporter->totalErrors = 0;
$this->reporter->totalWarnings = 0;
$this->reporter->totalFixable = 0;
$this->reporter->totalFixed = 0;
// Process the files.
$pathsProcessed = [];
ob_start();
for ($i = $startAt; $i < $endAt; $i++) {
$path = $todo->key();
$file = $todo->current();
if ($file->ignored === true) {
$todo->next();
continue;
}
$currDir = dirname($path);
if ($lastDir !== $currDir) {
if (PHP_CODESNIFFER_VERBOSITY > 0) {
echo 'Changing into directory '.Common::stripBasepath($currDir, $this->config->basepath).PHP_EOL;
}
$lastDir = $currDir;
}
$this->processFile($file);
$pathsProcessed[] = $path;
$todo->next();
}//end for
$debugOutput = ob_get_contents();
ob_end_clean();
// Write information about the run to the filesystem
// so it can be picked up by the main process.
$childOutput = [
'totalFiles' => $this->reporter->totalFiles,
'totalErrors' => $this->reporter->totalErrors,
'totalWarnings' => $this->reporter->totalWarnings,
'totalFixable' => $this->reporter->totalFixable,
'totalFixed' => $this->reporter->totalFixed,
];
$output = '<'.'?php'."\n".' $childOutput = ';
$output .= var_export($childOutput, true);
$output .= ";\n\$debugOutput = ";
$output .= var_export($debugOutput, true);
if ($this->config->cache === true) {
$childCache = [];
foreach ($pathsProcessed as $path) {
$childCache[$path] = Cache::get($path);
}
$output .= ";\n\$childCache = ";
$output .= var_export($childCache, true);
}
$output .= ";\n?".'>';
file_put_contents($childOutFilename, $output);
exit();
}//end if
}//end for
$success = $this->processChildProcs($childProcs);
if ($success === false) {
throw new RuntimeException('One or more child processes failed to run');
}
}//end if
restore_error_handler();
if (PHP_CODESNIFFER_VERBOSITY === 0
&& $this->config->interactive === false
&& $this->config->showProgress === true
) {
echo PHP_EOL.PHP_EOL;
}
if ($this->config->cache === true) {
Cache::save();
}
$ignoreWarnings = Config::getConfigData('ignore_warnings_on_exit');
$ignoreErrors = Config::getConfigData('ignore_errors_on_exit');
$return = ($this->reporter->totalErrors + $this->reporter->totalWarnings);
if ($ignoreErrors !== null) {
$ignoreErrors = (bool) $ignoreErrors;
if ($ignoreErrors === true) {
$return -= $this->reporter->totalErrors;
}
}
if ($ignoreWarnings !== null) {
$ignoreWarnings = (bool) $ignoreWarnings;
if ($ignoreWarnings === true) {
$return -= $this->reporter->totalWarnings;
}
}
return $return;
}//end run()
/**
* Converts all PHP errors into exceptions.
*
* This method forces a sniff to stop processing if it is not
* able to handle a specific piece of code, instead of continuing
* and potentially getting into a loop.
*
* @param int $code The level of error raised.
* @param string $message The error message.
* @param string $file The path of the file that raised the error.
* @param int $line The line number the error was raised at.
*
* @return void
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException
*/
public function handleErrors($code, $message, $file, $line)
{
if ((error_reporting() & $code) === 0) {
// This type of error is being muted.
return true;
}
throw new RuntimeException("$message in $file on line $line");
}//end handleErrors()
/**
* Processes a single file, including checking and fixing.
*
* @param \PHP_CodeSniffer\Files\File $file The file to be processed.
*
* @return void
* @throws \PHP_CodeSniffer\Exceptions\DeepExitException
*/
public function processFile($file)
{
if (PHP_CODESNIFFER_VERBOSITY > 0) {
$startTime = microtime(true);
echo 'Processing '.basename($file->path).' ';
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo PHP_EOL;
}
}
try {
$file->process();
if (PHP_CODESNIFFER_VERBOSITY > 0) {
$timeTaken = ((microtime(true) - $startTime) * 1000);
if ($timeTaken < 1000) {
$timeTaken = round($timeTaken);
echo "DONE in {$timeTaken}ms";
} else {
$timeTaken = round(($timeTaken / 1000), 2);
echo "DONE in $timeTaken secs";
}
if (PHP_CODESNIFFER_CBF === true) {
$errors = $file->getFixableCount();
echo " ($errors fixable violations)".PHP_EOL;
} else {
$errors = $file->getErrorCount();
$warnings = $file->getWarningCount();
echo " ($errors errors, $warnings warnings)".PHP_EOL;
}
}
} catch (\Exception $e) {
$error = 'An error occurred during processing; checking has been aborted. The error message was: '.$e->getMessage();
$file->addErrorOnLine($error, 1, 'Internal.Exception');
}//end try
$this->reporter->cacheFileReport($file, $this->config);
if ($this->config->interactive === true) {
/*
Running interactively.
Print the error report for the current file and then wait for user input.
*/
// Get current violations and then clear the list to make sure
// we only print violations for a single file each time.
$numErrors = null;
while ($numErrors !== 0) {
$numErrors = ($file->getErrorCount() + $file->getWarningCount());
if ($numErrors === 0) {
continue;
}
$this->reporter->printReport('full');
echo '<ENTER> to recheck, [s] to skip or [q] to quit : ';
$input = fgets(STDIN);
$input = trim($input);
switch ($input) {
case 's':
break(2);
case 'q':
throw new DeepExitException('', 0);
default:
// Repopulate the sniffs because some of them save their state
// and only clear it when the file changes, but we are rechecking
// the same file.
$file->ruleset->populateTokenListeners();
$file->reloadContent();
$file->process();
$this->reporter->cacheFileReport($file, $this->config);
break;
}
}//end while
}//end if
// Clean up the file to save (a lot of) memory.
$file->cleanUp();
}//end processFile()
/**
* Waits for child processes to complete and cleans up after them.
*
* The reporting information returned by each child process is merged
* into the main reporter class.
*
* @param array $childProcs An array of child processes to wait for.
*
* @return bool
*/
private function processChildProcs($childProcs)
{
$numProcessed = 0;
$totalBatches = count($childProcs);
$success = true;
while (count($childProcs) > 0) {
$pid = pcntl_waitpid(0, $status);
if ($pid <= 0) {
continue;
}
$out = $childProcs[$pid];
unset($childProcs[$pid]);
if (file_exists($out) === false) {
continue;
}
include $out;
unlink($out);
$numProcessed++;
if (isset($childOutput) === false) {
// The child process died, so the run has failed.
$file = new DummyFile('', $this->ruleset, $this->config);
$file->setErrorCounts(1, 0, 0, 0);
$this->printProgress($file, $totalBatches, $numProcessed);
$success = false;
continue;
}
$this->reporter->totalFiles += $childOutput['totalFiles'];
$this->reporter->totalErrors += $childOutput['totalErrors'];
$this->reporter->totalWarnings += $childOutput['totalWarnings'];
$this->reporter->totalFixable += $childOutput['totalFixable'];
$this->reporter->totalFixed += $childOutput['totalFixed'];
if (isset($debugOutput) === true) {
echo $debugOutput;
}
if (isset($childCache) === true) {
foreach ($childCache as $path => $cache) {
Cache::set($path, $cache);
}
}
// Fake a processed file so we can print progress output for the batch.
$file = new DummyFile('', $this->ruleset, $this->config);
$file->setErrorCounts(
$childOutput['totalErrors'],
$childOutput['totalWarnings'],
$childOutput['totalFixable'],
$childOutput['totalFixed']
);
$this->printProgress($file, $totalBatches, $numProcessed);
}//end while
return $success;
}//end processChildProcs()
/**
* Print progress information for a single processed file.
*
* @param \PHP_CodeSniffer\Files\File $file The file that was processed.
* @param int $numFiles The total number of files to process.
* @param int $numProcessed The number of files that have been processed,
* including this one.
*
* @return void
*/
public function printProgress(File $file, $numFiles, $numProcessed)
{
if (PHP_CODESNIFFER_VERBOSITY > 0
|| $this->config->showProgress === false
) {
return;
}
// Show progress information.
if ($file->ignored === true) {
echo 'S';
} else {
$errors = $file->getErrorCount();
$warnings = $file->getWarningCount();
$fixable = $file->getFixableCount();
$fixed = $file->getFixedCount();
if (PHP_CODESNIFFER_CBF === true) {
// Files with fixed errors or warnings are F (green).
// Files with unfixable errors or warnings are E (red).
// Files with no errors or warnings are . (black).
if ($fixable > 0) {
if ($this->config->colors === true) {
echo "\033[31m";
}
echo 'E';
if ($this->config->colors === true) {
echo "\033[0m";
}
} else if ($fixed > 0) {
if ($this->config->colors === true) {
echo "\033[32m";
}
echo 'F';
if ($this->config->colors === true) {
echo "\033[0m";
}
} else {
echo '.';
}//end if
} else {
// Files with errors are E (red).
// Files with fixable errors are E (green).
// Files with warnings are W (yellow).
// Files with fixable warnings are W (green).
// Files with no errors or warnings are . (black).
if ($errors > 0) {
if ($this->config->colors === true) {
if ($fixable > 0) {
echo "\033[32m";
} else {
echo "\033[31m";
}
}
echo 'E';
if ($this->config->colors === true) {
echo "\033[0m";
}
} else if ($warnings > 0) {
if ($this->config->colors === true) {
if ($fixable > 0) {
echo "\033[32m";
} else {
echo "\033[33m";
}
}
echo 'W';
if ($this->config->colors === true) {
echo "\033[0m";
}
} else {
echo '.';
}//end if
}//end if
}//end if
$numPerLine = 60;
if ($numProcessed !== $numFiles && ($numProcessed % $numPerLine) !== 0) {
return;
}
$percent = round(($numProcessed / $numFiles) * 100);
$padding = (strlen($numFiles) - strlen($numProcessed));
if ($numProcessed === $numFiles
&& $numFiles > $numPerLine
&& ($numProcessed % $numPerLine) !== 0
) {
$padding += ($numPerLine - ($numFiles - (floor($numFiles / $numPerLine) * $numPerLine)));
}
echo str_repeat(' ', $padding)." $numProcessed / $numFiles ($percent%)".PHP_EOL;
}//end printProgress()
}//end class

View file

@ -0,0 +1,172 @@
<?php
/**
* Processes single and multi-line arrays.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Sniffs;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
abstract class AbstractArraySniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
final public function register()
{
return [
T_ARRAY,
T_OPEN_SHORT_ARRAY,
];
}//end register()
/**
* Processes this sniff, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['code'] === T_ARRAY) {
$phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'no');
$arrayStart = $tokens[$stackPtr]['parenthesis_opener'];
if (isset($tokens[$arrayStart]['parenthesis_closer']) === false) {
// Incomplete array.
return;
}
$arrayEnd = $tokens[$arrayStart]['parenthesis_closer'];
} else {
$phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'yes');
$arrayStart = $stackPtr;
$arrayEnd = $tokens[$stackPtr]['bracket_closer'];
}
$lastContent = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($arrayEnd - 1), null, true);
if ($tokens[$lastContent]['code'] === T_COMMA) {
// Last array item ends with a comma.
$phpcsFile->recordMetric($stackPtr, 'Array end comma', 'yes');
} else {
$phpcsFile->recordMetric($stackPtr, 'Array end comma', 'no');
}
$indices = [];
$current = $arrayStart;
while (($next = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), $arrayEnd, true)) !== false) {
$end = $this->getNext($phpcsFile, $next, $arrayEnd);
if ($tokens[$end]['code'] === T_DOUBLE_ARROW) {
$indexEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($end - 1), null, true);
$valueStart = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true);
$indices[] = [
'index_start' => $next,
'index_end' => $indexEnd,
'arrow' => $end,
'value_start' => $valueStart,
];
} else {
$valueStart = $next;
$indices[] = ['value_start' => $valueStart];
}
$current = $this->getNext($phpcsFile, $valueStart, $arrayEnd);
}
if ($tokens[$arrayStart]['line'] === $tokens[$arrayEnd]['line']) {
$this->processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices);
} else {
$this->processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices);
}
}//end process()
/**
* Find next separator in array - either: comma or double arrow.
*
* @param File $phpcsFile The current file being checked.
* @param int $ptr The position of current token.
* @param int $arrayEnd The token that ends the array definition.
*
* @return int
*/
private function getNext(File $phpcsFile, $ptr, $arrayEnd)
{
$tokens = $phpcsFile->getTokens();
while ($ptr < $arrayEnd) {
if (isset($tokens[$ptr]['scope_closer']) === true) {
$ptr = $tokens[$ptr]['scope_closer'];
} else if (isset($tokens[$ptr]['parenthesis_closer']) === true) {
$ptr = $tokens[$ptr]['parenthesis_closer'];
} else if (isset($tokens[$ptr]['bracket_closer']) === true) {
$ptr = $tokens[$ptr]['bracket_closer'];
}
if ($tokens[$ptr]['code'] === T_COMMA
|| $tokens[$ptr]['code'] === T_DOUBLE_ARROW
) {
return $ptr;
}
++$ptr;
}
return $ptr;
}//end getNext()
/**
* Processes a single-line array definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param int $arrayStart The token that starts the array definition.
* @param int $arrayEnd The token that ends the array definition.
* @param array $indices An array of token positions for the array keys,
* double arrows, and values.
*
* @return void
*/
abstract protected function processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices);
/**
* Processes a multi-line array definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param int $arrayStart The token that starts the array definition.
* @param int $arrayEnd The token that ends the array definition.
* @param array $indices An array of token positions for the array keys,
* double arrows, and values.
*
* @return void
*/
abstract protected function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices);
}//end class

View file

@ -0,0 +1,936 @@
<?php
/**
* Processes pattern strings and checks that the code conforms to the pattern.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Sniffs;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHP_CodeSniffer\Tokenizers\PHP;
use PHP_CodeSniffer\Exceptions\RuntimeException;
abstract class AbstractPatternSniff implements Sniff
{
/**
* If true, comments will be ignored if they are found in the code.
*
* @var boolean
*/
public $ignoreComments = false;
/**
* The current file being checked.
*
* @var string
*/
protected $currFile = '';
/**
* The parsed patterns array.
*
* @var array
*/
private $parsedPatterns = [];
/**
* Tokens that this sniff wishes to process outside of the patterns.
*
* @var int[]
* @see registerSupplementary()
* @see processSupplementary()
*/
private $supplementaryTokens = [];
/**
* Positions in the stack where errors have occurred.
*
* @var array<int, bool>
*/
private $errorPos = [];
/**
* Constructs a AbstractPatternSniff.
*
* @param boolean $ignoreComments If true, comments will be ignored.
*/
public function __construct($ignoreComments=null)
{
// This is here for backwards compatibility.
if ($ignoreComments !== null) {
$this->ignoreComments = $ignoreComments;
}
$this->supplementaryTokens = $this->registerSupplementary();
}//end __construct()
/**
* Registers the tokens to listen to.
*
* Classes extending <i>AbstractPatternTest</i> should implement the
* <i>getPatterns()</i> method to register the patterns they wish to test.
*
* @return int[]
* @see process()
*/
final public function register()
{
$listenTypes = [];
$patterns = $this->getPatterns();
foreach ($patterns as $pattern) {
$parsedPattern = $this->parse($pattern);
// Find a token position in the pattern that we can use
// for a listener token.
$pos = $this->getListenerTokenPos($parsedPattern);
$tokenType = $parsedPattern[$pos]['token'];
$listenTypes[] = $tokenType;
$patternArray = [
'listen_pos' => $pos,
'pattern' => $parsedPattern,
'pattern_code' => $pattern,
];
if (isset($this->parsedPatterns[$tokenType]) === false) {
$this->parsedPatterns[$tokenType] = [];
}
$this->parsedPatterns[$tokenType][] = $patternArray;
}//end foreach
return array_unique(array_merge($listenTypes, $this->supplementaryTokens));
}//end register()
/**
* Returns the token types that the specified pattern is checking for.
*
* Returned array is in the format:
* <code>
* array(
* T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token
* // should occur in the pattern.
* );
* </code>
*
* @param array $pattern The parsed pattern to find the acquire the token
* types from.
*
* @return array<int, int>
*/
private function getPatternTokenTypes($pattern)
{
$tokenTypes = [];
foreach ($pattern as $pos => $patternInfo) {
if ($patternInfo['type'] === 'token') {
if (isset($tokenTypes[$patternInfo['token']]) === false) {
$tokenTypes[$patternInfo['token']] = $pos;
}
}
}
return $tokenTypes;
}//end getPatternTokenTypes()
/**
* Returns the position in the pattern that this test should register as
* a listener for the pattern.
*
* @param array $pattern The pattern to acquire the listener for.
*
* @return int The position in the pattern that this test should register
* as the listener.
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If we could not determine a token to listen for.
*/
private function getListenerTokenPos($pattern)
{
$tokenTypes = $this->getPatternTokenTypes($pattern);
$tokenCodes = array_keys($tokenTypes);
$token = Tokens::getHighestWeightedToken($tokenCodes);
// If we could not get a token.
if ($token === false) {
$error = 'Could not determine a token to listen for';
throw new RuntimeException($error);
}
return $tokenTypes[$token];
}//end getListenerTokenPos()
/**
* Processes the test.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token occurred.
* @param int $stackPtr The position in the tokens stack
* where the listening token type
* was found.
*
* @return void
* @see register()
*/
final public function process(File $phpcsFile, $stackPtr)
{
$file = $phpcsFile->getFilename();
if ($this->currFile !== $file) {
// We have changed files, so clean up.
$this->errorPos = [];
$this->currFile = $file;
}
$tokens = $phpcsFile->getTokens();
if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens, true) === true) {
$this->processSupplementary($phpcsFile, $stackPtr);
}
$type = $tokens[$stackPtr]['code'];
// If the type is not set, then it must have been a token registered
// with registerSupplementary().
if (isset($this->parsedPatterns[$type]) === false) {
return;
}
$allErrors = [];
// Loop over each pattern that is listening to the current token type
// that we are processing.
foreach ($this->parsedPatterns[$type] as $patternInfo) {
// If processPattern returns false, then the pattern that we are
// checking the code with must not be designed to check that code.
$errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr);
if ($errors === false) {
// The pattern didn't match.
continue;
} else if (empty($errors) === true) {
// The pattern matched, but there were no errors.
break;
}
foreach ($errors as $stackPtr => $error) {
if (isset($this->errorPos[$stackPtr]) === false) {
$this->errorPos[$stackPtr] = true;
$allErrors[$stackPtr] = $error;
}
}
}
foreach ($allErrors as $stackPtr => $error) {
$phpcsFile->addError($error, $stackPtr, 'Found');
}
}//end process()
/**
* Processes the pattern and verifies the code at $stackPtr.
*
* @param array $patternInfo Information about the pattern used
* for checking, which includes are
* parsed token representation of the
* pattern.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token occurred.
* @param int $stackPtr The position in the tokens stack where
* the listening token type was found.
*
* @return array
*/
protected function processPattern($patternInfo, File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$pattern = $patternInfo['pattern'];
$patternCode = $patternInfo['pattern_code'];
$errors = [];
$found = '';
$ignoreTokens = [T_WHITESPACE => T_WHITESPACE];
if ($this->ignoreComments === true) {
$ignoreTokens += Tokens::$commentTokens;
}
$origStackPtr = $stackPtr;
$hasError = false;
if ($patternInfo['listen_pos'] > 0) {
$stackPtr--;
for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) {
if ($pattern[$i]['type'] === 'token') {
if ($pattern[$i]['token'] === T_WHITESPACE) {
if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
$found = $tokens[$stackPtr]['content'].$found;
}
// Only check the size of the whitespace if this is not
// the first token. We don't care about the size of
// leading whitespace, just that there is some.
if ($i !== 0) {
if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) {
$hasError = true;
}
}
} else {
// Check to see if this important token is the same as the
// previous important token in the pattern. If it is not,
// then the pattern cannot be for this piece of code.
$prev = $phpcsFile->findPrevious(
$ignoreTokens,
$stackPtr,
null,
true
);
if ($prev === false
|| $tokens[$prev]['code'] !== $pattern[$i]['token']
) {
return false;
}
// If we skipped past some whitespace tokens, then add them
// to the found string.
$tokenContent = $phpcsFile->getTokensAsString(
($prev + 1),
($stackPtr - $prev - 1)
);
$found = $tokens[$prev]['content'].$tokenContent.$found;
if (isset($pattern[($i - 1)]) === true
&& $pattern[($i - 1)]['type'] === 'skip'
) {
$stackPtr = $prev;
} else {
$stackPtr = ($prev - 1);
}
}//end if
} else if ($pattern[$i]['type'] === 'skip') {
// Skip to next piece of relevant code.
if ($pattern[$i]['to'] === 'parenthesis_closer') {
$to = 'parenthesis_opener';
} else {
$to = 'scope_opener';
}
// Find the previous opener.
$next = $phpcsFile->findPrevious(
$ignoreTokens,
$stackPtr,
null,
true
);
if ($next === false || isset($tokens[$next][$to]) === false) {
// If there was not opener, then we must be
// using the wrong pattern.
return false;
}
if ($to === 'parenthesis_opener') {
$found = '{'.$found;
} else {
$found = '('.$found;
}
$found = '...'.$found;
// Skip to the opening token.
$stackPtr = ($tokens[$next][$to] - 1);
} else if ($pattern[$i]['type'] === 'string') {
$found = 'abc';
} else if ($pattern[$i]['type'] === 'newline') {
if ($this->ignoreComments === true
&& isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true
) {
$startComment = $phpcsFile->findPrevious(
Tokens::$commentTokens,
($stackPtr - 1),
null,
true
);
if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) {
$startComment++;
}
$tokenContent = $phpcsFile->getTokensAsString(
$startComment,
($stackPtr - $startComment + 1)
);
$found = $tokenContent.$found;
$stackPtr = ($startComment - 1);
}
if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) {
$found = $tokens[$stackPtr]['content'].$found;
// This may just be an indent that comes after a newline
// so check the token before to make sure. If it is a newline, we
// can ignore the error here.
if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar)
&& ($this->ignoreComments === true
&& isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false)
) {
$hasError = true;
} else {
$stackPtr--;
}
} else {
$found = 'EOL'.$found;
}
} else {
$found = $tokens[$stackPtr]['content'].$found;
$hasError = true;
}//end if
if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') {
// Make sure they only have 1 newline.
$prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) {
$hasError = true;
}
}
}//end if
}//end for
}//end if
$stackPtr = $origStackPtr;
$lastAddedStackPtr = null;
$patternLen = count($pattern);
for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) {
if (isset($tokens[$stackPtr]) === false) {
break;
}
if ($pattern[$i]['type'] === 'token') {
if ($pattern[$i]['token'] === T_WHITESPACE) {
if ($this->ignoreComments === true) {
// If we are ignoring comments, check to see if this current
// token is a comment. If so skip it.
if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) {
continue;
}
// If the next token is a comment, the we need to skip the
// current token as we should allow a space before a
// comment for readability.
if (isset($tokens[($stackPtr + 1)]) === true
&& isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true
) {
continue;
}
}
$tokenContent = '';
if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
if (isset($pattern[($i + 1)]) === false) {
// This is the last token in the pattern, so just compare
// the next token of content.
$tokenContent = $tokens[$stackPtr]['content'];
} else {
// Get all the whitespace to the next token.
$next = $phpcsFile->findNext(
Tokens::$emptyTokens,
$stackPtr,
null,
true
);
$tokenContent = $phpcsFile->getTokensAsString(
$stackPtr,
($next - $stackPtr)
);
$lastAddedStackPtr = $stackPtr;
$stackPtr = $next;
}//end if
if ($stackPtr !== $lastAddedStackPtr) {
$found .= $tokenContent;
}
} else {
if ($stackPtr !== $lastAddedStackPtr) {
$found .= $tokens[$stackPtr]['content'];
$lastAddedStackPtr = $stackPtr;
}
}//end if
if (isset($pattern[($i + 1)]) === true
&& $pattern[($i + 1)]['type'] === 'skip'
) {
// The next token is a skip token, so we just need to make
// sure the whitespace we found has *at least* the
// whitespace required.
if (strpos($tokenContent, $pattern[$i]['value']) !== 0) {
$hasError = true;
}
} else {
if ($tokenContent !== $pattern[$i]['value']) {
$hasError = true;
}
}
} else {
// Check to see if this important token is the same as the
// next important token in the pattern. If it is not, then
// the pattern cannot be for this piece of code.
$next = $phpcsFile->findNext(
$ignoreTokens,
$stackPtr,
null,
true
);
if ($next === false
|| $tokens[$next]['code'] !== $pattern[$i]['token']
) {
// The next important token did not match the pattern.
return false;
}
if ($lastAddedStackPtr !== null) {
if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET
|| $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET)
&& isset($tokens[$next]['scope_condition']) === true
&& $tokens[$next]['scope_condition'] > $lastAddedStackPtr
) {
// This is a brace, but the owner of it is after the current
// token, which means it does not belong to any token in
// our pattern. This means the pattern is not for us.
return false;
}
if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS
|| $tokens[$next]['code'] === T_CLOSE_PARENTHESIS)
&& isset($tokens[$next]['parenthesis_owner']) === true
&& $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr
) {
// This is a bracket, but the owner of it is after the current
// token, which means it does not belong to any token in
// our pattern. This means the pattern is not for us.
return false;
}
}//end if
// If we skipped past some whitespace tokens, then add them
// to the found string.
if (($next - $stackPtr) > 0) {
$hasComment = false;
for ($j = $stackPtr; $j < $next; $j++) {
$found .= $tokens[$j]['content'];
if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) {
$hasComment = true;
}
}
// If we are not ignoring comments, this additional
// whitespace or comment is not allowed. If we are
// ignoring comments, there needs to be at least one
// comment for this to be allowed.
if ($this->ignoreComments === false
|| ($this->ignoreComments === true
&& $hasComment === false)
) {
$hasError = true;
}
// Even when ignoring comments, we are not allowed to include
// newlines without the pattern specifying them, so
// everything should be on the same line.
if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) {
$hasError = true;
}
}//end if
if ($next !== $lastAddedStackPtr) {
$found .= $tokens[$next]['content'];
$lastAddedStackPtr = $next;
}
if (isset($pattern[($i + 1)]) === true
&& $pattern[($i + 1)]['type'] === 'skip'
) {
$stackPtr = $next;
} else {
$stackPtr = ($next + 1);
}
}//end if
} else if ($pattern[$i]['type'] === 'skip') {
if ($pattern[$i]['to'] === 'unknown') {
$next = $phpcsFile->findNext(
$pattern[($i + 1)]['token'],
$stackPtr
);
if ($next === false) {
// Couldn't find the next token, so we must
// be using the wrong pattern.
return false;
}
$found .= '...';
$stackPtr = $next;
} else {
// Find the previous opener.
$next = $phpcsFile->findPrevious(
Tokens::$blockOpeners,
$stackPtr
);
if ($next === false
|| isset($tokens[$next][$pattern[$i]['to']]) === false
) {
// If there was not opener, then we must
// be using the wrong pattern.
return false;
}
$found .= '...';
if ($pattern[$i]['to'] === 'parenthesis_closer') {
$found .= ')';
} else {
$found .= '}';
}
// Skip to the closing token.
$stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1);
}//end if
} else if ($pattern[$i]['type'] === 'string') {
if ($tokens[$stackPtr]['code'] !== T_STRING) {
$hasError = true;
}
if ($stackPtr !== $lastAddedStackPtr) {
$found .= 'abc';
$lastAddedStackPtr = $stackPtr;
}
$stackPtr++;
} else if ($pattern[$i]['type'] === 'newline') {
// Find the next token that contains a newline character.
$newline = 0;
for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) {
if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) {
$newline = $j;
break;
}
}
if ($newline === 0) {
// We didn't find a newline character in the rest of the file.
$next = ($phpcsFile->numTokens - 1);
$hasError = true;
} else {
if ($this->ignoreComments === false) {
// The newline character cannot be part of a comment.
if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) {
$hasError = true;
}
}
if ($newline === $stackPtr) {
$next = ($stackPtr + 1);
} else {
// Check that there were no significant tokens that we
// skipped over to find our newline character.
$next = $phpcsFile->findNext(
$ignoreTokens,
$stackPtr,
null,
true
);
if ($next < $newline) {
// We skipped a non-ignored token.
$hasError = true;
} else {
$next = ($newline + 1);
}
}
}//end if
if ($stackPtr !== $lastAddedStackPtr) {
$found .= $phpcsFile->getTokensAsString(
$stackPtr,
($next - $stackPtr)
);
$lastAddedStackPtr = ($next - 1);
}
$stackPtr = $next;
}//end if
}//end for
if ($hasError === true) {
$error = $this->prepareError($found, $patternCode);
$errors[$origStackPtr] = $error;
}
return $errors;
}//end processPattern()
/**
* Prepares an error for the specified patternCode.
*
* @param string $found The actual found string in the code.
* @param string $patternCode The expected pattern code.
*
* @return string The error message.
*/
protected function prepareError($found, $patternCode)
{
$found = str_replace("\r\n", '\n', $found);
$found = str_replace("\n", '\n', $found);
$found = str_replace("\r", '\n', $found);
$found = str_replace("\t", '\t', $found);
$found = str_replace('EOL', '\n', $found);
$expected = str_replace('EOL', '\n', $patternCode);
$error = "Expected \"$expected\"; found \"$found\"";
return $error;
}//end prepareError()
/**
* Returns the patterns that should be checked.
*
* @return string[]
*/
abstract protected function getPatterns();
/**
* Registers any supplementary tokens that this test might wish to process.
*
* A sniff may wish to register supplementary tests when it wishes to group
* an arbitrary validation that cannot be performed using a pattern, with
* other pattern tests.
*
* @return int[]
* @see processSupplementary()
*/
protected function registerSupplementary()
{
return [];
}//end registerSupplementary()
/**
* Processes any tokens registered with registerSupplementary().
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to
* process the skip.
* @param int $stackPtr The position in the tokens stack to
* process.
*
* @return void
* @see registerSupplementary()
*/
protected function processSupplementary(File $phpcsFile, $stackPtr)
{
}//end processSupplementary()
/**
* Parses a pattern string into an array of pattern steps.
*
* @param string $pattern The pattern to parse.
*
* @return array The parsed pattern array.
* @see createSkipPattern()
* @see createTokenPattern()
*/
private function parse($pattern)
{
$patterns = [];
$length = strlen($pattern);
$lastToken = 0;
$firstToken = 0;
for ($i = 0; $i < $length; $i++) {
$specialPattern = false;
$isLastChar = ($i === ($length - 1));
$oldFirstToken = $firstToken;
if (substr($pattern, $i, 3) === '...') {
// It's a skip pattern. The skip pattern requires the
// content of the token in the "from" position and the token
// to skip to.
$specialPattern = $this->createSkipPattern($pattern, ($i - 1));
$lastToken = ($i - $firstToken);
$firstToken = ($i + 3);
$i += 2;
if ($specialPattern['to'] !== 'unknown') {
$firstToken++;
}
} else if (substr($pattern, $i, 3) === 'abc') {
$specialPattern = ['type' => 'string'];
$lastToken = ($i - $firstToken);
$firstToken = ($i + 3);
$i += 2;
} else if (substr($pattern, $i, 3) === 'EOL') {
$specialPattern = ['type' => 'newline'];
$lastToken = ($i - $firstToken);
$firstToken = ($i + 3);
$i += 2;
}//end if
if ($specialPattern !== false || $isLastChar === true) {
// If we are at the end of the string, don't worry about a limit.
if ($isLastChar === true) {
// Get the string from the end of the last skip pattern, if any,
// to the end of the pattern string.
$str = substr($pattern, $oldFirstToken);
} else {
// Get the string from the end of the last special pattern,
// if any, to the start of this special pattern.
if ($lastToken === 0) {
// Note that if the last special token was zero characters ago,
// there will be nothing to process so we can skip this bit.
// This happens if you have something like: EOL... in your pattern.
$str = '';
} else {
$str = substr($pattern, $oldFirstToken, $lastToken);
}
}
if ($str !== '') {
$tokenPatterns = $this->createTokenPattern($str);
foreach ($tokenPatterns as $tokenPattern) {
$patterns[] = $tokenPattern;
}
}
// Make sure we don't skip the last token.
if ($isLastChar === false && $i === ($length - 1)) {
$i--;
}
}//end if
// Add the skip pattern *after* we have processed
// all the tokens from the end of the last skip pattern
// to the start of this skip pattern.
if ($specialPattern !== false) {
$patterns[] = $specialPattern;
}
}//end for
return $patterns;
}//end parse()
/**
* Creates a skip pattern.
*
* @param string $pattern The pattern being parsed.
* @param string $from The token content that the skip pattern starts from.
*
* @return array The pattern step.
* @see createTokenPattern()
* @see parse()
*/
private function createSkipPattern($pattern, $from)
{
$skip = ['type' => 'skip'];
$nestedParenthesis = 0;
$nestedBraces = 0;
for ($start = $from; $start >= 0; $start--) {
switch ($pattern[$start]) {
case '(':
if ($nestedParenthesis === 0) {
$skip['to'] = 'parenthesis_closer';
}
$nestedParenthesis--;
break;
case '{':
if ($nestedBraces === 0) {
$skip['to'] = 'scope_closer';
}
$nestedBraces--;
break;
case '}':
$nestedBraces++;
break;
case ')':
$nestedParenthesis++;
break;
}//end switch
if (isset($skip['to']) === true) {
break;
}
}//end for
if (isset($skip['to']) === false) {
$skip['to'] = 'unknown';
}
return $skip;
}//end createSkipPattern()
/**
* Creates a token pattern.
*
* @param string $str The tokens string that the pattern should match.
*
* @return array The pattern step.
* @see createSkipPattern()
* @see parse()
*/
private function createTokenPattern($str)
{
// Don't add a space after the closing php tag as it will add a new
// whitespace token.
$tokenizer = new PHP('<?php '.$str.'?>', null);
// Remove the <?php tag from the front and the end php tag from the back.
$tokens = $tokenizer->getTokens();
$tokens = array_slice($tokens, 1, (count($tokens) - 2));
$patterns = [];
foreach ($tokens as $patternInfo) {
$patterns[] = [
'type' => 'token',
'token' => $patternInfo['code'],
'value' => $patternInfo['content'],
];
}
return $patterns;
}//end createTokenPattern()
}//end class

View file

@ -0,0 +1,191 @@
<?php
/**
* Allows tests that extend this class to listen for tokens within a particular scope.
*
* Below is a test that listens to methods that exist only within classes:
* <code>
* class ClassScopeTest extends PHP_CodeSniffer_Standards_AbstractScopeSniff
* {
* public function __construct()
* {
* parent::__construct(array(T_CLASS), array(T_FUNCTION));
* }
*
* protected function processTokenWithinScope(\PHP_CodeSniffer\Files\File $phpcsFile, $stackPtr, $currScope)
* {
* $className = $phpcsFile->getDeclarationName($currScope);
* echo 'encountered a method within class '.$className;
* }
* }
* </code>
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Sniffs;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Exceptions\RuntimeException;
abstract class AbstractScopeSniff implements Sniff
{
/**
* The token types that this test wishes to listen to within the scope.
*
* @var array
*/
private $tokens = [];
/**
* The type of scope opener tokens that this test wishes to listen to.
*
* @var string
*/
private $scopeTokens = [];
/**
* True if this test should fire on tokens outside of the scope.
*
* @var boolean
*/
private $listenOutside = false;
/**
* Constructs a new AbstractScopeTest.
*
* @param array $scopeTokens The type of scope the test wishes to listen to.
* @param array $tokens The tokens that the test wishes to listen to
* within the scope.
* @param boolean $listenOutside If true this test will also alert the
* extending class when a token is found outside
* the scope, by calling the
* processTokenOutsideScope method.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified tokens arrays are empty
* or invalid.
*/
public function __construct(
array $scopeTokens,
array $tokens,
$listenOutside=false
) {
if (empty($scopeTokens) === true) {
$error = 'The scope tokens list cannot be empty';
throw new RuntimeException($error);
}
if (empty($tokens) === true) {
$error = 'The tokens list cannot be empty';
throw new RuntimeException($error);
}
$invalidScopeTokens = array_intersect($scopeTokens, $tokens);
if (empty($invalidScopeTokens) === false) {
$invalid = implode(', ', $invalidScopeTokens);
$error = "Scope tokens [$invalid] can't be in the tokens array";
throw new RuntimeException($error);
}
$this->listenOutside = $listenOutside;
$this->scopeTokens = array_flip($scopeTokens);
$this->tokens = $tokens;
}//end __construct()
/**
* The method that is called to register the tokens this test wishes to
* listen to.
*
* DO NOT OVERRIDE THIS METHOD. Use the constructor of this class to register
* for the desired tokens and scope.
*
* @return int[]
* @see __constructor()
*/
final public function register()
{
return $this->tokens;
}//end register()
/**
* Processes the tokens that this test is listening for.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position in the stack where this
* token was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
* the rest of the file.
* @see processTokenWithinScope()
*/
final public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$foundScope = false;
$skipTokens = [];
foreach ($tokens[$stackPtr]['conditions'] as $scope => $code) {
if (isset($this->scopeTokens[$code]) === true) {
$skipTokens[] = $this->processTokenWithinScope($phpcsFile, $stackPtr, $scope);
$foundScope = true;
}
}
if ($this->listenOutside === true && $foundScope === false) {
$skipTokens[] = $this->processTokenOutsideScope($phpcsFile, $stackPtr);
}
if (empty($skipTokens) === false) {
return min($skipTokens);
}
return;
}//end process()
/**
* Processes a token that is found within the scope that this test is
* listening to.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position in the stack where this
* token was found.
* @param int $currScope The position in the tokens array that
* opened the scope that this test is
* listening for.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
* the rest of the file.
*/
abstract protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope);
/**
* Processes a token that is found outside the scope that this test is
* listening to.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position in the stack where this
* token was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return (count($tokens) + 1) to skip
* the rest of the file.
*/
abstract protected function processTokenOutsideScope(File $phpcsFile, $stackPtr);
}//end class

View file

@ -0,0 +1,230 @@
<?php
/**
* A class to find T_VARIABLE tokens.
*
* This class can distinguish between normal T_VARIABLE tokens, and those tokens
* that represent class members. If a class member is encountered, then the
* processMemberVar method is called so the extending class can process it. If
* the token is found to be a normal T_VARIABLE token, then processVariable is
* called.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Sniffs;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
abstract class AbstractVariableSniff extends AbstractScopeSniff
{
/**
* List of PHP Reserved variables.
*
* Used by various naming convention sniffs.
*
* @var array
*/
protected $phpReservedVars = [
'_SERVER' => true,
'_GET' => true,
'_POST' => true,
'_REQUEST' => true,
'_SESSION' => true,
'_ENV' => true,
'_COOKIE' => true,
'_FILES' => true,
'GLOBALS' => true,
'http_response_header' => true,
'HTTP_RAW_POST_DATA' => true,
'php_errormsg' => true,
];
/**
* Constructs an AbstractVariableTest.
*/
public function __construct()
{
$scopes = Tokens::$ooScopeTokens;
$listen = [
T_VARIABLE,
T_DOUBLE_QUOTED_STRING,
T_HEREDOC,
];
parent::__construct($scopes, $listen, true);
}//end __construct()
/**
* Processes the token in the specified PHP_CodeSniffer\Files\File.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this
* token was found.
* @param int $stackPtr The position where the token was found.
* @param int $currScope The current scope opener token.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
* the rest of the file.
*/
final protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope)
{
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING
|| $tokens[$stackPtr]['code'] === T_HEREDOC
) {
// Check to see if this string has a variable in it.
$pattern = '|(?<!\\\\)(?:\\\\{2})*\${?[a-zA-Z0-9_]+}?|';
if (preg_match($pattern, $tokens[$stackPtr]['content']) !== 0) {
return $this->processVariableInString($phpcsFile, $stackPtr);
}
return;
}
// If this token is nested inside a function at a deeper
// level than the current OO scope that was found, it's a normal
// variable and not a member var.
$conditions = array_reverse($tokens[$stackPtr]['conditions'], true);
$inFunction = false;
foreach ($conditions as $scope => $code) {
if (isset(Tokens::$ooScopeTokens[$code]) === true) {
break;
}
if ($code === T_FUNCTION || $code === T_CLOSURE) {
$inFunction = true;
}
}
if ($scope !== $currScope) {
// We found a closer scope to this token, so ignore
// this particular time through the sniff. We will process
// this token when this closer scope is found to avoid
// duplicate checks.
return;
}
// Just make sure this isn't a variable in a function declaration.
if ($inFunction === false && isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
foreach ($tokens[$stackPtr]['nested_parenthesis'] as $opener => $closer) {
if (isset($tokens[$opener]['parenthesis_owner']) === false) {
// Check if this is a USE statement for a closure.
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true);
if ($tokens[$prev]['code'] === T_USE) {
$inFunction = true;
break;
}
continue;
}
$owner = $tokens[$opener]['parenthesis_owner'];
if ($tokens[$owner]['code'] === T_FUNCTION
|| $tokens[$owner]['code'] === T_CLOSURE
) {
$inFunction = true;
break;
}
}
}//end if
if ($inFunction === true) {
return $this->processVariable($phpcsFile, $stackPtr);
} else {
return $this->processMemberVar($phpcsFile, $stackPtr);
}
}//end processTokenWithinScope()
/**
* Processes the token outside the scope in the file.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this
* token was found.
* @param int $stackPtr The position where the token was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
* the rest of the file.
*/
final protected function processTokenOutsideScope(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// These variables are not member vars.
if ($tokens[$stackPtr]['code'] === T_VARIABLE) {
return $this->processVariable($phpcsFile, $stackPtr);
} else if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING
|| $tokens[$stackPtr]['code'] === T_HEREDOC
) {
// Check to see if this string has a variable in it.
$pattern = '|(?<!\\\\)(?:\\\\{2})*\${?[a-zA-Z0-9_]+}?|';
if (preg_match($pattern, $tokens[$stackPtr]['content']) !== 0) {
return $this->processVariableInString($phpcsFile, $stackPtr);
}
}
}//end processTokenOutsideScope()
/**
* Called to process class member vars.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this
* token was found.
* @param int $stackPtr The position where the token was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
* the rest of the file.
*/
abstract protected function processMemberVar(File $phpcsFile, $stackPtr);
/**
* Called to process normal member vars.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this
* token was found.
* @param int $stackPtr The position where the token was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
* the rest of the file.
*/
abstract protected function processVariable(File $phpcsFile, $stackPtr);
/**
* Called to process variables found in double quoted strings or heredocs.
*
* Note that there may be more than one variable in the string, which will
* result only in one call for the string or one call per line for heredocs.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this
* token was found.
* @param int $stackPtr The position where the double quoted
* string was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return ($phpcsFile->numTokens + 1) to skip
* the rest of the file.
*/
abstract protected function processVariableInString(File $phpcsFile, $stackPtr);
}//end class

View file

@ -0,0 +1,80 @@
<?php
/**
* Represents a PHP_CodeSniffer sniff for sniffing coding standards.
*
* A sniff registers what token types it wishes to listen for, then, when
* PHP_CodeSniffer encounters that token, the sniff is invoked and passed
* information about where the token was found in the stack, and the
* PHP_CodeSniffer file in which the token was found.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Sniffs;
use PHP_CodeSniffer\Files\File;
interface Sniff
{
/**
* Registers the tokens that this sniff wants to listen for.
*
* An example return value for a sniff that wants to listen for whitespace
* and any comments would be:
*
* <code>
* return array(
* T_WHITESPACE,
* T_DOC_COMMENT,
* T_COMMENT,
* );
* </code>
*
* @return mixed[]
* @see Tokens.php
*/
public function register();
/**
* Called when one of the token types that this sniff is listening for
* is found.
*
* The stackPtr variable indicates where in the stack the token was found.
* A sniff can acquire information about this token, along with all the other
* tokens within the stack by first acquiring the token stack:
*
* <code>
* $tokens = $phpcsFile->getTokens();
* echo 'Encountered a '.$tokens[$stackPtr]['type'].' token';
* echo 'token information: ';
* print_r($tokens[$stackPtr]);
* </code>
*
* If the sniff discovers an anomaly in the code, they can raise an error
* by calling addError() on the \PHP_CodeSniffer\Files\File object, specifying an error
* message and the position of the offending token:
*
* <code>
* $phpcsFile->addError('Encountered an error', $stackPtr);
* </code>
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position in the PHP_CodeSniffer
* file's token stack where the token
* was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return (count($tokens) + 1) to skip
* the rest of the file.
*/
public function process(File $phpcsFile, $stackPtr);
}//end interface

View file

@ -0,0 +1,23 @@
<documentation title="Short Array Syntax">
<standard>
<![CDATA[
Short array syntax must be used to define arrays.
]]>
</standard>
<code_comparison>
<code title="Valid: Short form of array.">
<![CDATA[
$arr = <em>[</em>
'foo' => 'bar',
<em>]</em>;
]]>
</code>
<code title="Invalid: Long form of array.">
<![CDATA[
$arr = <em>array(</em>
'foo' => 'bar',
<em>)</em>;
]]>
</code>
</code_comparison>
</documentation>

View file

@ -0,0 +1,23 @@
<documentation title="Long Array Syntax">
<standard>
<![CDATA[
Long array syntax must be used to define arrays.
]]>
</standard>
<code_comparison>
<code title="Valid: Long form of array.">
<![CDATA[
$arr = <em>array(</em>
'foo' => 'bar',
<em>)</em>;
]]>
</code>
<code title="Invalid: Short form of array.">
<![CDATA[
$arr = <em>[</em>
'foo' => 'bar',
<em>]</em>;
]]>
</code>
</code_comparison>
</documentation>

View file

@ -0,0 +1,27 @@
<documentation title="Duplicate Class Names">
<standard>
<![CDATA[
Class and Interface names should be unique in a project. They should never be duplicated.
]]>
</standard>
<code_comparison>
<code title="Valid: A unique class name.">
<![CDATA[
class <em>Foo</em>
{
}
]]>
</code>
<code title="Invalid: A class duplicated (including across multiple files).">
<![CDATA[
class <em>Foo</em>
{
}
class <em>Foo</em>
{
}
]]>
</code>
</code_comparison>
</documentation>

Some files were not shown because too many files have changed in this diff Show more