Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/sniffs_system - twigcs #35

Open
wants to merge 53 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
77e0d12
Add phpunit/phpunit to composer.json
adrienrn Nov 1, 2017
734e24b
First attempt at sniffs
adrienrn Nov 1, 2017
aa24645
Try to organize things a bit
adrienrn Nov 1, 2017
97d63e3
Fixing some things, adding one warning for trans
adrienrn Nov 1, 2017
bd7b47b
Some more sniffs + default severity and utils
adrienrn Nov 2, 2017
fe543cc
Preprocessor (wip)
adrienrn Nov 5, 2017
707e1bc
Clean preprocessor + Pre / Post parser sniff interface and abstract c…
adrienrn Nov 5, 2017
67bac54
Glue things together, finally (Linter.php + Ruleset.php)
adrienrn Nov 6, 2017
04b8077
New Report class, enable/disbale sniff, Linter makes more, clean test…
adrienrn Nov 6, 2017
9bfa9ea
Detect simple / double quotes (SimpleQuotesSniff.php) + clean
adrienrn Nov 6, 2017
3164941
Add position & filename to the token / node and to the final report (…
adrienrn Nov 6, 2017
f4759f1
Compute stats on the final report + tests
adrienrn Nov 6, 2017
1fcc1aa
EnforceHashKeyQuotesSniff + tests
adrienrn Nov 7, 2017
000f877
Sniffs for hashes + tests
adrienrn Nov 7, 2017
eb5196a
Return a Twig_Node from CatchAll to be able to sniff them!
adrienrn Nov 8, 2017
1341b3f
Clean some things (tests mainly)
adrienrn Nov 9, 2017
5a55096
Load files using Finder, first try at twigcs.yml + add new sniff for …
adrienrn Nov 9, 2017
f8129f1
Added twigcs.yml - loading, parsing, validating + tests with Ruleset.php
adrienrn Nov 10, 2017
d4d8343
Reorganize tests + fix comments with {% and {{ inside
adrienrn Nov 10, 2017
3ef1b27
Reorganize tests (fixtures files)
adrienrn Nov 10, 2017
32b4130
Factorization of code (AbstractSniff) + severity as an option
adrienrn Nov 10, 2017
a39cc05
Added RulesetFactory to easy things with config files
adrienrn Nov 10, 2017
65deaeb
New twigcs command + various fixes
adrienrn Nov 10, 2017
b8e1f13
Replace simple array by SniffViolation.php + fixes
adrienrn Nov 11, 2017
45bea3e
Use \Twig_Source + some fixes
adrienrn Nov 11, 2017
1ba77ed
Add copyright on all new files!
adrienrn Nov 11, 2017
195c15f
PHPdoc (almost) everywhere
adrienrn Nov 11, 2017
164a89c
Adjust composer.json version for php5.3 - php5.5
adrienrn Nov 11, 2017
94e969f
Fixes for php5.3+ short array and const
adrienrn Nov 12, 2017
d0c9172
Make Travis work with php5.3
adrienrn Nov 12, 2017
6437871
Another try at fixing php5.3 in travis
adrienrn Nov 12, 2017
79f7360
Tokenizer errors + catch in Linter.php with new message
adrienrn Nov 13, 2017
e1517f3
Add missing *Sniff suffix to WhitespaceBeforeAfterExpression.php
adrienrn Nov 13, 2017
de38830
Split sniffs to do one specific task + more tests
adrienrn Nov 15, 2017
0ff5eaf
Remove the use of Twig_TokenParserBroker (deprecated) + fix all depre…
adrienrn Nov 15, 2017
5db3fa4
Remove the use of Twig_TokenParserBroker (deprecated) + fix all depre…
adrienrn Nov 15, 2017
e355fe2
paths / exclude via twigcs.yml + fixes for files, deprecated warnings
adrienrn Nov 15, 2017
b7db5fe
Fix EnsureBlankAtEOFSniff when file is empty
adrienrn Nov 18, 2017
40f6ae2
Added text formatter + filtering on level or severity
adrienrn Nov 18, 2017
a034e38
Fix EnsureHashKeyQuotesSniff for complex keys eg. expressions
adrienrn Nov 18, 2017
886b966
Timing / memory consumption + 'explain' options on TextFormatter
adrienrn Nov 19, 2017
9307f7d
Fix EnsureHashKeyQuotesSniff again + fix tests
adrienrn Nov 19, 2017
6921278
phpcs / php compat
adrienrn Nov 19, 2017
fdcd69d
Apply fix for travis, php 5.3 and trusty
adrienrn Nov 19, 2017
2c4f400
DisallowCommentedCodesniff + tests
adrienrn Nov 24, 2017
31f3f1d
Added an autoloader and a config for external sniffs
adrienrn Nov 28, 2017
2846627
Deleted unused fixture file 'twigcs.yml'
adrienrn Nov 28, 2017
746bd27
Find and load 'twigcs_global.yml' from $HOME for external sniffs
adrienrn Nov 28, 2017
ca109cb
Remove typo in class name (case sensitivity)
adrienrn Nov 28, 2017
0f23be9
Fix two words tests 'same as' and 'divisible by' for example
adrienrn Nov 30, 2017
76fe4d7
Forward-compatibility for phpunit and PHP7
adrienrn Nov 30, 2017
7acd15b
Merge branch 'remove/deprecated_warnings' into feature/sniffs_system
adrienrn Nov 30, 2017
fc7a1a1
Oops composer.json after merge
adrienrn Nov 30, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
language: php

dist: trusty

sudo: false

php:
- 5.3
- 5.4
- 5.5
- 5.6
Expand All @@ -13,3 +14,8 @@ php:
before_script: composer install

script: phpunit

matrix:
include: # https://github.com/travis-ci/travis-ci/issues/7712#issuecomment-300553336
- php: "5.3"
dist: precise
8 changes: 7 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
"require": {
"symfony/console": "^2.1 || ^3.0",
"symfony/finder": "^2.1 || ^3.0",
"twig/twig": "^1.16.2"
"twig/twig": "^1.16.2",
"symfony/config": "^2.8 || ^3.0",
"symfony/stopwatch": "^2.8 || ^3.0",
"aura/autoload": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^5.5 || ^6.2"
},
"autoload": {
"psr-0": { "Asm89\\Twig\\Lint\\": "src/" }
Expand Down
36 changes: 29 additions & 7 deletions src/Asm89/Twig/Lint/Command/LintCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ protected function configure()
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Excludes, based on regex, paths of files and folders from parsing'
),
new InputOption(
'stub-tag',
'',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'List of tags that the lint command has to provide stub for',
array()
),
new InputOption(
'stub-test',
'',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'List of tests that the lint command has to provide stub for',
array()
),
new InputOption('only-print-errors', '', InputOption::VALUE_NONE),
new InputOption('summary', '', InputOption::VALUE_NONE)
))
Expand Down Expand Up @@ -81,12 +95,20 @@ protected function configure()

protected function execute(InputInterface $input, CliOutputInterface $output)
{
$twig = new StubbedEnvironment(new \Twig_Loader_String());
$template = null;
$filename = $input->getArgument('filename');
$exclude = $input->getOption('exclude');
$summary = $input->getOption('summary');
$output = $this->getOutput($output, $input->getOption('format'));
$template = null;
$filename = $input->getArgument('filename');
$exclude = $input->getOption('exclude');
$stubTagList = $input->getOption('stub-tag');
$stubTestsList = $input->getOption('stub-test');
$summary = $input->getOption('summary');
$output = $this->getOutput($output, $input->getOption('format'));
$twig = new StubbedEnvironment(
new \Twig_Loader_Array(),
array(
'stub_tags' => $stubTagList,
'stub_tests' => $stubTestsList,
)
);

if (!$filename) {
if (0 !== ftell(STDIN)) {
Expand Down Expand Up @@ -151,7 +173,7 @@ protected function validateTemplate(
)
{
try {
$twig->parse($twig->tokenize($template, $file ? (string) $file : null));
$twig->parse($twig->tokenize(new \Twig_Source($template, $file ? (string) $file : null)));
if (false === $onlyPrintErrors) {
$output->ok($template, $file);
}
Expand Down
169 changes: 169 additions & 0 deletions src/Asm89/Twig/Lint/Command/TwigCSCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
<?php

/*
* This file is part of twig-lint.
*
* (c) Alexander <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Asm89\Twig\Lint\Command;

use Asm89\Twig\Lint\Config;
use Asm89\Twig\Lint\Linter;
use Asm89\Twig\Lint\RulesetFactory;
use Asm89\Twig\Lint\StubbedEnvironment;
use Asm89\Twig\Lint\Config\Loader;
use Asm89\Twig\Lint\Report\TextFormatter;
use Asm89\Twig\Lint\Tokenizer\Tokenizer;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Finder\Finder;

/**
* TwigCS stands for "Twig Code Sniffer" and will check twig template againt all
* rules which have been defined in the twigcs.yml of your project.
*
* This is heavily inspired by the symfony lint command and PHP_CodeSniffer tool
* (https://github.com/squizlabs/PHP_CodeSniffer).
*
* @author Hussard <[email protected]>
*/
class TwigCSCommand extends Command
{
protected function configure()
{
$this
->setName('twigcs')
->setDescription('Lints a template and outputs encountered errors')
->setDefinition(array(
new InputOption(
'exclude',
'',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Excludes, based on regex, paths of files and folders from parsing',
array()
),
new InputOption(
'format',
'',
InputOption::VALUE_OPTIONAL,
'Implemented formats are: full',
'full'
),
new InputOption(
'level',
'',
InputOption::VALUE_OPTIONAL,
'Allowed values are: warning, error',
'warning'
),
new InputOption(
'severity',
'',
InputOption::VALUE_OPTIONAL,
'Allowed values are: 0 - 10',
''
),
new InputOption(
'working-dir',
'',
InputOption::VALUE_OPTIONAL,
'Run as if this was started in <working-dir> instead of the current working directory',
getcwd()
),
))
->addArgument('filename', InputArgument::OPTIONAL)
->setHelp(<<<EOF
The <info>%command.name%</info> will check twig templates against a set of rules defined in
a "twigcs.yml".

<info>php %command.full_name% filename</info>

The command gets the contents of <comment>filename</comment> and outputs violations of the rules to stdout.

<info>php %command.full_name% dirname</info>

The command finds all twig templates in <comment>dirname</comment> and validates the syntax
of each Twig templates.

EOF
)
;
}

/**
* @{inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$filename = $input->getArgument('filename');
$exclude = $input->getOption('exclude');
$format = $input->getOption('format');
$level = $input->getOption('level');
$severity = $input->getOption('severity');
$currentDir = $input->getOption('working-dir');

// Load config files.
$globalLoader = new Loader(new FileLocator(getenv('HOME') . '/.twigcs'));
$loader = new Loader(new FileLocator($currentDir));

$globalConfig = array();
try {
$globalConfig = $globalLoader->load('twigcs_global.yml');
} catch (\Exception $e) {
// The global config file may not exist but it's ok.
}

// Compute the final config object.
$config = new Config(
$globalConfig,
$loader->load('twigcs.yml'),
array('workingDirectory' => $currentDir)
);

$twig = new StubbedEnvironment(new \Twig_Loader_Array(), array('stub_tags' => $config->get('stub')));
$linter = new Linter($twig, new Tokenizer($twig));
$factory = new RulesetFactory();
$reporter = $this->getReportFormatter($input, $output, $format);
$exitCode = 0;

// Get the rules to apply.
$ruleset = $factory->createRulesetFromConfig($config);

// Execute the linter.
$report = $linter->run($config->findFiles(), $ruleset);

// Format the output.
$reporter->display($report, array(
'level' => $level,
'severity' => $severity,
));

// Return a meaningful error code.
if ($report->getTotalErrors()) {
$exitCode = 1;
}

return $exitCode;
}

protected function getReportFormatter($input, $output, $format)
{
switch ($format) {
case 'full':
return new TextFormatter($input, $output, array('explain' => true));
case 'text':
return new TextFormatter($input, $output);
default:
throw new \Exception(sprintf('Unknown format "%s"', $format));
}
}
}
134 changes: 134 additions & 0 deletions src/Asm89/Twig/Lint/Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

/*
* This file is part of twig-lint.
*
* (c) Alexander <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Asm89\Twig\Lint;

use Aura\Autoload\Loader as Autoloader;
use Symfony\Component\Finder\Finder;

/**
* TwigCS configuration data.
*
* @author Hussard <[email protected]>
*/
class Config
{
/**
* Default configuration.
*
* @var array
*/
public static $defaultConfig = array(
'exclude' => array(),
'pattern' => '*.twig',
'paths' => array(),
'standardPaths' => array(),
'stub' => array(),
'workingDirectory' => '',
);

/**
* Current configuration.
*
* @var array
*/
protected $config;

/**
* Autoloader for sniffs.
*
* @var Autoloader\Loader
*/
protected $autoloader;

/**
* Constructor.
*/
public function __construct()
{
$args = func_get_args();

$this->config = $this::$defaultConfig;
foreach ($args as $arg) {
$this->config = array_merge($this->config, $arg);
}

$this->autoloader = new Autoloader();
$this->autoloader->register();

$standardPaths = $this->get('standardPaths');
if ($standardPaths) {
$this->autoloader->setPrefixes($standardPaths);
}
}

/**
* Find all files to process, based on a file or directory and exclude patterns.
*
* @param string $fileOrDirectory a file or a directory.
* @param array $exclude array of exclude patterns.
*
* @return array
*/
public function findFiles($fileOrDirectory = null, $exclude = null)
{
$files = array();

if (is_file($fileOrDirectory)) {
// Early return with the given file. Should we exclude things to here?
return array($fileOrDirectory);
}

if (is_dir($fileOrDirectory)) {
$fileOrDirectory = array($fileOrDirectory);
}

if (!$fileOrDirectory) {
$fileOrDirectory = $this->get('paths');
$exclude = $this->get('exclude');
}

// Build the finder.
$files = Finder::create()
->in($this->get('workingDirectory'))
->name($this->config['pattern'])
->files()
;

// Include all matching paths.
foreach ($fileOrDirectory as $path) {
$files->path($path);
}

// Exclude all matching paths.
if ($exclude) {
$files->exclude($exclude);
}

return $files;
}

/**
* Get a configuration value for the given $key.
*
* @param string $key
*
* @return any
*/
public function get($key)
{
if (!isset($this->config[$key])) {
throw new \Exception(sprintf('Configuration key "%s" does not exist', $key));
}

return $this->config[$key];
}
}
Loading