Skip to content
This repository was archived by the owner on Jan 21, 2020. It is now read-only.

Commit dc3d49b

Browse files
committed
Added LicenseHeader and MD files sniffs
1 parent 4d14354 commit dc3d49b

18 files changed

+493
-3
lines changed

COPYRIGHT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Copyright (c) 2018, Zend Technologies USA, Inc.

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"phpunit/phpunit": "^7.0.1"
1616
},
1717
"autoload": {
18+
"files": ["src/ZendCodingStandard/helper.php"],
1819
"psr-4": {
1920
"ZendCodingStandard\\": "src/ZendCodingStandard/"
2021
}

phpcs.xml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
55
<rule ref="src/ZendCodingStandard/ruleset.xml"/>
66

7-
<file>src</file>
8-
<file>test</file>
9-
7+
<file>.</file>
108
<exclude-pattern>test/*.inc</exclude-pattern>
9+
<exclude-pattern>vendor/</exclude-pattern>
10+
11+
<rule ref="PSR1.Files.SideEffects">
12+
<exclude-pattern>src/ZendCodingStandard/helper.php</exclude-pattern>
13+
</rule>
1114
</ruleset>

ruleset.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<?xml version="1.0"?>
22
<ruleset name="Zend Framework Coding Standard">
33
<rule ref="vendor/zendframework/zend-coding-standard/src/ZendCodingStandard/ruleset.xml"/>
4+
5+
<file>../../../</file>
6+
<exclude-pattern>vendor/</exclude-pattern>
7+
<exclude-pattern>test/**/TestAsset/</exclude-pattern>
48
</ruleset>
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ZendCodingStandard\Sniffs\Commenting;
6+
7+
use PHP_CodeSniffer\Config;
8+
use PHP_CodeSniffer\Files\File;
9+
use PHP_CodeSniffer\Sniffs\Sniff;
10+
11+
use function array_merge;
12+
use function stripos;
13+
use function strtolower;
14+
use function strtr;
15+
16+
use const T_DECLARE;
17+
use const T_DOC_COMMENT_OPEN_TAG;
18+
use const T_OPEN_TAG;
19+
use const T_STRING;
20+
use const T_WHITESPACE;
21+
22+
class LicenseHeaderSniff implements Sniff
23+
{
24+
/**
25+
* @var null|string
26+
*/
27+
public $comment = <<<'EOC'
28+
/**
29+
* @see https://github.com/{org}/{repo} for the canonical source repository
30+
* @copyright https://github.com/{org}/{repo}/blob/master/COPYRIGHT.md Copyright
31+
* @license https://github.com/{org}/{repo}/blob/master/LICENSE.md New BSD License
32+
*/
33+
EOC;
34+
35+
/**
36+
* @var array
37+
*/
38+
public $variables = [];
39+
40+
/**
41+
* @return int[]
42+
*/
43+
public function register() : array
44+
{
45+
return [T_OPEN_TAG];
46+
}
47+
48+
/**
49+
* @param int $stackPtr
50+
* @return int
51+
*/
52+
public function process(File $phpcsFile, $stackPtr)
53+
{
54+
$tokens = $phpcsFile->getTokens();
55+
$next = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true);
56+
57+
if ($tokens[$next]['code'] === T_DECLARE) {
58+
$string = $phpcsFile->findNext(
59+
T_STRING,
60+
$tokens[$next]['parenthesis_opener'] + 1,
61+
$tokens[$next]['parenthesis_closer']
62+
);
63+
64+
// If the first statement in the file is strict type declaration.
65+
if ($string && stripos($tokens[$string]['content'], 'strict_types') !== false) {
66+
$eos = $phpcsFile->findEndOfStatement($next);
67+
$next = $phpcsFile->findNext(T_WHITESPACE, $eos + 1, null, true);
68+
}
69+
}
70+
71+
if ($next && $tokens[$next]['code'] === T_DOC_COMMENT_OPEN_TAG) {
72+
$prev = $phpcsFile->findPrevious(T_WHITESPACE, $next - 1, null, true);
73+
if ($tokens[$prev]['code'] === T_OPEN_TAG
74+
&& $tokens[$prev]['line'] + 1 !== $tokens[$next]['line']
75+
) {
76+
$error = 'License header must be in next line after opening PHP tag';
77+
$fix = $phpcsFile->addFixableError($error, $next, 'Line');
78+
79+
if ($fix) {
80+
$phpcsFile->fixer->beginChangeset();
81+
for ($i = $next - 1; $i > $prev; --$i) {
82+
$phpcsFile->fixer->replaceToken($i, '');
83+
}
84+
$phpcsFile->fixer->endChangeset();
85+
}
86+
}
87+
88+
$content = $phpcsFile->getTokensAsString($next, $tokens[$next]['comment_closer'] - $next + 1);
89+
$comment = $this->getComment();
90+
91+
if ($comment === $content) {
92+
return $phpcsFile->numTokens + 1;
93+
}
94+
95+
if ($this->hasTags($phpcsFile, $next)) {
96+
$error = 'Invalid doc license header';
97+
$fix = $phpcsFile->addFixableError($error, $next, 'Invalid');
98+
99+
if ($fix) {
100+
$phpcsFile->fixer->beginChangeset();
101+
for ($i = $next; $i < $tokens[$next]['comment_closer']; ++$i) {
102+
$phpcsFile->fixer->replaceToken($i, '');
103+
}
104+
$phpcsFile->fixer->replaceToken($tokens[$next]['comment_closer'], $comment);
105+
$phpcsFile->fixer->endChangeset();
106+
}
107+
108+
return $phpcsFile->numTokens + 1;
109+
}
110+
}
111+
112+
$error = 'Missing license header';
113+
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'Missing');
114+
115+
if ($fix) {
116+
$phpcsFile->fixer->addContent($stackPtr, $this->getComment() . $phpcsFile->eolChar);
117+
}
118+
119+
return $phpcsFile->numTokens + 1;
120+
}
121+
122+
private function getComment() : string
123+
{
124+
return strtr($this->comment, array_merge($this->getDefaultVariables(), $this->variables));
125+
}
126+
127+
private function hasTags(File $phpcsFile, int $comment) : bool
128+
{
129+
$tokens = $phpcsFile->getTokens();
130+
$tags = ['@copyright' => true, '@license' => true];
131+
132+
foreach ($tokens[$comment]['comment_tags'] ?? [] as $token) {
133+
$content = strtolower($tokens[$token]['content']);
134+
unset($tags[$content]);
135+
}
136+
137+
return ! $tags;
138+
}
139+
140+
private function getDefaultVariables() : array
141+
{
142+
return [
143+
'{org}' => Config::getConfigData('zfcs:org') ?: 'zendframework',
144+
'{repo}' => Config::getConfigData('zfcs:repo'),
145+
];
146+
}
147+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ZendCodingStandard\Sniffs\Files;
6+
7+
use PHP_CodeSniffer\Config;
8+
use PHP_CodeSniffer\Files\File;
9+
use PHP_CodeSniffer\Sniffs\Sniff;
10+
11+
use function array_merge;
12+
use function basename;
13+
use function file_get_contents;
14+
use function filter_var;
15+
use function gmdate;
16+
use function preg_match;
17+
use function rtrim;
18+
use function strpos;
19+
use function strstr;
20+
use function strtolower;
21+
use function strtr;
22+
use function substr;
23+
use function trim;
24+
use function ucfirst;
25+
26+
use const FILTER_VALIDATE_URL;
27+
use const T_MD_LINE;
28+
29+
class MdSniff implements Sniff
30+
{
31+
/**
32+
* @var string[]
33+
*/
34+
public $supportedTokenizers = [
35+
'MD',
36+
];
37+
38+
/**
39+
* @var string[]
40+
*/
41+
public $templates = [
42+
// @phpcs:disable Generic.Files.LineLength.TooLong
43+
'LICENSE.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/LICENSE.md',
44+
'COPYRIGHT.md' => 'Copyright (c) {year}, Zend Technologies USA, Inc.' . "\n",
45+
'CODE_OF_CONDUCT.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/CODE_OF_CONDUCT.md',
46+
'CONTRIBUTING.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/CONTRIBUTING.md',
47+
'ISSUE_TEMPLATE.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/ISSUE_TEMPLATE.md',
48+
'PULL_REQUEST_TEMPLATE.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/PULL_REQUEST_TEMPLATE.md',
49+
'SUPPORT.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/SUPPORT.md',
50+
// @phpcs:enable
51+
];
52+
53+
/**
54+
* @var string[]
55+
*/
56+
public $variables = [];
57+
58+
/**
59+
* @return int[]
60+
*/
61+
public function register() : array
62+
{
63+
return [T_MD_LINE];
64+
}
65+
66+
/**
67+
* @param int $stackPtr
68+
* @return int
69+
*/
70+
public function process(File $phpcsFile, $stackPtr)
71+
{
72+
$template = $this->getTemplate($phpcsFile->getFilename());
73+
if ($template === null) {
74+
$tokens = $phpcsFile->getTokens();
75+
76+
do {
77+
$content = $tokens[$stackPtr]['content'];
78+
$expected = rtrim($content) . (substr($content, -1) === "\n" ? "\n" : '');
79+
80+
if ($content !== $expected) {
81+
$error = 'White characters found at the end of the line';
82+
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'WhiteChars');
83+
84+
if ($fix) {
85+
$phpcsFile->fixer->replaceToken($stackPtr, $expected);
86+
}
87+
}
88+
} while ($stackPtr = $phpcsFile->findNext(T_MD_LINE, $stackPtr + 1));
89+
90+
if (trim($tokens[$phpcsFile->numTokens - 1]['content']) !== '') {
91+
$error = 'Missing empty line at the end of the file';
92+
$fix = $phpcsFile->addFixableError($error, $phpcsFile->numTokens - 1, 'MissingEmptyLine');
93+
94+
if ($fix) {
95+
$phpcsFile->fixer->addNewline($phpcsFile->numTokens - 1);
96+
}
97+
} elseif ($phpcsFile->numTokens > 1 && trim($tokens[$phpcsFile->numTokens - 2]['content']) === '') {
98+
$error = 'Redundant empty line at the end of the file';
99+
$fix = $phpcsFile->addFixableError($error, $phpcsFile->numTokens - 1, 'RedundantEmptyLine');
100+
101+
if ($fix) {
102+
$phpcsFile->fixer->replaceToken($phpcsFile->numTokens - 2, '');
103+
}
104+
}
105+
106+
return $phpcsFile->numTokens + 1;
107+
}
108+
109+
$content = $phpcsFile->getTokensAsString(0, $phpcsFile->numTokens);
110+
111+
$variables = $this->variables;
112+
if (preg_match('/\s(\d{4})(-\d{4})?/', $content, $match)) {
113+
$year = $match[1];
114+
$currentYear = gmdate('Y');
115+
if ($year < $currentYear) {
116+
$year .= '-' . $currentYear;
117+
}
118+
$variables['{year}'] = $year;
119+
}
120+
121+
$newContent = strtr($template, array_merge($this->getDefaultVariables(), $variables));
122+
123+
if ($content !== $newContent) {
124+
$error = 'Content is outdated; found %s; expected %s';
125+
$data = [$content, $newContent];
126+
$code = ucfirst(strtolower(strstr(basename($phpcsFile->getFilename()), '.', true)));
127+
$fix = $phpcsFile->addFixableError($error, $stackPtr, $code, $data);
128+
129+
if ($fix) {
130+
$phpcsFile->fixer->beginChangeset();
131+
for ($i = 0; $i < $phpcsFile->numTokens; ++$i) {
132+
$phpcsFile->fixer->replaceToken($i, '');
133+
}
134+
$phpcsFile->fixer->addContent(0, $newContent);
135+
$phpcsFile->fixer->endChangeset();
136+
}
137+
}
138+
139+
return $phpcsFile->numTokens + 1;
140+
}
141+
142+
private function getTemplate(string $filename) : ?string
143+
{
144+
foreach ($this->templates as $name => $template) {
145+
if (strpos($filename, '/' . $name) !== false) {
146+
if (filter_var($template, FILTER_VALIDATE_URL)) {
147+
return file_get_contents($template);
148+
}
149+
150+
return $template;
151+
}
152+
}
153+
154+
return null;
155+
}
156+
157+
private function getDefaultVariables() : array
158+
{
159+
return [
160+
'{category}' => Config::getConfigData('zfcs:category') ?: 'components',
161+
'{org}' => Config::getConfigData('zfcs:org') ?: 'zendframework',
162+
'{repo}' => Config::getConfigData('zfcs:repo'),
163+
'{year}' => gmdate('Y'),
164+
];
165+
}
166+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ZendCodingStandard\Tokenizer;
6+
7+
use PHP_CodeSniffer\Tokenizers\Tokenizer;
8+
9+
use function explode;
10+
use function preg_replace;
11+
12+
use const T_MD_LINE;
13+
14+
class MD extends Tokenizer
15+
{
16+
/**
17+
* Creates an array of tokens when given some content.
18+
*
19+
* @param string $string The string to tokenize.
20+
* @return array
21+
*/
22+
protected function tokenize($string)
23+
{
24+
$lines = explode("\n", $string);
25+
26+
foreach ($lines as $n => &$line) {
27+
$line = [
28+
'content' => $line . "\n",
29+
'code' => T_MD_LINE,
30+
'type' => 'T_MD_LINE',
31+
];
32+
}
33+
34+
$line['content'] = preg_replace('/\n$/', '', $line['content']);
35+
36+
return $lines;
37+
}
38+
39+
/**
40+
* Performs additional processing after main tokenizing.
41+
*/
42+
protected function processAdditional()
43+
{
44+
}
45+
}

0 commit comments

Comments
 (0)