Completed
Push — add/cs-package ( 5b344d )
by
unknown
17:21 queued 10:11
created

process_matched_token()   F

Complexity

Conditions 20
Paths 330

Size

Total Lines 90

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
nc 330
nop 3
dl 0
loc 90
rs 1.7083
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This sniff verifies a WordPress hook function has a preceding docblock.
4
 *
5
 * @package   automattic/jetpack-coding-standards
6
 */
7
8
namespace Automattic\Jetpack\CodingStandards\Sniffs\InlineDocs;
9
10
use WordPressCS\WordPress\AbstractFunctionRestrictionsSniff;
11
use PHP_CodeSniffer\Util\Tokens;
12
13
/**
14
 * Class HooksMustHaveDocblockSniff
15
 *
16
 * @package Automattic\Jetpack\CodingStandards\Sniffs\InlineDocs
17
 */
18
class HooksMustHaveDocblockSniff extends AbstractFunctionRestrictionsSniff {
19
20
	/**
21
	 * Array of WordPress hook execution functions.
22
	 *
23
	 * @var array WordPress hook execution function name => filter or action.
24
	 */
25
	protected $hook_functions = array(
26
		'apply_filters'            => 'filter',
27
		'apply_filters_ref_array'  => 'filter',
28
		'apply_filters_deprecated' => 'filter',
29
		'do_action'                => 'action',
30
		'do_action_ref_array'      => 'action',
31
		'do_action_deprecated'     => 'action',
32
	);
33
34
	/**
35
	 * Groups of functions to restrict.
36
	 *
37
	 * Example: groups => array(
38
	 *  'lambda' => array(
39
	 *      'type'      => 'error' | 'warning',
40
	 *      'message'   => 'Use anonymous functions instead please!',
41
	 *      'functions' => array( 'file_get_contents', 'create_function' ),
42
	 *  )
43
	 * )
44
	 *
45
	 * @return array
46
	 */
47
	public function getGroups() {
48
		return array(
49
			'hooks' => array(
50
				'functions' => array_keys( $this->hook_functions ),
51
			),
52
		);
53
	}
54
55
	/**
56
	 * Process a matched token.
57
	 *
58
	 * @since 1.0.0 Logic split off from the `process_token()` method.
59
	 *
60
	 * @param int    $stack_ptr       The position of the current token in the stack.
61
	 * @param string $group_name      The name of the group which was matched.
62
	 * @param string $matched_content The token content (function name) which was matched.
63
	 *
64
	 * @return int|void Integer stack pointer to skip forward or void to continue
65
	 *                  normal file processing.
66
	 */
67
	public function process_matched_token( $stack_ptr, $group_name, $matched_content ) {
68
69
		$func_open_paren_token = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stack_ptr + 1 ), null, true );
70
		if ( false === $func_open_paren_token
71
			|| \T_OPEN_PARENTHESIS !== $this->tokens[ $func_open_paren_token ]['code']
72
			|| ! isset( $this->tokens[ $func_open_paren_token ]['parenthesis_closer'] )
73
		) {
74
			// Live coding, parse error or not a function call.
75
			return;
76
		}
77
78
		$previous_comment = $this->phpcsFile->findPrevious( Tokens::$commentTokens, ( $stack_ptr - 1 ) );
79
80
		if ( false !== $previous_comment ) {
81
			/*
82
			 * Check to determine if there is a comment immediately preceding the function call.
83
			 */
84
			if ( ( $this->tokens[ $previous_comment ]['line'] + 1 ) !== $this->tokens[ $stack_ptr ]['line'] ) {
85
				$this->phpcsFile->addError(
86
					'The inline documentation for a hook must be on the line immediately before the function call.',
87
					$stack_ptr,
88
					'DocMustBePreceding'
89
				);
90
			}
91
92
			/*
93
			 * Check that the comment starts is a docblock.
94
			 */
95
			if ( \T_DOC_COMMENT_CLOSE_TAG !== $this->tokens[ $previous_comment ]['code'] ) {
96
				$this->phpcsFile->addError(
97
					'Hooks must include a docblock with /** formatting */',
98
					$stack_ptr,
99
					'NoDocblockFound'
100
				);
101
				// return; Do we need to return here? Can we keep going?
102
			}
103
104
			/*
105
			 * Process docblock tags.
106
			 */
107
			$comment_end   = $previous_comment;
108
			$comment_start = ( isset( $this->tokens[ $comment_end ]['comment_opener'] ) ) ? $this->tokens[ $comment_end ]['comment_opener'] : false;
109
			$has           = array(
110
				'module' => false,
111
				'since'  => false,
112
			);
113
114
			// The comment isn't a docblock, so we're going to stop here.
115
			if ( ! $comment_start ) {
116
				return;
117
			}
118
119
			$string = $this->phpcsFile->findNext( T_DOC_COMMENT_STRING, $comment_start, $comment_end );
120
			// If the call is documented elsewhere, stop here.
121
			if ( 0 === strpos( $this->tokens[ $string ]['content'], 'This filter is documented in' ) ) {
122
				return;
123
			}
124
125
			foreach ( $this->tokens[ $comment_start ]['comment_tags'] as $tag ) {
126
				// Is the next tag of the docblock the "@module" tag?
127
				if ( '@module' === $this->tokens[ $tag ]['content'] ) {
128
					// This is used later to determine if we need to throw an error for no module tag.
129
					$has['module'] = true;
130
131
					// Find the next string, which will be the text after the @module.
132
					$string = $this->phpcsFile->findNext( T_DOC_COMMENT_STRING, $tag, $comment_end );
133
					// If it is false, there is no text or if the text is on the another line, error.
134
					if ( false === $string || $this->tokens[ $string ]['line'] !== $this->tokens[ $tag ]['line'] ) {
135
						$this->phpcsFile->addError( 'Module tag must have a value.', $tag, 'EmptyModule' );
136
					}
137
				}
138
139
				if ( '@since' === $this->tokens[ $tag ]['content'] ) {
140
					$has['since'] = true;
141
					$string       = $this->phpcsFile->findNext( T_DOC_COMMENT_STRING, $tag, $comment_end );
142
					if ( false === $string || $this->tokens[ $string ]['line'] !== $this->tokens[ $tag ]['line'] ) {
143
						$this->phpcsFile->addError( 'Since tag must have a value.', $tag, 'EmptySince' );
144
					} elseif ( ! preg_match( '\'/^\d+\.\d+\.\d+/\'', $string ) ) { // Requires X.Y.Z. Trailing 0 is needed for a major release.
145
						$this->phpcsFile->addError( 'Since tag must have a X.Y.Z. version number.', $tag, 'InvalidSince' );
146
					}
147
				}
148
			}
149
150
			foreach ( $has as $name => $present ) {
151
				if ( ! $present ) {
152
					$this->phpcsFile->addError( 'Hook documentation is missing a tag: ' . $name, $comment_start, 'No' . ucfirst( $name ) );
153
				}
154
			}
155
		}
156
	}
157
}
158