diff --git a/COPYRIGHT.md b/COPYRIGHT.md
new file mode 100644
index 00000000..42bc88cf
--- /dev/null
+++ b/COPYRIGHT.md
@@ -0,0 +1 @@
+Copyright (c) 2016-2018, Zend Technologies USA, Inc.
diff --git a/composer.json b/composer.json
index 7209b89d..b32e13f7 100644
--- a/composer.json
+++ b/composer.json
@@ -15,6 +15,7 @@
"phpunit/phpunit": "^7.0.1"
},
"autoload": {
+ "files": ["src/ZendCodingStandard/helper.php"],
"psr-4": {
"ZendCodingStandard\\": "src/ZendCodingStandard/"
}
diff --git a/phpcs.xml b/phpcs.xml
index 13388786..849465f6 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -4,8 +4,11 @@
xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
- src
- test
-
+ .
test/*.inc
+ vendor/
+
+
+ src/ZendCodingStandard/helper.php
+
diff --git a/ruleset.xml b/ruleset.xml
index 35c6319a..92248c0d 100644
--- a/ruleset.xml
+++ b/ruleset.xml
@@ -1,4 +1,8 @@
+
+ ../../../
+ vendor/
+ test/**/TestAsset/
diff --git a/src/ZendCodingStandard/CodingStandard.php b/src/ZendCodingStandard/CodingStandard.php
index e1919fd4..a72729f7 100644
--- a/src/ZendCodingStandard/CodingStandard.php
+++ b/src/ZendCodingStandard/CodingStandard.php
@@ -1,4 +1,9 @@
getTokens();
+ $next = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true);
+
+ if ($tokens[$next]['code'] === T_DECLARE) {
+ $string = $phpcsFile->findNext(
+ T_STRING,
+ $tokens[$next]['parenthesis_opener'] + 1,
+ $tokens[$next]['parenthesis_closer']
+ );
+
+ // If the first statement in the file is strict type declaration.
+ if ($string && stripos($tokens[$string]['content'], 'strict_types') !== false) {
+ $eos = $phpcsFile->findEndOfStatement($next);
+ $next = $phpcsFile->findNext(T_WHITESPACE, $eos + 1, null, true);
+ }
+ }
+
+ if ($next && $tokens[$next]['code'] === T_DOC_COMMENT_OPEN_TAG) {
+ $prev = $phpcsFile->findPrevious(T_WHITESPACE, $next - 1, null, true);
+ if ($tokens[$prev]['code'] === T_OPEN_TAG
+ && $tokens[$prev]['line'] + 1 !== $tokens[$next]['line']
+ ) {
+ $error = 'License header must be in next line after opening PHP tag';
+ $fix = $phpcsFile->addFixableError($error, $next, 'Line');
+
+ if ($fix) {
+ $phpcsFile->fixer->beginChangeset();
+ for ($i = $next - 1; $i > $prev; --$i) {
+ $phpcsFile->fixer->replaceToken($i, '');
+ }
+ $phpcsFile->fixer->endChangeset();
+ }
+ }
+
+ $content = $phpcsFile->getTokensAsString($next, $tokens[$next]['comment_closer'] - $next + 1);
+ $comment = $this->getComment();
+
+ if ($comment === $content) {
+ return $phpcsFile->numTokens + 1;
+ }
+
+ if ($this->hasTags($phpcsFile, $next)) {
+ $error = 'Invalid doc license header';
+ $fix = $phpcsFile->addFixableError($error, $next, 'Invalid');
+
+ if ($fix) {
+ $phpcsFile->fixer->beginChangeset();
+ for ($i = $next; $i < $tokens[$next]['comment_closer']; ++$i) {
+ $phpcsFile->fixer->replaceToken($i, '');
+ }
+ $phpcsFile->fixer->replaceToken($tokens[$next]['comment_closer'], $comment);
+ $phpcsFile->fixer->endChangeset();
+ }
+
+ return $phpcsFile->numTokens + 1;
+ }
+ }
+
+ $error = 'Missing license header';
+ $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Missing');
+
+ if ($fix) {
+ $phpcsFile->fixer->addContent($stackPtr, $this->getComment() . $phpcsFile->eolChar);
+ }
+
+ return $phpcsFile->numTokens + 1;
+ }
+
+ private function getComment() : string
+ {
+ return strtr($this->comment, array_merge($this->getDefaultVariables(), $this->variables));
+ }
+
+ private function hasTags(File $phpcsFile, int $comment) : bool
+ {
+ $tokens = $phpcsFile->getTokens();
+ $tags = ['@copyright' => true, '@license' => true];
+
+ foreach ($tokens[$comment]['comment_tags'] ?? [] as $token) {
+ $content = strtolower($tokens[$token]['content']);
+ unset($tags[$content]);
+ }
+
+ return ! $tags;
+ }
+
+ private function getDefaultVariables() : array
+ {
+ return [
+ '{org}' => Config::getConfigData('zfcs:org') ?: 'zendframework',
+ '{repo}' => Config::getConfigData('zfcs:repo'),
+ ];
+ }
+}
diff --git a/src/ZendCodingStandard/Sniffs/Commenting/NoInlineCommentAfterCurlyCloseSniff.php b/src/ZendCodingStandard/Sniffs/Commenting/NoInlineCommentAfterCurlyCloseSniff.php
index 2001db83..60af2c68 100644
--- a/src/ZendCodingStandard/Sniffs/Commenting/NoInlineCommentAfterCurlyCloseSniff.php
+++ b/src/ZendCodingStandard/Sniffs/Commenting/NoInlineCommentAfterCurlyCloseSniff.php
@@ -1,4 +1,9 @@
run) {
+ return $phpcsFile->numTokens + 1;
+ }
+ $this->run = true;
+
+ foreach ($this->files as $file) {
+ if (! file_exists($file)) {
+ $error = 'File %s does not exist';
+ $data = [$file];
+ $fix = $phpcsFile->addFixableError($error, 0, 'NotExists', $data);
+
+ if ($fix) {
+ touch($file);
+ }
+ }
+ }
+
+ return $phpcsFile->numTokens + 1;
+ }
+}
diff --git a/src/ZendCodingStandard/Sniffs/Files/DeclareStrictTypesSniff.php b/src/ZendCodingStandard/Sniffs/Files/DeclareStrictTypesSniff.php
index ddf542a7..a473142e 100644
--- a/src/ZendCodingStandard/Sniffs/Files/DeclareStrictTypesSniff.php
+++ b/src/ZendCodingStandard/Sniffs/Files/DeclareStrictTypesSniff.php
@@ -1,4 +1,9 @@
'https://raw.githubusercontent.com/zendframework/maintainers/master/template/LICENSE.md',
+ 'COPYRIGHT.md' => 'Copyright (c) {year}, Zend Technologies USA, Inc.' . "\n",
+ 'CODE_OF_CONDUCT.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/CODE_OF_CONDUCT.md',
+ 'CONTRIBUTING.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/CONTRIBUTING.md',
+ 'ISSUE_TEMPLATE.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/ISSUE_TEMPLATE.md',
+ 'PULL_REQUEST_TEMPLATE.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/PULL_REQUEST_TEMPLATE.md',
+ 'SUPPORT.md' => 'https://raw.githubusercontent.com/zendframework/maintainers/master/template/docs/SUPPORT.md',
+ // @phpcs:enable
+ ];
+
+ /**
+ * @var string[]
+ */
+ public $variables = [];
+
+ /**
+ * @var string
+ */
+ public $yearTimezone = 'GMT';
+
+ /**
+ * @var null|string
+ */
+ private $yearRange;
+
+ /**
+ * @return int[]
+ */
+ public function register() : array
+ {
+ return [T_MD_LINE];
+ }
+
+ /**
+ * @param int $stackPtr
+ * @return int
+ */
+ public function process(File $phpcsFile, $stackPtr)
+ {
+ $template = $this->getTemplate($phpcsFile->getFilename());
+ if ($template === null) {
+ $tokens = $phpcsFile->getTokens();
+
+ do {
+ $content = $tokens[$stackPtr]['content'];
+ $expected = rtrim($content) . (substr($content, -1) === "\n" ? "\n" : '');
+
+ if ($content !== $expected) {
+ $error = 'White characters found at the end of the line';
+ $fix = $phpcsFile->addFixableError($error, $stackPtr, 'WhiteChars');
+
+ if ($fix) {
+ $phpcsFile->fixer->replaceToken($stackPtr, $expected);
+ }
+ }
+ } while ($stackPtr = $phpcsFile->findNext(T_MD_LINE, $stackPtr + 1));
+
+ if (trim($tokens[$phpcsFile->numTokens - 1]['content']) !== '') {
+ $error = 'Missing empty line at the end of the file';
+ $fix = $phpcsFile->addFixableError($error, $phpcsFile->numTokens - 1, 'MissingEmptyLine');
+
+ if ($fix) {
+ $phpcsFile->fixer->addNewline($phpcsFile->numTokens - 1);
+ }
+ } elseif ($phpcsFile->numTokens > 1 && trim($tokens[$phpcsFile->numTokens - 2]['content']) === '') {
+ $error = 'Redundant empty line at the end of the file';
+ $fix = $phpcsFile->addFixableError($error, $phpcsFile->numTokens - 1, 'RedundantEmptyLine');
+
+ if ($fix) {
+ $phpcsFile->fixer->replaceToken($phpcsFile->numTokens - 2, '');
+ }
+ }
+
+ return $phpcsFile->numTokens + 1;
+ }
+
+ $content = $phpcsFile->getTokensAsString(0, $phpcsFile->numTokens);
+
+ $newContent = strtr($template, array_merge($this->getDefaultVariables(), $this->variables));
+
+ if ($content !== $newContent) {
+ $error = 'Content is outdated; found %s; expected %s';
+ $data = [$content, $newContent];
+ $code = ucfirst(strtolower(strstr(basename($phpcsFile->getFilename()), '.', true)));
+ $fix = $phpcsFile->addFixableError($error, $stackPtr, $code, $data);
+
+ if ($fix) {
+ $phpcsFile->fixer->beginChangeset();
+ for ($i = 0; $i < $phpcsFile->numTokens; ++$i) {
+ $phpcsFile->fixer->replaceToken($i, '');
+ }
+ $phpcsFile->fixer->addContent(0, $newContent);
+ $phpcsFile->fixer->endChangeset();
+ }
+ }
+
+ return $phpcsFile->numTokens + 1;
+ }
+
+ private function getTemplate(string $filename) : ?string
+ {
+ foreach ($this->templates as $name => $template) {
+ if (strpos($filename, '/' . $name) !== false) {
+ if (filter_var($template, FILTER_VALIDATE_URL)) {
+ return file_get_contents($template);
+ }
+
+ return $template;
+ }
+ }
+
+ return null;
+ }
+
+ private function getDefaultVariables() : array
+ {
+ return [
+ '{category}' => Config::getConfigData('zfcs:category') ?: 'components',
+ '{org}' => Config::getConfigData('zfcs:org') ?: 'zendframework',
+ '{repo}' => Config::getConfigData('zfcs:repo'),
+ '{year}' => $this->getYearRange(),
+ ];
+ }
+
+ private function getYearRange() : string
+ {
+ if (! $this->yearRange) {
+ $timezone = new DateTimeZone($this->yearTimezone);
+
+ exec('git tag -l --format=\'%(taggerdate)\' | head -n 1', $output, $return);
+ $date = new DateTime($return === 0 && isset($output[0]) ? $output[0] : 'now');
+ $date->setTimezone($timezone);
+ $this->yearRange = $date->format('Y');
+
+ $currentYear = (new DateTime('now', $timezone))->format('Y');
+ if ($this->yearRange < $currentYear) {
+ $this->yearRange .= '-' . $currentYear;
+ }
+ }
+
+ return $this->yearRange;
+ }
+}
diff --git a/src/ZendCodingStandard/Sniffs/Formatting/DoubleColonSniff.php b/src/ZendCodingStandard/Sniffs/Formatting/DoubleColonSniff.php
index a7c0f328..1a7749c6 100644
--- a/src/ZendCodingStandard/Sniffs/Formatting/DoubleColonSniff.php
+++ b/src/ZendCodingStandard/Sniffs/Formatting/DoubleColonSniff.php
@@ -1,4 +1,9 @@
&$line) {
+ $line = [
+ 'content' => $line . "\n",
+ 'code' => T_MD_LINE,
+ 'type' => 'T_MD_LINE',
+ ];
+ }
+
+ $line['content'] = preg_replace('/\n$/', '', $line['content']);
+
+ return $lines;
+ }
+
+ /**
+ * Performs additional processing after main tokenizing.
+ */
+ protected function processAdditional()
+ {
+ }
+}
diff --git a/src/ZendCodingStandard/helper.php b/src/ZendCodingStandard/helper.php
new file mode 100644
index 00000000..facc4ea6
--- /dev/null
+++ b/src/ZendCodingStandard/helper.php
@@ -0,0 +1,34 @@
+
Zend Framework Coding Standard
+
+
+
diff --git a/test/Ruleset.php b/test/Ruleset.php
index 59f7e27d..dbdabc3b 100644
--- a/test/Ruleset.php
+++ b/test/Ruleset.php
@@ -1,4 +1,9 @@
1];
+ case 'LicenseHeaderUnitTest.3.inc':
+ return [3 => 2];
+ }
+
+ return [
+ 1 => 1,
+ ];
+ }
+
+ public function getWarningList(string $testFile = '') : array
+ {
+ return [];
+ }
+}
diff --git a/test/Sniffs/Commenting/NoInlineCommentAfterCurlyCloseUnitTest.php b/test/Sniffs/Commenting/NoInlineCommentAfterCurlyCloseUnitTest.php
index 038c01b8..e7b2cdc2 100644
--- a/test/Sniffs/Commenting/NoInlineCommentAfterCurlyCloseUnitTest.php
+++ b/test/Sniffs/Commenting/NoInlineCommentAfterCurlyCloseUnitTest.php
@@ -1,4 +1,9 @@