From c1447a29244d79fdf98313f5e61f5257a1913ce6 Mon Sep 17 00:00:00 2001 From: Bill Mitchell Date: Sat, 10 Dec 2016 20:28:11 +0000 Subject: [PATCH] Fixes #23 --- README.md | 12 +- .../Files/FunctionNameContainsAndOrSniff.php | 7 +- .../Sniffs/Files/FunctionParameterSniff.php | 4 +- .../Sniffs/Files/IndentationLevelSniff.php | 107 ++++++++ src/Codor/Sniffs/Files/ReturnNullSniff.php | 3 +- .../ClosureWithNoCode.inc | 5 + .../ClosureWithNoIndentation.inc | 6 + .../ClosureWithOneIndentation.inc | 9 + .../ClosureWithTwoIndentation.inc | 12 + .../FunctionWithNoCode.inc | 6 + .../FunctionWithNoIndentation.inc | 6 + .../FunctionWithOneIndentation.inc | 9 + .../FunctionWithTwoIndentation.inc | 12 + .../IndentationLevelSniff/InvalidClass.inc | 40 +++ .../MethodWithNoCode.inc | 8 + .../MethodWithNoIndentation.inc | 9 + .../MethodWithOneIndentation.inc | 12 + .../MethodWithTwoIndentation.inc | 15 ++ .../SwitchStatementInFunctionWithNoCode.inc | 8 + ...chStatementInFunctionWithNoIndentation.inc | 9 + ...hStatementInFunctionWithOneIndentation.inc | 11 + ...hStatementInFunctionWithTwoIndentation.inc | 13 + .../SwitchStatementInMethodWithNoCode.inc | 10 + ...itchStatementInMethodWithNoIndentation.inc | 11 + ...tchStatementInMethodWithOneIndentation.inc | 13 + ...tchStatementInMethodWithTwoIndentation.inc | 15 ++ .../SwitchStatementWithNoCode.inc | 4 + .../SwitchStatementWithNoIndentation.inc | 5 + .../SwitchStatementWithOneIndentation.inc | 7 + .../SwitchStatementWithTwoIndentation.inc | 9 + .../IndentationLevelSniff/ValidClass.inc | 37 +++ .../Files/IndentationLevelSniffTest.php | 244 ++++++++++++++++++ 32 files changed, 667 insertions(+), 11 deletions(-) create mode 100644 src/Codor/Sniffs/Files/IndentationLevelSniff.php create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/ClosureWithNoCode.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/ClosureWithNoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/ClosureWithOneIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/ClosureWithTwoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/FunctionWithNoCode.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/FunctionWithNoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/FunctionWithOneIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/FunctionWithTwoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/InvalidClass.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/MethodWithNoCode.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/MethodWithNoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/MethodWithOneIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/MethodWithTwoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementInFunctionWithNoCode.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementInFunctionWithNoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementInFunctionWithOneIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementInFunctionWithTwoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementInMethodWithNoCode.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementInMethodWithNoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementInMethodWithOneIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementInMethodWithTwoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementWithNoCode.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementWithNoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementWithOneIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/SwitchStatementWithTwoIndentation.inc create mode 100644 tests/Sniffs/Files/Assets/IndentationLevelSniff/ValidClass.inc create mode 100644 tests/Sniffs/Files/IndentationLevelSniffTest.php diff --git a/README.md b/README.md index 2fbe594..54c5131 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,10 @@ Classes must be no more than 200 lines. ### Codor.Files.FunctionNameContainsAndOr ### Functions/methods cannot contain "And" or "Or". This could be a sign of a function that does more than one thing. +### Codor.Files.IndentationLevel ### +Functions/methods cannot have more than 1 level of indentation. + + ## Customizing Sniffs ## Some of the sniff rules can be customized to your liking. For example, if you'd want the `Codor.Files.FunctionLength` to make sure your functions are no more than 30 lines instead of 20, you can do that. Here's an example of a `codor.xml` file with that customization: ``` @@ -94,11 +98,13 @@ Some of the sniff rules can be customized to your liking. For example, if you'd ### Customizations Available * `Codor.Files.FunctionLength` - * `maxLength`: The maximum number of lines a function/method can be. + * `maxLength`: The maximum number of lines a function/method can be (default = 200). * `Codor.Files.FunctionParameter` - * `maxParameters`: The maximum number of parameters a function/method can have. + * `maxParameters`: The maximum number of parameters a function/method can have (default = 3). * `Codor.Classes.ClassLength` - * `maxLength`: The maximum number of lines a Class can be. + * `maxLength`: The maximum number of lines a Class can be (default = 20). +* `Codor.Files.IndentationLevel` + * `indentationLimit`: Cannot have more than or equal to this number of indentations (default = 2). ## Contributing ## Please see [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/src/Codor/Sniffs/Files/FunctionNameContainsAndOrSniff.php b/src/Codor/Sniffs/Files/FunctionNameContainsAndOrSniff.php index 87d1caa..f6e1558 100644 --- a/src/Codor/Sniffs/Files/FunctionNameContainsAndOrSniff.php +++ b/src/Codor/Sniffs/Files/FunctionNameContainsAndOrSniff.php @@ -52,13 +52,12 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) */ protected function containsKeywords($string) { + $contains = false; foreach ($this->keywords as $keyword) { - if ($this->contains($keyword, $string)) { - return true; - } + $this->contains($keyword, $string) ? $contains = true : null; } - return false; + return $contains; } /** diff --git a/src/Codor/Sniffs/Files/FunctionParameterSniff.php b/src/Codor/Sniffs/Files/FunctionParameterSniff.php index 08fdcf2..f52c3aa 100644 --- a/src/Codor/Sniffs/Files/FunctionParameterSniff.php +++ b/src/Codor/Sniffs/Files/FunctionParameterSniff.php @@ -40,9 +40,7 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) $numberOfParameters = 0; for ($index=$openParenIndex+1; $index <= $closedParenIndex; $index++) { - if ($tokens[$index]['type'] == 'T_VARIABLE') { - $numberOfParameters++; - } + $tokens[$index]['type'] == 'T_VARIABLE' ? $numberOfParameters++ : null; } if ($numberOfParameters > $this->maxParameters) { diff --git a/src/Codor/Sniffs/Files/IndentationLevelSniff.php b/src/Codor/Sniffs/Files/IndentationLevelSniff.php new file mode 100644 index 0000000..24143b1 --- /dev/null +++ b/src/Codor/Sniffs/Files/IndentationLevelSniff.php @@ -0,0 +1,107 @@ += to this number. + * @var integer + */ + public $indentationLimit = 2; + + /** + * The highest indentation level found. + * @var integer + */ + protected $maxIndentationFound; + + /** + * Returns the token types that this sniff is interested in. + * @return array + */ + public function register() + { + return [T_FUNCTION, T_CLOSURE, T_SWITCH]; + } + + /** + * Processes the tokens that this sniff is interested in. + * + * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. + * @param integer $stackPtr The position in the stack where + * the token was found. + * @return void + */ + public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $token = $tokens[$stackPtr]; + $this->maxIndentationFound = 0; + + // Ignore functions with no body + if (isset($token['scope_opener']) === false) { + return; + } + + $this->inspectScope($token, $tokens); + + if ($this->maxIndentationFound <= $this->indentationLimit) { + return; + } + + $phpcsFile->addError($this->getErrorMessage(), $stackPtr); + } + + /** + * Inspect the tokens in the scope of the provided $token. + * @codingStandardsIgnoreStart + * @param array $token Token Data. + * @param array $tokens Tokens. + * @return void + */ + protected function inspectScope(array $token, array $tokens) + { + $start = $token['scope_opener']; + $end = $token['scope_closer']; + $this->relativeScopeLevel = $tokens[$start]['level']; + for ($index=$start; $index <= $end; $index++) { + $nestedToken = $tokens[$index]; + if ($nestedToken['type'] === "T_SWITCH") { + return; + } + $this->adjustMaxIndentationFound($nestedToken); + } + } + // @codingStandardsIgnoreEnd + + /** + * Adjust the maximum indentation level found value. + * @param array $nestedToken Token data. + * @return void + */ + protected function adjustMaxIndentationFound(array $nestedToken) + { + $tokenNestedLevel = $nestedToken['level']; + $nestedLevel = $tokenNestedLevel - $this->relativeScopeLevel; + $nestedLevel > $this->maxIndentationFound ? $this->maxIndentationFound = $nestedLevel : null; + } + + /** + * Produce the error message. + * @return string + */ + protected function getErrorMessage() + { + // Hack to fix the output numbers for the + // indentation levels found and the + // indentation limit. + $indentationFound = $this->maxIndentationFound - 1; + $indentationLimit = $this->indentationLimit - 1; + return "{$indentationFound} indenation levels found. " . + "Maximum of {$indentationLimit} indenation levels allowed."; + } +} diff --git a/src/Codor/Sniffs/Files/ReturnNullSniff.php b/src/Codor/Sniffs/Files/ReturnNullSniff.php index dd50ede..c96bfe7 100644 --- a/src/Codor/Sniffs/Files/ReturnNullSniff.php +++ b/src/Codor/Sniffs/Files/ReturnNullSniff.php @@ -19,7 +19,7 @@ public function register() /** * Processes the tokens that this sniff is interested in. - * + * @codingStandardsIgnoreStart * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. * @param integer $stackPtr The position in the stack where * the token was found. @@ -39,6 +39,7 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) break; } } + // @codingStandardsIgnoreEnd if ($returnValueToken['type'] === 'T_NULL') { $error = "Return null value found."; diff --git a/tests/Sniffs/Files/Assets/IndentationLevelSniff/ClosureWithNoCode.inc b/tests/Sniffs/Files/Assets/IndentationLevelSniff/ClosureWithNoCode.inc new file mode 100644 index 0000000..85934dc --- /dev/null +++ b/tests/Sniffs/Files/Assets/IndentationLevelSniff/ClosureWithNoCode.inc @@ -0,0 +1,5 @@ +runner->setSniff('Codor.Files.IndentationLevel')->setFolder(__DIR__.'/Assets/IndentationLevelSniff/'); + } + + /** @test */ + public function a_class_with_many_valid_methods_produces_no_errors_or_warnings() + { + $results = $this->runner->sniff('ValidClass.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_class_with_one_invalid_methods_produces_no_errors_or_warnings() + { + $results = $this->runner->sniff('InvalidClass.inc'); + $this->assertEquals(1, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_function_with_no_code_produces_no_errors_or_warnings() + { + $results = $this->runner->sniff('FunctionWithNoCode.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_function_with_no_indentation_produces_no_errors_or_warnings() + { + $results = $this->runner->sniff('FunctionWithNoIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_function_with_one_level_of_indentation_produces_no_errors_or_warnings() + { + $results = $this->runner->sniff('FunctionWithOneIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_function_with_two_levels_of_indentation_produces_an_error() + { + $results = $this->runner->sniff('FunctionWithTwoIndentation.inc'); + $this->assertEquals(1, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + + $errorMessages = $results->getAllErrorMessages(); + $this->assertAllEqual('2 indenation levels found. Maximum of 1 indenation levels allowed.', $errorMessages); + } + + /** @test */ + public function a_method_with_no_code_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('MethodWithNoCode.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_method_with_no_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('MethodWithNoIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_method_with_one_level_of_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('MethodWithOneIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_method_with_two_levels_of_indentation_produces_an_error() + { + $results = $this->runner->sniff('MethodWithTwoIndentation.inc'); + $this->assertEquals(1, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + + $errorMessages = $results->getAllErrorMessages(); + $this->assertAllEqual('2 indenation levels found. Maximum of 1 indenation levels allowed.', $errorMessages); + } + + /** @test */ + public function a_closure_with_code_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('ClosureWithNoCode.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_closure_with_no_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('ClosureWithNoIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_closure_with_one_level_of_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('ClosureWithOneIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_closure_with_two_levels_of_indentation_produces_an_error() + { + $results = $this->runner->sniff('ClosureWithTwoIndentation.inc'); + $this->assertEquals(1, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + + $errorMessages = $results->getAllErrorMessages(); + $this->assertAllEqual('2 indenation levels found. Maximum of 1 indenation levels allowed.', $errorMessages); + } + + /** @test */ + public function a_switch_with_no_code_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementWithNoCode.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_switch_with_no_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementWithNoIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_switch_with_one_level_of_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementWithOneIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_switch_with_two_levels_of_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementWithTwoIndentation.inc'); + $this->assertEquals(1, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + $errorMessages = $results->getAllErrorMessages(); + $this->assertAllEqual('2 indenation levels found. Maximum of 1 indenation levels allowed.', $errorMessages); + } + + /** @test */ + public function a_switch_in_a_function_with_no_code_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementInFunctionWithNoCode.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_switch_in_a_function_with_no_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementInFunctionWithNoIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_switch_in_a_function_with_one_level_of_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementInFunctionWithOneIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_switch_in_a_function_with_two_levels_of_indentation_produces_an_error() + { + $results = $this->runner->sniff('SwitchStatementInFunctionWithTwoIndentation.inc'); + $this->assertEquals(1, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + $errorMessages = $results->getAllErrorMessages(); + $this->assertAllEqual('2 indenation levels found. Maximum of 1 indenation levels allowed.', $errorMessages); + } + + /** @test */ + public function a_switch_in_a_method_with_no_code_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementInMethodWithNoCode.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_switch_in_a_method_with_no_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementInMethodWithNoIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_switch_in_a_method_with_one_level_of_indentation_produces_no_erorrs_or_warnings() + { + $results = $this->runner->sniff('SwitchStatementInMethodWithOneIndentation.inc'); + $this->assertEquals(0, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + } + + /** @test */ + public function a_switch_in_a_method_with_two_levels_of_indentation_produces_an_error() + { + $results = $this->runner->sniff('SwitchStatementInMethodWithTwoIndentation.inc'); + $this->assertEquals(1, $results->getErrorCount()); + $this->assertEquals(0, $results->getWarningCount()); + $errorMessages = $results->getAllErrorMessages(); + $this->assertAllEqual('2 indenation levels found. Maximum of 1 indenation levels allowed.', $errorMessages); + } +} \ No newline at end of file