diff --git a/README.md b/README.md
index ab7610a..fb84e65 100644
--- a/README.md
+++ b/README.md
@@ -126,6 +126,71 @@ return $config;
//
```
+## PedroTroller/exceptions_punctuation
+
+Exception messages MUST ends by ".", "…", "?" or "!".
Risky: will change the exception message.
+
+### Configuration
+
+```php
+// .php_cs.dist
+setRules([
+ // ...
+ 'PedroTroller/exceptions_punctuation' => true,
+ // ...
+ ])
+ // ...
+ ->registerCustomFixers(new PedroTroller\CS\Fixer\Fixers())
+;
+
+return $config;
+```
+
+**OR** using my [rule list builder](doc/rule-set-factory.md).
+
+```php
+// .php_cs.dist
+setRules(PedroTroller\CS\Fixer\RuleSetFactory::create()
+ ->enable('PedroTroller/exceptions_punctuation')
+ ->getRules()
+ ])
+ // ...
+ ->registerCustomFixers(new PedroTroller\CS\Fixer\Fixers())
+;
+
+return $config;
+```
+
+### Fixes
+
+```diff
+--- Original // 80 chars
++++ New //
+@@ @@ //
+ class MyClass { //
+ public function fun1() //
+ { //
+- throw new \Exception('This is the message'); //
++ throw new \Exception('This is the message.'); //
+ } //
+ //
+ public function fun2($data) //
+ { //
+- throw new LogicException(sprintf('This is the %s', 'message')); //
++ throw new LogicException(sprintf('This is the %s.', 'message')); //
+ } //
+ } //
+ //
+```
+
## PedroTroller/forbidden_functions
Forbidden functions MUST BE commented
diff --git a/bin/doc.twig b/bin/doc.twig
index e16b5cc..9719f05 100644
--- a/bin/doc.twig
+++ b/bin/doc.twig
@@ -31,7 +31,7 @@ return $config;
## {{ fixer.name }}
-{{ fixer.doc.summary}}
+{{ fixer.doc.summary|raw }}
{% if fixer.deprecated %}
**DEPRECATED**
diff --git a/src/PedroTroller/CS/Fixer/CodingStyle/ExceptionsPunctuationFixer.php b/src/PedroTroller/CS/Fixer/CodingStyle/ExceptionsPunctuationFixer.php
new file mode 100644
index 0000000..5c04c0b
--- /dev/null
+++ b/src/PedroTroller/CS/Fixer/CodingStyle/ExceptionsPunctuationFixer.php
@@ -0,0 +1,172 @@
+isTokenKindFound(T_THROW);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isRisky()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSampleCode()
+ {
+ return <<<'PHP'
+
Risky: will change the exception message.';
+ }
+
+ protected function applyFix(SplFileInfo $file, Tokens $tokens)
+ {
+ $cases = $this->analyze($tokens)->findAllSequences([
+ [
+ [T_THROW],
+ [T_NEW],
+ [T_STRING],
+ '(',
+ [T_CONSTANT_ENCAPSED_STRING],
+ ',',
+ ],
+ [
+ [T_THROW],
+ [T_NEW],
+ [T_NS_SEPARATOR],
+ [T_STRING],
+ '(',
+ [T_CONSTANT_ENCAPSED_STRING],
+ ',',
+ ],
+ [
+ [T_THROW],
+ [T_NEW],
+ [T_STRING],
+ '(',
+ [T_STRING, 'sprintf'],
+ '(',
+ [T_CONSTANT_ENCAPSED_STRING],
+ ',',
+ ],
+ [
+ [T_THROW],
+ [T_NEW],
+ [T_NS_SEPARATOR],
+ [T_STRING],
+ '(',
+ [T_STRING, 'sprintf'],
+ '(',
+ [T_CONSTANT_ENCAPSED_STRING],
+ ',',
+ ],
+ [
+ [T_THROW],
+ [T_NEW],
+ [T_STRING],
+ '(',
+ [T_CONSTANT_ENCAPSED_STRING],
+ ')',
+ ],
+ [
+ [T_THROW],
+ [T_NEW],
+ [T_NS_SEPARATOR],
+ [T_STRING],
+ '(',
+ [T_CONSTANT_ENCAPSED_STRING],
+ ')',
+ ],
+ [
+ [T_THROW],
+ [T_NEW],
+ [T_STRING],
+ '(',
+ [T_STRING, 'sprintf'],
+ '(',
+ [T_CONSTANT_ENCAPSED_STRING],
+ ')',
+ ],
+ [
+ [T_THROW],
+ [T_NEW],
+ [T_NS_SEPARATOR],
+ [T_STRING],
+ '(',
+ [T_STRING, 'sprintf'],
+ '(',
+ [T_CONSTANT_ENCAPSED_STRING],
+ ')',
+ ],
+ ]);
+
+ foreach ($cases as $case) {
+ $keys = array_keys($case);
+ array_pop($keys);
+ array_pop($case);
+ $tokens[end($keys)] = $this->cleanupMessage(end($case));
+ }
+ }
+
+ /**
+ * @return Token
+ */
+ private function cleanupMessage(Token $token)
+ {
+ $content = $token->getContent();
+ $chars = str_split($content);
+ $quotes = array_shift($chars);
+ $quotes = array_pop($chars);
+ $ponctuation = end($chars);
+
+ switch ($ponctuation) {
+ case '.':
+ case '…':
+ case '?':
+ case '!':
+ return $token;
+ }
+
+ return new Token([T_CONSTANT_ENCAPSED_STRING, sprintf('%s%s.%s', $quotes, implode($chars), $quotes)]);
+ }
+}
diff --git a/src/PedroTroller/CS/Fixer/TokensAnalyzer.php b/src/PedroTroller/CS/Fixer/TokensAnalyzer.php
index f865cd3..59de011 100644
--- a/src/PedroTroller/CS/Fixer/TokensAnalyzer.php
+++ b/src/PedroTroller/CS/Fixer/TokensAnalyzer.php
@@ -406,4 +406,30 @@ public function getLineIndentation($index)
return end($parts);
}
+
+ /**
+ * @return array
+ */
+ public function findAllSequences(array $seqs)
+ {
+ $sequences = [];
+
+ foreach ($seqs as $seq) {
+ $index = 0;
+
+ do {
+ $extract = $this->tokens->findSequence($seq, (int) $index);
+
+ if (null !== $extract) {
+ $keys = array_keys($extract);
+ $index = end($keys) + 1;
+ $sequences[reset($keys)] = $extract;
+ }
+ } while (null !== $extract);
+ }
+
+ ksort($sequences);
+
+ return $sequences;
+ }
}
diff --git a/tests/UseCase/ExceptionsPunctuation.php b/tests/UseCase/ExceptionsPunctuation.php
new file mode 100644
index 0000000..b3dd708
--- /dev/null
+++ b/tests/UseCase/ExceptionsPunctuation.php
@@ -0,0 +1,97 @@
+