Skip to content

Commit

Permalink
Linter: add class Latte\Tools\Linter [Closes nette#286]
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed May 7, 2022
1 parent 2e21dba commit 5e49c82
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 111 deletions.
114 changes: 3 additions & 111 deletions bin/latte-lint
Original file line number Diff line number Diff line change
Expand Up @@ -38,115 +38,7 @@ if ($argc < 2) {
}

$debug = in_array('--debug', $argv, true);
$ok = scanPath($argv[1], $debug);
$path = $argv[1];
$linter = new Latte\Tools\Linter(null, $debug);
$ok = $linter->scanDirectory($path);
exit($ok ? 0 : 1);


function scanPath(string $path, bool $debug): bool
{
echo "Scanning $path\n";

$it = new RecursiveDirectoryIterator($path);
$it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::LEAVES_ONLY);
$it = new RegexIterator($it, '~\.latte$~');

$counter = 0;
$success = true;
$latte = createLatte();
foreach ($it as $file) {
echo str_pad(str_repeat('.', $counter++ % 40), 40), "\x0D";
$success = lintLatte($latte, (string) $file, $debug) && $success;
}

echo str_pad('', 40), "\x0D";
echo "Done.\n";
return $success;
}


function createLatte(): Latte\Engine
{
$latte = new Latte\Engine;
$latte->setLoader(new Latte\Loaders\StringLoader);

if (class_exists(Nette\Bridges\CacheLatte\CacheMacro::class)) {
$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
}
if (class_exists(Nette\Bridges\ApplicationLatte\UIMacros::class)) {
Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
}
if (class_exists(Nette\Bridges\FormsLatte\FormMacros::class)) {
Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
}

return $latte;
}


function lintLatte(Latte\Engine $latte, string $file, bool $debug): bool
{
set_error_handler(function (int $severity, string $message) use ($file) {
if (in_array($severity, [E_USER_DEPRECATED, E_USER_WARNING, E_USER_NOTICE], true)) {
$pos = preg_match('~on line (\d+)~', $message, $m) ? ':' . $m[1] : '';
fwrite(STDERR, "[DEPRECATED] $file$pos $message\n");
return null;
}
return false;
});

if ($debug) {
echo $file, "\n";
}
$s = file_get_contents($file);
if (substr($s, 0, 3) === "\xEF\xBB\xBF") {
fwrite(STDERR, "[WARNING] $file contains BOM\n");
$contents = substr($s, 3);
}

try {
$code = $latte->compile($s);

} catch (Latte\CompileException $e) {
if ($debug) {
echo $e;
}
$pos = $e->sourceLine ? ':' . $e->sourceLine : '';
fwrite(STDERR, "[ERROR] $file$pos {$e->getMessage()}\n");
return false;

} finally {
restore_error_handler();
}

if ($error = lintPHP($code)) {
fwrite(STDERR, "[ERROR] $file $error\n");
return false;
}

return true;
}


function lintPHP(string $code): ?string
{
$php = defined('PHP_BINARY') ? PHP_BINARY : 'php';
$stdin = tmpfile();
fwrite($stdin, $code);
fseek($stdin, 0);
$process = proc_open(
$php . ' -l -d display_errors=1',
[$stdin, ['pipe', 'w'], ['pipe', 'w']],
$pipes,
null,
null,
['bypass_shell' => true],
);
if (!is_resource($process)) {
return 'Unable to lint PHP code';
}
$error = stream_get_contents($pipes[1]);
if (proc_close($process)) {
return strip_tags(explode("\n", $error)[1]);
}
return null;
}
144 changes: 144 additions & 0 deletions src/Tools/Linter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

/**
* This file is part of the Latte (https://latte.nette.org)
* Copyright (c) 2008 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Latte\Tools;

use Latte;
use Nette;


final class Linter
{
use Latte\Strict;

/** @var bool */
private $debug;

/** @var Latte\Engine|null */
private $engine;


public function __construct(?Latte\Engine $engine = null, bool $debug = false)
{
$this->engine = $engine;
$this->debug = $debug;
}


public function scanDirectory(string $dir): bool
{
echo "Scanning $dir\n";

$it = new \RecursiveDirectoryIterator($dir);
$it = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::LEAVES_ONLY);
$it = new \RegexIterator($it, '~\.latte$~');

$this->engine = $this->engine ?? $this->createEngine();
$this->engine->setLoader(new Latte\Loaders\StringLoader);

$counter = 0;
$success = true;
foreach ($it as $file) {
echo str_pad(str_repeat('.', $counter++ % 40), 40), "\x0D";
$success = $this->lintLatte((string) $file) && $success;
}

echo str_pad('', 40), "\x0D";
echo "Done.\n";
return $success;
}


private function createEngine(): Latte\Engine
{
$engine = new Latte\Engine;

if (class_exists(Nette\Bridges\CacheLatte\CacheMacro::class)) {
$engine->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
}

if (class_exists(Nette\Bridges\ApplicationLatte\UIMacros::class)) {
Nette\Bridges\ApplicationLatte\UIMacros::install($engine->getCompiler());
}

if (class_exists(Nette\Bridges\FormsLatte\FormMacros::class)) {
Nette\Bridges\FormsLatte\FormMacros::install($engine->getCompiler());
}

return $engine;
}


public function lintLatte(string $file): bool
{
set_error_handler(function (int $severity, string $message) use ($file) {
if (in_array($severity, [E_USER_DEPRECATED, E_USER_WARNING, E_USER_NOTICE], true)) {
$pos = preg_match('~on line (\d+)~', $message, $m) ? ':' . $m[1] : '';
fwrite(STDERR, "[DEPRECATED] $file$pos $message\n");
return null;
}
return false;
});

if ($this->debug) {
echo $file, "\n";
}
$s = file_get_contents($file);
if (substr($s, 0, 3) === "\xEF\xBB\xBF") {
fwrite(STDERR, "[WARNING] $file contains BOM\n");
}

try {
$code = $this->engine->compile($s);

} catch (Latte\CompileException $e) {
if ($this->debug) {
echo $e;
}
$pos = $e->sourceLine ? ':' . $e->sourceLine : '';
fwrite(STDERR, "[ERROR] $file$pos {$e->getMessage()}\n");
return false;

} finally {
restore_error_handler();
}

if ($error = $this->lintPHP($code)) {
fwrite(STDERR, "[ERROR] $file $error\n");
return false;
}

return true;
}


private function lintPHP(string $code): ?string
{
$php = defined('PHP_BINARY') ? PHP_BINARY : 'php';
$stdin = tmpfile();
fwrite($stdin, $code);
fseek($stdin, 0);
$process = proc_open(
$php . ' -l -d display_errors=1',
[$stdin, ['pipe', 'w'], ['pipe', 'w']],
$pipes,
null,
null,
['bypass_shell' => true]
);
if (!is_resource($process)) {
return 'Unable to lint PHP code';
}
$error = stream_get_contents($pipes[1]);
if (proc_close($process)) {
return strip_tags(explode("\n", $error)[1]);
}
return null;
}
}

0 comments on commit 5e49c82

Please sign in to comment.