Skip to content

Commit a22e869

Browse files
authored
Merge pull request #348 from humanmade/add-pattern-filename-slug-match-sniff
2 parents 0fdff4e + 310491f commit a22e869

7 files changed

Lines changed: 163 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
<!-- When submitting a PR for a feature, add the appropriate prerelease heading here
44
and fill in the contents as you go. This simplifies later release management. -->
55

6+
## 2.5.0
7+
8+
- Add `HM.Files.PatternSlugMatchesFilename` sniff verifying that a theme pattern file's `Slug:` header matches its filename
9+
610
## 2.4.0
711

812
- Add `HM.Functions.RegisterBlockTypePath` sniff recommending `register_block_type_from_metadata()` when a file path is passed to `register_block_type()`
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
/**
3+
* Sniff verifying that theme pattern slugs match their filenames.
4+
*/
5+
6+
namespace HM\Sniffs\Files;
7+
8+
use PHP_CodeSniffer\Files\File;
9+
use PHP_CodeSniffer\Sniffs\Sniff;
10+
11+
/**
12+
* Flags theme pattern files whose Slug: header does not end with the file's basename.
13+
*
14+
* A pattern with `Slug: my-theme/hero-banner` must live in `hero-banner.php`.
15+
*/
16+
class PatternSlugMatchesFilenameSniff implements Sniff {
17+
18+
/**
19+
* Register for the open tag so the check runs once per file.
20+
*
21+
* @return array<int|string>
22+
*/
23+
public function register() : array {
24+
return [ T_OPEN_TAG ];
25+
}
26+
27+
/**
28+
* Compare the trailing segment of the pattern's Slug header to the filename.
29+
*
30+
* @param File $phpcs_file The file being scanned.
31+
* @param int $stack_ptr Position of the open tag.
32+
* @return int Pointer past the end of the file, so the sniff runs only once.
33+
*/
34+
public function process( File $phpcs_file, $stack_ptr ) {
35+
if ( ! preg_match( '#/themes/[^/]+/patterns/([^/]+)\.php$#', $phpcs_file->getFilename(), $matches ) ) {
36+
return $phpcs_file->numTokens;
37+
}
38+
$filename = $matches[1];
39+
40+
foreach ( $phpcs_file->getTokens() as $ptr => $token ) {
41+
if ( T_DOC_COMMENT_STRING !== $token['code'] ) {
42+
continue;
43+
}
44+
if ( ! preg_match( '#^Slug:\s*(\S+)#', trim( $token['content'] ), $slug_match ) ) {
45+
continue;
46+
}
47+
48+
$slug = $slug_match[1];
49+
if ( basename( $slug ) !== $filename ) {
50+
$phpcs_file->addError(
51+
'Pattern slug "%s" does not match the filename; expected the file to be named "%s.php".',
52+
$ptr,
53+
'SlugMismatch',
54+
[ $slug, basename( $slug ) ]
55+
);
56+
}
57+
break;
58+
}
59+
60+
return $phpcs_file->numTokens;
61+
}
62+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace HM\Tests\Files;
4+
5+
use RecursiveDirectoryIterator;
6+
use RecursiveIteratorIterator;
7+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
8+
9+
/**
10+
* Class PatternSlugMatchesFilenameUnitTest
11+
*
12+
* @group hm-sniffs
13+
*/
14+
class PatternSlugMatchesFilenameUnitTest extends AbstractSniffUnitTest {
15+
/**
16+
* Get files to test against.
17+
*
18+
* Overridden from base to recurse through the fixture directory, since
19+
* the sniff only applies to files within a theme's patterns directory.
20+
*/
21+
protected function getTestFiles( $test_base_dir ) {
22+
$test_base_dir = rtrim( $test_base_dir, '.' );
23+
$test_files = [];
24+
25+
$di = new RecursiveIteratorIterator(
26+
new RecursiveDirectoryIterator( $test_base_dir )
27+
);
28+
29+
foreach ( $di as $file ) {
30+
if ( ! $file->isFile() ) {
31+
continue;
32+
}
33+
34+
$test_files[] = $file->getPathname();
35+
}
36+
37+
// Put them in order.
38+
sort( $test_files );
39+
40+
return $test_files;
41+
}
42+
43+
/**
44+
* Returns the lines where errors should occur.
45+
*
46+
* @return array <int line number> => <int number of errors>
47+
*/
48+
public function getErrorList() {
49+
$file = func_get_arg( 0 );
50+
$fail = [
51+
'renamed-pattern.php' => [
52+
4 => 1,
53+
],
54+
];
55+
56+
return $fail[ $file ] ?? [];
57+
}
58+
59+
/**
60+
* Returns the lines where warnings should occur.
61+
*
62+
* @return array <int line number> => <int number of warnings>
63+
*/
64+
public function getWarningList() {
65+
return [];
66+
}
67+
68+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
/**
3+
* Title: Not a pattern
4+
* Slug: my-theme/some-other-slug
5+
*
6+
* Files outside a patterns directory are ignored even if they have a Slug header.
7+
*/
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
/**
3+
* Title: Hero Banner
4+
* Slug: my-theme/hero-banner
5+
* Categories: banner
6+
*/
7+
?>
8+
<!-- wp:paragraph --><p>Hero banner content.</p><!-- /wp:paragraph -->
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
/**
3+
* A pattern-directory file without a Slug header is not flagged.
4+
*/
5+
?>
6+
<!-- wp:paragraph --><p>No slug here.</p><!-- /wp:paragraph -->
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
/**
3+
* Title: Hero Banner
4+
* Slug: my-theme/hero-banner
5+
* Categories: banner
6+
*/
7+
?>
8+
<!-- wp:paragraph --><p>The slug above does not match this filename.</p><!-- /wp:paragraph -->

0 commit comments

Comments
 (0)