Merge branch 'master' into 7.0.x

This commit is contained in:
Clément Fournier
2022-03-03 19:55:10 +01:00
38 changed files with 2137 additions and 474 deletions

View File

@ -6539,6 +6539,15 @@
"code"
]
},
{
"login": "filiprafalowicz",
"name": "filiprafalowicz",
"avatar_url": "https://avatars.githubusercontent.com/u/24355557?v=4",
"profile": "https://github.com/filiprafalowicz",
"contributions": [
"code"
]
},
{
"login": "JerritEic",
"name": "JerritEic",

View File

@ -775,163 +775,164 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/ekkirala"><img src="https://avatars.githubusercontent.com/u/44954455?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ekkirala</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aekkirala" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/emersonmoura"><img src="https://avatars.githubusercontent.com/u/5419868?v=4?s=100" width="100px;" alt=""/><br /><sub><b>emersonmoura</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aemersonmoura" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://juejin.cn/user/1063982985642525"><img src="https://avatars.githubusercontent.com/u/24585054?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fairy</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aguxiaonian" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/filiprafalowicz"><img src="https://avatars.githubusercontent.com/u/24355557?v=4?s=100" width="100px;" alt=""/><br /><sub><b>filiprafalowicz</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=filiprafalowicz" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/foxmason"><img src="https://avatars.githubusercontent.com/u/33361071?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxmason</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Afoxmason" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/frankegabor"><img src="https://avatars.githubusercontent.com/u/13273444?v=4?s=100" width="100px;" alt=""/><br /><sub><b>frankegabor</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Afrankegabor" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/frankegabor"><img src="https://avatars.githubusercontent.com/u/13273444?v=4?s=100" width="100px;" alt=""/><br /><sub><b>frankegabor</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Afrankegabor" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/fanlw0816"><img src="https://avatars.githubusercontent.com/u/22781995?v=4?s=100" width="100px;" alt=""/><br /><sub><b>frankl</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Afanlw0816" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/freafrea"><img src="https://avatars.githubusercontent.com/u/39403091?v=4?s=100" width="100px;" alt=""/><br /><sub><b>freafrea</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Afreafrea" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/fsapatin"><img src="https://avatars.githubusercontent.com/u/10675254?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fsapatin</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Afsapatin" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/gracia19"><img src="https://avatars.githubusercontent.com/u/32557952?v=4?s=100" width="100px;" alt=""/><br /><sub><b>gracia19</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Agracia19" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/ief2009"><img src="https://avatars.githubusercontent.com/u/1955449?v=4?s=100" width="100px;" alt=""/><br /><sub><b>guo fei</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aief2009" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/gurmsc5"><img src="https://avatars.githubusercontent.com/u/26914263?v=4?s=100" width="100px;" alt=""/><br /><sub><b>gurmsc5</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Agurmsc5" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/gwilymatgearset"><img src="https://avatars.githubusercontent.com/u/43957113?v=4?s=100" width="100px;" alt=""/><br /><sub><b>gwilymatgearset</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=gwilymatgearset" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Agwilymatgearset" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/gwilymatgearset"><img src="https://avatars.githubusercontent.com/u/43957113?v=4?s=100" width="100px;" alt=""/><br /><sub><b>gwilymatgearset</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=gwilymatgearset" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Agwilymatgearset" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/haigsn"><img src="https://avatars.githubusercontent.com/u/52993319?v=4?s=100" width="100px;" alt=""/><br /><sub><b>haigsn</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ahaigsn" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/hemanshu070"><img src="https://avatars.githubusercontent.com/u/32012651?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hemanshu070</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ahemanshu070" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/henrik242"><img src="https://avatars.githubusercontent.com/u/129931?v=4?s=100" width="100px;" alt=""/><br /><sub><b>henrik242</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ahenrik242" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/hongpuwu"><img src="https://avatars.githubusercontent.com/u/19198552?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hongpuwu</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ahongpuwu" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/hvbtup"><img src="https://avatars.githubusercontent.com/u/7644776?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hvbtup</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=hvbtup" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Ahvbtup" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://www.igniti.de/"><img src="https://avatars.githubusercontent.com/u/7207145?v=4?s=100" width="100px;" alt=""/><br /><sub><b>igniti GmbH</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aigniti-gmbh" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/ilovezfs"><img src="https://avatars.githubusercontent.com/u/5268928?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ilovezfs</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ailovezfs" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/ilovezfs"><img src="https://avatars.githubusercontent.com/u/5268928?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ilovezfs</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ailovezfs" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/itaigilo"><img src="https://avatars.githubusercontent.com/u/13402361?v=4?s=100" width="100px;" alt=""/><br /><sub><b>itaigilo</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aitaigilo" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/jakivey32"><img src="https://avatars.githubusercontent.com/u/36869603?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jakivey32</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ajakivey32" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/jbennett2091"><img src="https://avatars.githubusercontent.com/u/16721671?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jbennett2091</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ajbennett2091" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/jcamerin"><img src="https://avatars.githubusercontent.com/u/7663252?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jcamerin</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ajcamerin" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/jkeener1"><img src="https://avatars.githubusercontent.com/u/11696155?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jkeener1</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ajkeener1" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/jmetertea"><img src="https://avatars.githubusercontent.com/u/33323555?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jmetertea</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ajmetertea" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/johnra2"><img src="https://avatars.githubusercontent.com/u/90150885?v=4?s=100" width="100px;" alt=""/><br /><sub><b>johnra2</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=johnra2" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/johnra2"><img src="https://avatars.githubusercontent.com/u/90150885?v=4?s=100" width="100px;" alt=""/><br /><sub><b>johnra2</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=johnra2" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/josemanuelrolon"><img src="https://avatars.githubusercontent.com/u/1685807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>josemanuelrolon</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=josemanuelrolon" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Ajosemanuelrolon" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/kabroxiko"><img src="https://avatars.githubusercontent.com/u/20568120?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kabroxiko</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=kabroxiko" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Akabroxiko" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/karwer"><img src="https://avatars.githubusercontent.com/u/862540?v=4?s=100" width="100px;" alt=""/><br /><sub><b>karwer</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Akarwer" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/kaulonline"><img src="https://avatars.githubusercontent.com/u/1171723?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kaulonline</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Akaulonline" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/kdaemonv"><img src="https://avatars.githubusercontent.com/u/5984651?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kdaemonv</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Akdaemonv" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/kenji21"><img src="https://avatars.githubusercontent.com/u/1105089?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kenji21</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=kenji21" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Akenji21" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/kfranic"><img src="https://avatars.githubusercontent.com/u/26544594?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kfranic</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Akfranic" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/kfranic"><img src="https://avatars.githubusercontent.com/u/26544594?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kfranic</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Akfranic" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/khalidkh"><img src="https://avatars.githubusercontent.com/u/6832066?v=4?s=100" width="100px;" alt=""/><br /><sub><b>khalidkh</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Akhalidkh" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/krzyk"><img src="https://avatars.githubusercontent.com/u/105730?v=4?s=100" width="100px;" alt=""/><br /><sub><b>krzyk</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Akrzyk" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/lasselindqvist"><img src="https://avatars.githubusercontent.com/u/13466645?v=4?s=100" width="100px;" alt=""/><br /><sub><b>lasselindqvist</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Alasselindqvist" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/lihuaib"><img src="https://avatars.githubusercontent.com/u/3365643?v=4?s=100" width="100px;" alt=""/><br /><sub><b>lihuaib</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Alihuaib" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/lonelyma1021"><img src="https://avatars.githubusercontent.com/u/22359014?v=4?s=100" width="100px;" alt=""/><br /><sub><b>lonelyma1021</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Alonelyma1021" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/lpeddy"><img src="https://avatars.githubusercontent.com/u/48803108?v=4?s=100" width="100px;" alt=""/><br /><sub><b>lpeddy</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Alpeddy" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://lujie.ac.cn/"><img src="https://avatars.githubusercontent.com/u/2918158?v=4?s=100" width="100px;" alt=""/><br /><sub><b>lujiefsi</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=lujiefsi" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="http://lujie.ac.cn/"><img src="https://avatars.githubusercontent.com/u/2918158?v=4?s=100" width="100px;" alt=""/><br /><sub><b>lujiefsi</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=lujiefsi" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/lyriccoder"><img src="https://avatars.githubusercontent.com/u/20803206?v=4?s=100" width="100px;" alt=""/><br /><sub><b>lyriccoder</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Alyriccoder" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/marcelmore"><img src="https://avatars.githubusercontent.com/u/2975481?v=4?s=100" width="100px;" alt=""/><br /><sub><b>marcelmore</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amarcelmore" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/matchboxy"><img src="https://avatars.githubusercontent.com/u/6457674?v=4?s=100" width="100px;" alt=""/><br /><sub><b>matchbox</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amatchboxy" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/matthiaskraaz"><img src="https://avatars.githubusercontent.com/u/5954500?v=4?s=100" width="100px;" alt=""/><br /><sub><b>matthiaskraaz</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amatthiaskraaz" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/mkeller-ergon"><img src="https://avatars.githubusercontent.com/u/23031669?v=4?s=100" width="100px;" alt=""/><br /><sub><b>meandonlyme</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amkeller-ergon" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/mikesive"><img src="https://avatars.githubusercontent.com/u/4043189?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mikesive</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amikesive" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/milossesic"><img src="https://avatars.githubusercontent.com/u/20756244?v=4?s=100" width="100px;" alt=""/><br /><sub><b>milossesic</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amilossesic" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/milossesic"><img src="https://avatars.githubusercontent.com/u/20756244?v=4?s=100" width="100px;" alt=""/><br /><sub><b>milossesic</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amilossesic" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/mriddell95"><img src="https://avatars.githubusercontent.com/u/25618660?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mriddell95</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amriddell95" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/mrlzh"><img src="https://avatars.githubusercontent.com/u/13222791?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mrlzh</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amrlzh" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/msloan"><img src="https://avatars.githubusercontent.com/u/1783723?v=4?s=100" width="100px;" alt=""/><br /><sub><b>msloan</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amsloan" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/mucharlaravalika"><img src="https://avatars.githubusercontent.com/u/32505587?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mucharlaravalika</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amucharlaravalika" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/mvenneman"><img src="https://avatars.githubusercontent.com/u/1266912?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mvenneman</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amvenneman" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/nareshl119"><img src="https://avatars.githubusercontent.com/u/39321364?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nareshl119</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Anareshl119" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/nicolas-harraudeau-sonarsource"><img src="https://avatars.githubusercontent.com/u/40498978?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nicolas-harraudeau-sonarsource</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Anicolas-harraudeau-sonarsource" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/nicolas-harraudeau-sonarsource"><img src="https://avatars.githubusercontent.com/u/40498978?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nicolas-harraudeau-sonarsource</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Anicolas-harraudeau-sonarsource" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/noerremark"><img src="https://avatars.githubusercontent.com/u/4252411?v=4?s=100" width="100px;" alt=""/><br /><sub><b>noerremark</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Anoerremark" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/novsirion"><img src="https://avatars.githubusercontent.com/u/7797113?v=4?s=100" width="100px;" alt=""/><br /><sub><b>novsirion</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Anovsirion" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/oggboy"><img src="https://avatars.githubusercontent.com/u/4798818?v=4?s=100" width="100px;" alt=""/><br /><sub><b>oggboy</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aoggboy" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://journal.lampetty.net/archive/category/in%20English"><img src="https://avatars.githubusercontent.com/u/78990?v=4?s=100" width="100px;" alt=""/><br /><sub><b>oinume</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aoinume" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/orimarko"><img src="https://avatars.githubusercontent.com/u/17137249?v=4?s=100" width="100px;" alt=""/><br /><sub><b>orimarko</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=orimarko" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Aorimarko" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/pagarwal-ignitetech"><img src="https://avatars.githubusercontent.com/u/30888430?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pallavi agarwal</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Apagarwal-ignitetech" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/parksungrin"><img src="https://avatars.githubusercontent.com/u/29750262?v=4?s=100" width="100px;" alt=""/><br /><sub><b>parksungrin</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aparksungrin" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/parksungrin"><img src="https://avatars.githubusercontent.com/u/29750262?v=4?s=100" width="100px;" alt=""/><br /><sub><b>parksungrin</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aparksungrin" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/patpatpat123"><img src="https://avatars.githubusercontent.com/u/43899031?v=4?s=100" width="100px;" alt=""/><br /><sub><b>patpatpat123</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Apatpatpat123" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/patriksevallius"><img src="https://avatars.githubusercontent.com/u/7291479?v=4?s=100" width="100px;" alt=""/><br /><sub><b>patriksevallius</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Apatriksevallius" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/pbrajesh1"><img src="https://avatars.githubusercontent.com/u/32388299?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pbrajesh1</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Apbrajesh1" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/phoenix384"><img src="https://avatars.githubusercontent.com/u/3883662?v=4?s=100" width="100px;" alt=""/><br /><sub><b>phoenix384</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aphoenix384" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/piotrszymanski-sc"><img src="https://avatars.githubusercontent.com/u/71124942?v=4?s=100" width="100px;" alt=""/><br /><sub><b>piotrszymanski-sc</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=piotrszymanski-sc" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/plan3d"><img src="https://avatars.githubusercontent.com/u/76825073?v=4?s=100" width="100px;" alt=""/><br /><sub><b>plan3d</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aplan3d" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/poojasix"><img src="https://avatars.githubusercontent.com/u/85337280?v=4?s=100" width="100px;" alt=""/><br /><sub><b>poojasix</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Apoojasix" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/poojasix"><img src="https://avatars.githubusercontent.com/u/85337280?v=4?s=100" width="100px;" alt=""/><br /><sub><b>poojasix</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Apoojasix" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/prabhushrikant"><img src="https://avatars.githubusercontent.com/u/6848200?v=4?s=100" width="100px;" alt=""/><br /><sub><b>prabhushrikant</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aprabhushrikant" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/pujitha8783"><img src="https://avatars.githubusercontent.com/u/20646357?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pujitha8783</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Apujitha8783" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/r-r-a-j"><img src="https://avatars.githubusercontent.com/u/33902071?v=4?s=100" width="100px;" alt=""/><br /><sub><b>r-r-a-j</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ar-r-a-j" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/raghujayjunk"><img src="https://avatars.githubusercontent.com/u/48074475?v=4?s=100" width="100px;" alt=""/><br /><sub><b>raghujayjunk</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Araghujayjunk" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/rajeshveera"><img src="https://avatars.githubusercontent.com/u/1306514?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rajeshveera</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Arajeshveera" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/rajeswarreddy88"><img src="https://avatars.githubusercontent.com/u/48543250?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rajeswarreddy88</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Arajeswarreddy88" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/recdevs"><img src="https://avatars.githubusercontent.com/u/63118273?v=4?s=100" width="100px;" alt=""/><br /><sub><b>recdevs</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Arecdevs" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/recdevs"><img src="https://avatars.githubusercontent.com/u/63118273?v=4?s=100" width="100px;" alt=""/><br /><sub><b>recdevs</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Arecdevs" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/reudismam"><img src="https://avatars.githubusercontent.com/u/1970407?v=4?s=100" width="100px;" alt=""/><br /><sub><b>reudismam</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=reudismam" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Areudismam" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/rijkt"><img src="https://avatars.githubusercontent.com/u/56129985?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rijkt</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Arijkt" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/rillig-tk"><img src="https://avatars.githubusercontent.com/u/46376960?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rillig-tk</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Arillig-tk" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/rmohan20"><img src="https://avatars.githubusercontent.com/u/58573547?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rmohan20</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=rmohan20" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Armohan20" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://rxmicro.io/"><img src="https://avatars.githubusercontent.com/u/54791695?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rxmicro</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Arxmicro" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/ryan-gustafson"><img src="https://avatars.githubusercontent.com/u/1227016?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ryan-gustafson</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=ryan-gustafson" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Aryan-gustafson" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/sabi0"><img src="https://avatars.githubusercontent.com/u/11509875?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sabi0</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asabi0" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/sabi0"><img src="https://avatars.githubusercontent.com/u/11509875?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sabi0</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asabi0" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/scais"><img src="https://avatars.githubusercontent.com/u/4539192?v=4?s=100" width="100px;" alt=""/><br /><sub><b>scais</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ascais" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/sebbASF"><img src="https://avatars.githubusercontent.com/u/16689231?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sebbASF</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3AsebbASF" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/sergeygorbaty"><img src="https://avatars.githubusercontent.com/u/14813710?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sergeygorbaty</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=sergeygorbaty" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/shilko2013"><img src="https://avatars.githubusercontent.com/u/33313482?v=4?s=100" width="100px;" alt=""/><br /><sub><b>shilko2013</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ashilko2013" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/simeonKondr"><img src="https://avatars.githubusercontent.com/u/42644177?v=4?s=100" width="100px;" alt=""/><br /><sub><b>simeonKondr</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3AsimeonKondr" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/snajberk"><img src="https://avatars.githubusercontent.com/u/3585281?v=4?s=100" width="100px;" alt=""/><br /><sub><b>snajberk</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asnajberk" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/sniperrifle2004"><img src="https://avatars.githubusercontent.com/u/18223222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sniperrifle2004</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asniperrifle2004" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/sniperrifle2004"><img src="https://avatars.githubusercontent.com/u/18223222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sniperrifle2004</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asniperrifle2004" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/snuyanzin"><img src="https://avatars.githubusercontent.com/u/403174?v=4?s=100" width="100px;" alt=""/><br /><sub><b>snuyanzin</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asnuyanzin" title="Bug reports">🐛</a> <a href="https://github.com/pmd/pmd/commits?author=snuyanzin" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/sratz"><img src="https://avatars.githubusercontent.com/u/14908423?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sratz</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asratz" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/stonio"><img src="https://avatars.githubusercontent.com/u/19952825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>stonio</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Astonio" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/sturton"><img src="https://avatars.githubusercontent.com/u/1734891?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sturton</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=sturton" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Asturton" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/sudharmohan"><img src="https://avatars.githubusercontent.com/u/16752281?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sudharmohan</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asudharmohan" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/suruchidawar"><img src="https://avatars.githubusercontent.com/u/30810931?v=4?s=100" width="100px;" alt=""/><br /><sub><b>suruchidawar</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asuruchidawar" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/svenfinitiv"><img src="https://avatars.githubusercontent.com/u/5653724?v=4?s=100" width="100px;" alt=""/><br /><sub><b>svenfinitiv</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asvenfinitiv" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/svenfinitiv"><img src="https://avatars.githubusercontent.com/u/5653724?v=4?s=100" width="100px;" alt=""/><br /><sub><b>svenfinitiv</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asvenfinitiv" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/tashiscool"><img src="https://avatars.githubusercontent.com/u/1057457?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tashiscool</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Atashiscool" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/test-git-hook"><img src="https://avatars.githubusercontent.com/u/49142715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>test-git-hook</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Atest-git-hook" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/testation21"><img src="https://avatars.githubusercontent.com/u/47239708?v=4?s=100" width="100px;" alt=""/><br /><sub><b>testation21</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=testation21" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Atestation21" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/thanosa"><img src="https://avatars.githubusercontent.com/u/24596498?v=4?s=100" width="100px;" alt=""/><br /><sub><b>thanosa</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Athanosa" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/tiandiyixian"><img src="https://avatars.githubusercontent.com/u/27055337?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tiandiyixian</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Atiandiyixian" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/tobwoerk"><img src="https://avatars.githubusercontent.com/u/11739442?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tobwoerk</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Atobwoerk" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/tprouvot"><img src="https://avatars.githubusercontent.com/u/35368290?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tprouvot</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Atprouvot" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/tprouvot"><img src="https://avatars.githubusercontent.com/u/35368290?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tprouvot</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Atprouvot" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/trentchilders"><img src="https://avatars.githubusercontent.com/u/6664350?v=4?s=100" width="100px;" alt=""/><br /><sub><b>trentchilders</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Atrentchilders" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/triandicAnt"><img src="https://avatars.githubusercontent.com/u/2345902?v=4?s=100" width="100px;" alt=""/><br /><sub><b>triandicAnt</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3AtriandicAnt" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/trishul14"><img src="https://avatars.githubusercontent.com/u/24551131?v=4?s=100" width="100px;" alt=""/><br /><sub><b>trishul14</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Atrishul14" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/xmtsui"><img src="https://avatars.githubusercontent.com/u/1542690?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tsui</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Axmtsui" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/winhkey"><img src="https://avatars.githubusercontent.com/u/4877808?v=4?s=100" width="100px;" alt=""/><br /><sub><b>winhkey</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Awinhkey" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/witherspore"><img src="https://avatars.githubusercontent.com/u/813263?v=4?s=100" width="100px;" alt=""/><br /><sub><b>witherspore</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Awitherspore" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/wjljack"><img src="https://avatars.githubusercontent.com/u/1182478?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wjljack</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Awjljack" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/wjljack"><img src="https://avatars.githubusercontent.com/u/1182478?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wjljack</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Awjljack" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/wuchiuwong"><img src="https://avatars.githubusercontent.com/u/15967553?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wuchiuwong</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Awuchiuwong" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/songxing10000"><img src="https://avatars.githubusercontent.com/u/10040131?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xingsong</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Asongxing10000" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/xioayuge"><img src="https://avatars.githubusercontent.com/u/45328272?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xioayuge</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Axioayuge" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/xnYi9wRezm"><img src="https://avatars.githubusercontent.com/u/61201892?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xnYi9wRezm</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=xnYi9wRezm" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3AxnYi9wRezm" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/xuanuy"><img src="https://avatars.githubusercontent.com/u/3894777?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xuanuy</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Axuanuy" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/xyf0921"><img src="https://avatars.githubusercontent.com/u/17350974?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xyf0921</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Axyf0921" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/yalechen-cyw3"><img src="https://avatars.githubusercontent.com/u/34886223?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yalechen-cyw3</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ayalechen-cyw3" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/yalechen-cyw3"><img src="https://avatars.githubusercontent.com/u/34886223?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yalechen-cyw3</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ayalechen-cyw3" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/yasuharu-sato"><img src="https://avatars.githubusercontent.com/u/45546628?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yasuharu-sato</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ayasuharu-sato" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/zenglian"><img src="https://avatars.githubusercontent.com/u/5268434?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zenglian</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Azenglian" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/zgrzyt93"><img src="https://avatars.githubusercontent.com/u/54275965?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zgrzyt93</b></sub></a><br /><a href="https://github.com/pmd/pmd/commits?author=zgrzyt93" title="Code">💻</a> <a href="https://github.com/pmd/pmd/issues?q=author%3Azgrzyt93" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/zhangxinngang"><img src="https://avatars.githubusercontent.com/u/6891146?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zh3ng</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Azhangxinngang" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/yuchen1013"><img src="https://avatars.githubusercontent.com/u/17316917?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zt_soft</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Ayuchen1013" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/ztt79"><img src="https://avatars.githubusercontent.com/u/48408552?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ztt79</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aztt79" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/zzzzfeng"><img src="https://avatars.githubusercontent.com/u/8851007?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zzzzfeng</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Azzzzfeng" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/zzzzfeng"><img src="https://avatars.githubusercontent.com/u/8851007?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zzzzfeng</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Azzzzfeng" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/magwas"><img src="https://avatars.githubusercontent.com/u/756838?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Árpád Magosányi</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Amagwas" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/clsaa"><img src="https://avatars.githubusercontent.com/u/32028545?v=4?s=100" width="100px;" alt=""/><br /><sub><b>任贵杰</b></sub></a><br /><a href="https://github.com/pmd/pmd/issues?q=author%3Aclsaa" title="Bug reports">🐛</a></td>
</tr>

View File

@ -70,7 +70,7 @@ public class PmdExample {
configuration.setReportFormat("xml");
configuration.setReportFile("/home/workspace/pmd-report.xml");
PMD.runPMD(configuration);
PMD.runPmd(configuration);
}
}
```
@ -80,7 +80,7 @@ public class PmdExample {
This gives you more control over which files are processed, but is also more complicated.
You can also provide your own custom renderers.
1. First we create a `PMDConfiguration`. This is currently the only way to specify a ruleset:
1. First we create a `PMDConfiguration` and configure it, first the rules:
```java
PMDConfiguration configuration = new PMDConfiguration();
@ -88,58 +88,67 @@ You can also provide your own custom renderers.
configuration.setRuleSets("rulesets/java/quickstart.xml");
```
2. In order to support type resolution, PMD needs to have access to the compiled classes and dependencies
as well. This is called "auxclasspath" and is also configured here.
2. Then we configure, which paths to analyze:
```java
configuration.setInputPaths("/home/workspace/src/main/java/code");
```
3. The we configure the default language version for Java. And in order to support type resolution,
PMD needs to have access to the compiled classes and dependencies as well. This is called
"auxclasspath" and is also configured here.
Note: you can specify multiple class paths separated by `:` on Unix-systems or `;` under Windows.
```java
configuration.prependClasspath("/home/workspace/target/classes:/home/.m2/repository/my/dependency.jar");
configuration.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
configuration.prependAuxClasspath("/home/workspace/target/classes:/home/.m2/repository/my/dependency.jar");
```
3. Then we need to load the rulesets. This is done by using the configuration, taking the minimum priority into
account:
4. Then we configure the reporting. Configuring the report file is optional. If not specified, the report
will be written to `stdout`.
```java
RuleSetLoader ruleSetLoader = RuleSetLoader.fromPmdConfig(configuration);
List<RuleSet> ruleSets = ruleSetLoader.loadFromResources(Arrays.asList(configuration.getRuleSets().split(",")));
configuration.setReportFormat("xml");
configuration.setReportFile("/home/workspace/pmd-report.xml");
```
4. PMD operates on a list of `DataSource`. You can assemble a own list of `FileDataSource`, e.g.
5. Now an optional step: If you want to use additional renderers as in the example, set them up before
calling PMD. You can use a built-in renderer, e.g. `XMLRenderer` or a custom renderer implementing
`Renderer`. Note, that you must manually initialize the renderer by setting a suitable `Writer`:
```java
List<DataSource> files = Arrays.asList(new FileDataSource(new File("/path/to/src/MyClass.java")));
```
5. For reporting, you can use `GlobalAnalysisListener`, which receives events like violations and errors.
Useful implementations are provided by `Renderer` instances. To use a renderer, eg the built-in `XMLRenderer`,
create it and configure it with a suitable `Writer`.
Writer rendererOutput = new StringWriter();
Renderer renderer = createRenderer(rendererOutput);
```java
StringWriter rendererOutput = new StringWriter();
Renderer xmlRenderer = new XMLRenderer("UTF-8");
xmlRenderer.setWriter(rendererOutput);
// The listener is created from the renderer in the next listing
// ...
private static Renderer createRenderer(Writer writer) {
XMLRenderer xml = new XMLRenderer("UTF-8");
xml.setWriter(writer);
return xml;
}
```
6. Now, all the preparations are done, and PMD can be executed. This is done by calling
`PMD.processFiles(...)`. This method call takes the configuration, the rulesets, the files
to process, and the list of renderers. Provide an empty list, if you don't want to use
any renderer. Note: The auxclasspath needs to be closed explicitly. Otherwise the class or jar files may
remain open and file resources are leaked.
6. Finally we can start the PMD analysis. There is the possibility to fine-tune the configuration
by adding additional files to analyze or adding additional rulesets or renderers:
```java
try (GlobalAnalysisListener listener = xmlRenderer.newListener()) {
PMD.processFiles(configuration, ruleSets, files, listener);
} finally {
ClassLoader auxiliaryClassLoader = configuration.getClassLoader();
if (auxiliaryClassLoader instanceof ClasspathClassLoader) {
((ClasspathClassLoader) auxiliaryClassLoader).close();
}
try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) {
// optional: add more rulesets
pmd.addRuleSet(RuleSetLoader.fromPmdConfig(configuration).loadFromResource("custom-ruleset.xml"));
// optional: add more files
pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
// optional: add more renderers
pmd.addRenderer(renderer);
// or just call PMD
pmd.performAnalysis();
}
```
7. After the call, the renderer will have been flushed by PMD (through its `GlobalAnalysisListener`).
Then you can check the rendered output.
The renderer will be automatically flushed and closed at the end of the analysis.
7. Then you can check the rendered output.
``` java
System.out.println("Rendered Report:");
@ -152,28 +161,15 @@ Here is a complete example:
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.nio.file.Paths;
import net.sourceforge.pmd.PMD;
import net.sourceforge.pmd.PMDConfiguration;
import net.sourceforge.pmd.PmdAnalysis;
import net.sourceforge.pmd.RulePriority;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.RuleSetLoader;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.renderers.XMLRenderer;
import net.sourceforge.pmd.util.ClasspathClassLoader;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.datasource.FileDataSource;
public class PmdExample2 {
@ -181,22 +177,28 @@ public class PmdExample2 {
PMDConfiguration configuration = new PMDConfiguration();
configuration.setMinimumPriority(RulePriority.MEDIUM);
configuration.setRuleSets("rulesets/java/quickstart.xml");
configuration.prependClasspath("/home/workspace/target/classes");
RuleSetLoader ruleSetLoader = RuleSetLoader.fromPmdConfig(configuration);
List<RuleSet> ruleSets = ruleSetLoader.loadFromResources(Arrays.asList(configuration.getRuleSets().split(",")));
List<DataSource> files = determineFiles("/home/workspace/src/main/java/code");
configuration.setInputPaths("/home/workspace/src/main/java/code");
configuration.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
configuration.prependAuxClasspath("/home/workspace/target/classes");
configuration.setReportFormat("xml");
configuration.setReportFile("/home/workspace/pmd-report.xml");
Writer rendererOutput = new StringWriter();
Renderer renderer = createRenderer(rendererOutput);
try (GlobalAnalysisListener listener = renderer.newListener()) {
PMD.processFiles(configuration, ruleSets, files, listener);
} finally {
ClassLoader auxiliaryClassLoader = configuration.getClassLoader();
if (auxiliaryClassLoader instanceof ClasspathClassLoader) {
((ClasspathClassLoader) auxiliaryClassLoader).close();
}
try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) {
// optional: add more rulesets
pmd.addRuleSet(RuleSetLoader.fromPmdConfig(configuration).loadFromResource("custom-ruleset.xml"));
// optional: add more files
pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
// optional: add more renderers
pmd.addRenderer(renderer);
// or just call PMD
pmd.performAnalysis();
}
System.out.println("Rendered Report:");
@ -208,28 +210,6 @@ public class PmdExample2 {
xml.setWriter(writer);
return xml;
}
private static List<DataSource> determineFiles(String basePath) throws IOException {
Path dirPath = FileSystems.getDefault().getPath(basePath);
final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
final List<DataSource> files = new ArrayList<>();
Files.walkFileTree(dirPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
if (matcher.matches(path.getFileName())) {
System.out.printf("Using %s%n", path);
files.add(new FileDataSource(path.toFile()));
} else {
System.out.printf("Ignoring %s%n", path);
}
return super.visitFile(path, attrs);
}
});
System.out.printf("Analyzing %d files in %s%n", files.size(), basePath);
return files;
}
}
```

View File

@ -19,11 +19,71 @@ This is a {{ site.pmd.release_type }} release.
### New and noteworthy
#### New programmatic API
This release introduces a new programmatic API to replace the inflexible {% jdoc core::PMD %} class.
Programmatic execution of PMD should now be done with a {% jdoc core::PMDConfiguration %}
and a {% jdoc core::PmdAnalysis %}, for instance:
```java
PMDConfiguration config = new PMDConfiguration();
config.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
config.setInputPaths("src/main/java");
config.prependAuxClasspath("target/classes");
config.setMinimumPriority(RulePriority.HIGH);
config.setRuleSets("rulesets/java/quickstart.xml");
config.setReportFormat("xml");
config.setReportFile("target/pmd-report.xml");
try (PmdAnalysis pmd = PmdAnalysis.create(config)) {
// optional: add more rulesets
pmd.addRuleSet(RuleSetLoader.fromPmdConfig(configuration).loadFromResource("custom-ruleset.xml"));
// optional: add more files
pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
// optional: add more renderers
pmd.addRenderer(renderer);
// or just call PMD
pmd.performAnalysis();
}
```
The `PMD` class still supports methods related to CLI execution: `runPmd` and `main`.
All other members are now deprecated for removal.
The CLI itself remains compatible, if you run PMD via command-line, no action is required on your part.
### Fixed Issues
* apex-performance
* [#3773](https://github.com/pmd/pmd/pull/3773): \[apex] EagerlyLoadedDescribeSObjectResult false positives with SObjectField.getDescribe()
### API Changes
#### Deprecated API
* Several members of {% jdoc core::PMD %} have been newly deprecated, including:
- `PMD#EOL`: use `System#lineSeparator()`
- `PMD#SUPPRESS_MARKER`: use {% jdoc core::PMDConfiguration#DEFAULT_SUPPRESS_MARKER %}
- `PMD#processFiles`: use the [new programmatic API](#new-programmatic-api)
- `PMD#getApplicableFiles`: is internal
* {% jdoc !!core::PMDConfiguration#prependClasspath(java.lang.String) %} is deprecated
in favour of {% jdoc core::PMDConfiguration#prependAuxClasspath(java.lang.String) %}.
#### Experimental APIs
* Together with the [new programmatic API](#new-programmatic-api) the interface
{% jdoc core::lang.document.TextFile %} has been added as *experimental*. It intends
to replace {% jdoc core::util.datasource.DataSource %} and {% jdoc core::cpd.SourceCode %} in the long term.
This interface will change in PMD 7 to support read/write operations
and other things. You don't need to use it in PMD 6, as {% jdoc core::lang.document.FileCollector %}
decouples you from this. A file collector is available through {% jdoc !!core::PmdAnalysis#files() %}.
### External Contributions
* [#3773](https://github.com/pmd/pmd/pull/3773): \[apex] EagerlyLoadedDescribeSObjectResult false positives with SObjectField.getDescribe() - [@filiprafalowicz](https://github.com/filiprafalowicz)
{% endtocmaker %}

View File

@ -56,4 +56,12 @@ public final class ASTReferenceExpression extends AbstractApexNode<ReferenceExpr
public boolean isSafeNav() {
return node.isSafeNav();
}
public boolean isSObjectType() {
List<Identifier> identifiers = node.getNames();
if (identifiers != null) {
return identifiers.stream().anyMatch(id -> "sobjecttype".equalsIgnoreCase(id.getValue()));
}
return false;
}
}

View File

@ -146,23 +146,26 @@ public class Something {
<description>
This rule finds `DescribeSObjectResult`s which could have been loaded eagerly via `SObjectType.getDescribe()`.
When using `SObjectType.getDescribe()` or `Schema.describeSObjects()` without supplying a `SObjectDescribeOptions`, implicitely it will be using `SObjectDescribeOptions.DEFAULT` then all
When using `SObjectType.getDescribe()` or `Schema.describeSObjects()` without supplying a `SObjectDescribeOptions`,
implicitly it will be using `SObjectDescribeOptions.DEFAULT` and then all
child relationships will be loaded eagerly regardless whether this information is needed or not.
This has a potential negative performance impact. Instead [`SObjectType.getDescribe(options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_Schema_SObjectType.htm#unique_346834793)
or [`Schema.describeSObjects(SObjectTypes, options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_describeSObjects) should be used and a `SObjectDescribeOptions` should be supplied. By using
or [`Schema.describeSObjects(SObjectTypes, options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_describeSObjects)
should be used and a `SObjectDescribeOptions` should be supplied. By using
`SObjectDescribeOptions.DEFERRED` the describe attributes will be lazily initialized at first use.
Lazy loading `DescribeSObjectResult` on picklist fields is not recommended. The lazy loaded
Lazy loading `DescribeSObjectResult` on picklist fields is not always recommended. The lazy loaded
describe objects might not be 100% accurate. It might be safer to explicitly use
`SObjectDescribeOptions.FULL` in such a case. The same applies when you need the same `DescribeSObjectResult` to be consistent
accross different contexts and API versions.
`SObjectDescribeOptions.FULL` in such a case. The same applies when you need the same `DescribeSObjectResult`
to be consistent across different contexts and API versions.
Properties:
* `noDefault`: The behavior of `SObjectDescribeOptions.DEFAULT` changes from API Version 43 to 44:
With API Version 43, the attributes are loaded eagerly. With API Version 44, they are loaded lazily.
Simply using `SObjectDescribeOptions.DEFAULT` doesn't automatically make use of lazy loading.
(unless "Use Improved Schema Caching" critical update is applied, `SObjectDescribeOptions.DEFAULT` do fallback to lazy loading)
(unless "Use Improved Schema Caching" critical update is applied, `SObjectDescribeOptions.DEFAULT` does fallback
to lazy loading)
With this property enabled, such usages are found.
You might ignore this, if you can make sure, that you don't run a mix of API Versions.
</description>
@ -173,8 +176,20 @@ Properties:
<property name="xpath">
<value>
<![CDATA[
//MethodCallExpression[(lower-case(@MethodName) = "getdescribe" or lower-case(@MethodName) = "describesobjects") and not(VariableExpression/ReferenceExpression[lower-case(@Image) = "sobjectdescribeoptions" ])] |
//ReferenceExpression[$noDefault = true() and lower-case(@Image) = "sobjectdescribeoptions" and parent::VariableExpression[lower-case(@Image) = "default"]]
//MethodCallExpression
[
lower-case(@MethodName) = "getdescribe" and ReferenceExpression[@SObjectType = true()]
or lower-case(@MethodName) = "describesobjects"
]
[not(VariableExpression/ReferenceExpression
[lower-case(@Image) = ("sobjectdescribeoptions", "fielddescribeoptions")]
)
]
|
//ReferenceExpression
[$noDefault = true()]
[lower-case(@Image) = "sobjectdescribeoptions"]
[parent::VariableExpression[lower-case(@Image) = "default"]]
]]>
</value>
</property>

View File

@ -7,6 +7,7 @@
<test-code>
<description>No describer options</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>3</expected-linenumbers>
<code><![CDATA[
public class Foo {
public void bar(List<Account> accounts) {
@ -21,6 +22,7 @@ public class Foo {
<test-code>
<description>No describer options using Schema class</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>3</expected-linenumbers>
<code><![CDATA[
public class Foo {
public void bar(List<Account> accounts) {
@ -89,4 +91,54 @@ public class Foo {
]]></code>
</test-code>
<test-code>
<description>False positive with no describer options on SObjectField</description>
<expected-problems>0</expected-problems>
<!-- note, this is not a violation. The default behaviour for SObjectField.getDescribe()
doesn't seem to be a performance problem. #3773 -->
<code><![CDATA[
public class Foo {
public void bar(Case case) {
String fieldName = Case.Subject.getDescribe().getName();
}
}
]]></code>
</test-code>
<test-code>
<description>False positive on SObjectField with FieldDescribeOptions.FULL_DESCRIBE</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public void bar(Case case) {
String fieldName = Case.Subject.getDescribe(FieldDescribeOptions.FULL_DESCRIBE).getName();
}
}
]]></code>
</test-code>
<test-code>
<description>False positive on SObjectField with FieldDescribeOptions.DEFAULT</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public void bar(Case case) {
String fieldName = Case.Subject.getDescribe(FieldDescribeOptions.DEFAULT).getName();
}
}
]]></code>
</test-code>
<test-code>
<description>False positive on SObjectField with FieldDescribeOptions.DEFAULT with noDefault=true</description>
<rule-property name="noDefault">true</rule-property>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public void bar(Case case) {
String fieldName = Case.Subject.getDescribe(FieldDescribeOptions.DEFAULT).getName();
}
}
]]></code>
</test-code>
</test-data>

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ import net.sourceforge.pmd.util.ClasspathClassLoader;
* {@link #getClassLoader()}</li>
* <li>A means to configure a ClassLoader using a prepended classpath String,
* instead of directly setting it programmatically.
* {@link #prependClasspath(String)}</li>
* {@link #prependAuxClasspath(String)}</li>
* <li>A LanguageVersionDiscoverer instance, which defaults to using the default
* LanguageVersion of each Language. Means are provided to change the
* LanguageVersion for each Language.
@ -88,8 +88,12 @@ import net.sourceforge.pmd.util.ClasspathClassLoader;
* </ul>
*/
public class PMDConfiguration extends AbstractConfiguration {
/** The default suppress marker string. */
public static final String DEFAULT_SUPPRESS_MARKER = "NOPMD";
// General behavior options
private String suppressMarker = PMD.SUPPRESS_MARKER;
private String suppressMarker = DEFAULT_SUPPRESS_MARKER;
private int threads = Runtime.getRuntime().availableProcessors();
private ClassLoader classLoader = getClass().getClassLoader();
private LanguageVersionDiscoverer languageVersionDiscoverer = new LanguageVersionDiscoverer();
@ -197,13 +201,46 @@ public class PMDConfiguration extends AbstractConfiguration {
* if the given classpath is invalid (e.g. does not exist)
* @see PMDConfiguration#setClassLoader(ClassLoader)
* @see ClasspathClassLoader
*
* @deprecated Use {@link #prependAuxClasspath(String)}, which doesn't
* throw a checked {@link IOException}
*/
@Deprecated
public void prependClasspath(String classpath) throws IOException {
if (classLoader == null) {
classLoader = PMDConfiguration.class.getClassLoader();
try {
prependAuxClasspath(classpath);
} catch (IllegalArgumentException e) {
throw new IOException(e);
}
if (classpath != null) {
classLoader = new ClasspathClassLoader(classpath, classLoader);
}
/**
* Prepend the specified classpath like string to the current ClassLoader of
* the configuration. If no ClassLoader is currently configured, the
* ClassLoader used to load the {@link PMDConfiguration} class will be used
* as the parent ClassLoader of the created ClassLoader.
*
* <p>If the classpath String looks like a URL to a file (i.e. starts with
* <code>file://</code>) the file will be read with each line representing
* an entry on the classpath.</p>
*
* @param classpath The prepended classpath.
*
* @throws IllegalArgumentException if the given classpath is invalid (e.g. does not exist)
* @see PMDConfiguration#setClassLoader(ClassLoader)
*/
public void prependAuxClasspath(String classpath) {
try {
if (classLoader == null) {
classLoader = PMDConfiguration.class.getClassLoader();
}
if (classpath != null) {
classLoader = new ClasspathClassLoader(classpath, classLoader);
}
} catch (IOException e) {
// Note: IOExceptions shouldn't appear anymore, they should already be converted
// to IllegalArgumentException in ClasspathClassLoader.
throw new IllegalArgumentException(e);
}
}
@ -244,6 +281,7 @@ public class PMDConfiguration extends AbstractConfiguration {
*/
public void setForceLanguageVersion(LanguageVersion forceLanguageVersion) {
this.forceLanguageVersion = forceLanguageVersion;
languageVersionDiscoverer.setForcedVersion(forceLanguageVersion);
}
/**
@ -253,6 +291,7 @@ public class PMDConfiguration extends AbstractConfiguration {
* the LanguageVersion
*/
public void setDefaultLanguageVersion(LanguageVersion languageVersion) {
Objects.requireNonNull(languageVersion);
setDefaultLanguageVersions(Arrays.asList(languageVersion));
}
@ -265,6 +304,7 @@ public class PMDConfiguration extends AbstractConfiguration {
*/
public void setDefaultLanguageVersions(List<LanguageVersion> languageVersions) {
for (LanguageVersion languageVersion : languageVersions) {
Objects.requireNonNull(languageVersion);
languageVersionDiscoverer.setDefaultLanguageVersion(languageVersion);
}
}

View File

@ -0,0 +1,272 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Logger;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.benchmark.TimeTracker;
import net.sourceforge.pmd.benchmark.TimedOperation;
import net.sourceforge.pmd.benchmark.TimedOperationCategory;
import net.sourceforge.pmd.internal.util.FileCollectionUtil;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.LanguageVersionDiscoverer;
import net.sourceforge.pmd.lang.document.FileCollector;
import net.sourceforge.pmd.processor.AbstractPMDProcessor;
import net.sourceforge.pmd.processor.MonoThreadProcessor;
import net.sourceforge.pmd.processor.MultiThreadProcessor;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.util.ClasspathClassLoader;
import net.sourceforge.pmd.util.IOUtil;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.log.PmdLogger;
import net.sourceforge.pmd.util.log.PmdLogger.Level;
import net.sourceforge.pmd.util.log.SimplePmdLogger;
/**
* Main programmatic API of PMD. Create and configure a {@link PMDConfiguration},
* then use {@link #create(PMDConfiguration)} to obtain an instance.
* You can perform additional configuration on the instance, eg adding
* files to process, or additional rulesets and renderers. Then, call
* {@link #performAnalysis()}. Example:
* <pre>{@code
* PMDConfiguration config = new PMDConfiguration();
* config.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
* config.setInputPaths("src/main/java");
* config.prependClasspath("target/classes");
* config.setMinimumPriority(RulePriority.HIGH);
* config.setRuleSets("rulesets/java/quickstart.xml");
* config.setReportFormat("xml");
* config.setReportFile("target/pmd-report.xml");
*
* try (PmdAnalysis pmd = PmdAnalysis.create(config)) {
* // optional: add more rulesets
* pmd.addRuleSet(RuleSetLoader.fromPmdConfig(configuration).loadFromResource("custom-ruleset.xml"));
* // optional: add more files
* pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
* // optional: add more renderers
* pmd.addRenderer(renderer);
*
* pmd.performAnalysis();
* }
* }</pre>
*
*/
public final class PmdAnalysis implements AutoCloseable {
private final FileCollector collector;
private final List<Renderer> renderers = new ArrayList<>();
private final List<RuleSet> ruleSets = new ArrayList<>();
private final PMDConfiguration configuration;
private final SimplePmdLogger logger = new SimplePmdLogger(Logger.getLogger("net.sourceforge.pmd"));
/**
* Constructs a new instance. The files paths (input files, filelist,
* exclude list, etc) given in the configuration are collected into
* the file collector ({@link #files()}), but more can be added
* programmatically using the file collector.
*/
private PmdAnalysis(PMDConfiguration config) {
this.configuration = config;
this.collector = FileCollector.newCollector(
config.getLanguageVersionDiscoverer(),
logger
);
final Level logLevel = configuration.isDebug() ? Level.TRACE : Level.INFO;
this.logger.setLevel(logLevel);
}
/**
* Constructs a new instance from a configuration.
*
* <ul>
* <li> The files paths (input files, filelist,
* exclude list, etc) are explored and the files to analyse are
* collected into the file collector ({@link #files()}).
* More can be added programmatically using the file collector.
* <li>The rulesets given in the configuration are loaded ({@link PMDConfiguration#getRuleSets()})
* <li>A renderer corresponding to the parameters of the configuration
* is created and added (but not started).
* </ul>
*/
public static PmdAnalysis create(PMDConfiguration config) {
PmdAnalysis builder = new PmdAnalysis(config);
// note: do not filter files by language
// they could be ignored later. The problem is if you call
// addRuleSet later, then you could be enabling new languages
// So the files should not be pruned in advance
FileCollectionUtil.collectFiles(config, builder.files());
Renderer renderer = config.createRenderer();
renderer.setReportFile(config.getReportFile());
builder.addRenderer(renderer);
final RuleSetLoader ruleSetLoader = RuleSetLoader.fromPmdConfig(config);
final RuleSets ruleSets = RulesetsFactoryUtils.getRuleSetsWithBenchmark(config.getRuleSets(), ruleSetLoader.toFactory());
if (ruleSets != null) {
for (RuleSet ruleSet : ruleSets.getAllRuleSets()) {
builder.addRuleSet(ruleSet);
}
}
return builder;
}
@InternalApi
static PmdAnalysis createWithoutCollectingFiles(PMDConfiguration config) {
return new PmdAnalysis(config);
}
/**
* Returns the file collector for the analysed sources.
*/
public FileCollector files() {
return collector; // todo user can close collector programmatically
}
/**
* Add a new renderer. The given renderer must not already be started,
* it will be started by {@link #performAnalysis()}.
*
* @throws NullPointerException If the parameter is null
*/
public void addRenderer(Renderer renderer) {
this.renderers.add(Objects.requireNonNull(renderer));
}
/**
* Add a new ruleset.
*
* @throws NullPointerException If the parameter is null
*/
public void addRuleSet(RuleSet ruleSet) {
this.ruleSets.add(Objects.requireNonNull(ruleSet));
}
public List<RuleSet> getRulesets() {
return Collections.unmodifiableList(ruleSets);
}
/**
* Run PMD with the current state of this instance. This will start
* and finish the registered renderers. All files collected in the
* {@linkplain #files() file collector} are processed. This does not
* return a report, for compatibility with PMD 7.
*/
public void performAnalysis() {
performAnalysisAndCollectReport();
}
/**
* Run PMD with the current state of this instance. This will start
* and finish the registered renderers. All files collected in the
* {@linkplain #files() file collector} are processed. Returns the
* output report.
*/
// TODO PMD 7 @DeprecatedUntil700
public Report performAnalysisAndCollectReport() {
try (FileCollector files = collector) {
files.filterLanguages(getApplicableLanguages());
List<DataSource> dataSources = FileCollectionUtil.collectorToDataSource(files);
startRenderers();
Report report = performAnalysisImpl(dataSources);
finishRenderers();
return report;
}
}
Report performAnalysisImpl(List<DataSource> sortedFiles) {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.FILE_PROCESSING)) {
PMD.encourageToUseIncrementalAnalysis(configuration);
Report report = new Report();
report.addListener(configuration.getAnalysisCache());
RuleContext ctx = new RuleContext();
ctx.setReport(report);
newFileProcessor(configuration).processFiles(new RuleSets(ruleSets), sortedFiles, ctx, renderers);
configuration.getAnalysisCache().persist();
return report;
}
}
private void startRenderers() {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) {
for (Renderer renderer : renderers) {
try {
renderer.start();
} catch (IOException e) {
logger.errorEx("Error while starting renderer " + renderer.getName(), e);
}
}
}
}
private void finishRenderers() {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) {
for (Renderer renderer : renderers) {
try {
renderer.end();
renderer.flush();
} catch (IOException e) {
logger.errorEx("Error while finishing renderer " + renderer.getName(), e);
}
}
}
}
private Set<Language> getApplicableLanguages() {
final Set<Language> languages = new HashSet<>();
final LanguageVersionDiscoverer discoverer = configuration.getLanguageVersionDiscoverer();
for (RuleSet ruleSet : ruleSets) {
for (final Rule rule : ruleSet.getRules()) {
final Language ruleLanguage = rule.getLanguage();
if (!languages.contains(ruleLanguage)) {
final LanguageVersion version = discoverer.getDefaultLanguageVersion(ruleLanguage);
if (RuleSet.applies(rule, version)) {
languages.add(ruleLanguage);
logger.trace("Using {0} version ''{1}''", version.getLanguage().getName(), version.getTerseName());
}
}
}
}
return languages;
}
private static AbstractPMDProcessor newFileProcessor(final PMDConfiguration configuration) {
return configuration.getThreads() > 0 ? new MultiThreadProcessor(configuration)
: new MonoThreadProcessor(configuration);
}
public PmdLogger getLog() {
return logger;
}
@Override
public void close() {
collector.close();
/*
* Make sure it's our own classloader before attempting to close it....
* Maven + Jacoco provide us with a cloaseable classloader that if closed
* will throw a ClassNotFoundException.
*/
if (configuration.getClassLoader() instanceof ClasspathClassLoader) {
IOUtil.tryCloseClassLoader(configuration.getClassLoader());
}
}
}

View File

@ -233,9 +233,9 @@ public class PMDTaskImpl {
try {
if (auxClasspath != null) {
project.log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE);
configuration.prependClasspath(auxClasspath.toString());
configuration.prependAuxClasspath(auxClasspath.toString());
}
} catch (IOException ioe) {
} catch (IllegalArgumentException ioe) {
throw new BuildException(ioe.getMessage(), ioe);
}
}

View File

@ -4,7 +4,6 @@
package net.sourceforge.pmd.cli;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -235,8 +234,8 @@ public class PMDParameters {
}
try {
configuration.prependClasspath(this.getAuxclasspath());
} catch (IOException e) {
configuration.prependAuxClasspath(this.getAuxclasspath());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid auxiliary classpath: " + e.getMessage(), e);
}
return configuration;

View File

@ -0,0 +1,174 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.internal.util;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import net.sourceforge.pmd.PMDConfiguration;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.document.FileCollector;
import net.sourceforge.pmd.lang.document.TextFile;
import net.sourceforge.pmd.util.FileUtil;
import net.sourceforge.pmd.util.database.DBMSMetadata;
import net.sourceforge.pmd.util.database.DBURI;
import net.sourceforge.pmd.util.database.SourceObject;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.log.PmdLogger;
import net.sourceforge.pmd.util.log.PmdLogger.Level;
import net.sourceforge.pmd.util.log.PmdLoggerScope;
/**
* @author Clément Fournier
*/
public final class FileCollectionUtil {
private FileCollectionUtil() {
}
public static List<DataSource> collectorToDataSource(FileCollector collector) {
List<DataSource> result = new ArrayList<>();
for (TextFile file : collector.getCollectedFiles()) {
result.add(file.toDataSourceCompat());
}
return result;
}
public static FileCollector collectFiles(PMDConfiguration configuration, Set<Language> languages, PmdLogger logger) {
FileCollector collector = collectFiles(configuration, logger);
collector.filterLanguages(languages);
return collector;
}
private static FileCollector collectFiles(PMDConfiguration configuration, PmdLogger logger) {
FileCollector collector = FileCollector.newCollector(
configuration.getLanguageVersionDiscoverer(),
logger
);
collectFiles(configuration, collector);
return collector;
}
public static void collectFiles(PMDConfiguration configuration, FileCollector collector) {
if (configuration.getSourceEncoding() != null) {
collector.setCharset(configuration.getSourceEncoding());
}
if (configuration.getInputPaths() != null) {
collectFiles(collector, configuration.getInputPaths());
}
if (configuration.getInputUri() != null) {
collectDB(collector, configuration.getInputUri());
}
if (configuration.getInputFilePath() != null) {
collectFileList(collector, configuration.getInputFilePath());
}
if (configuration.getIgnoreFilePath() != null) {
// disable trace logs for this secondary collector (would report 'adding xxx')
PmdLoggerScope mutedLog = new PmdLoggerScope("exclude list", collector.getLog());
mutedLog.setLevel(Level.ERROR);
try (FileCollector excludeCollector = FileCollector.newCollector(configuration.getLanguageVersionDiscoverer(), mutedLog)) {
collectFileList(excludeCollector, configuration.getIgnoreFilePath());
collector.exclude(excludeCollector);
}
}
}
public static void collectFiles(FileCollector collector, String fileLocations) {
for (String rootLocation : fileLocations.split(",")) {
try {
collector.relativizeWith(rootLocation);
addRoot(collector, rootLocation);
} catch (IOException e) {
collector.getLog().errorEx("Error collecting " + rootLocation, e);
}
}
}
public static void collectFileList(FileCollector collector, String fileListLocation) {
Path path = Paths.get(fileListLocation);
if (!Files.exists(path)) {
collector.getLog().error("No such file {0}", fileListLocation);
return;
}
String filePaths;
try {
filePaths = FileUtil.readFilelist(path.toFile());
} catch (IOException e) {
collector.getLog().errorEx("Error reading {0}", new Object[] { fileListLocation }, e);
return;
}
collectFiles(collector, filePaths);
}
private static void addRoot(FileCollector collector, String rootLocation) throws IOException {
Path path = Paths.get(rootLocation);
if (!Files.exists(path)) {
collector.getLog().error("No such file {0}", path);
return;
}
if (Files.isDirectory(path)) {
collector.addDirectory(path);
} else if (rootLocation.endsWith(".zip") || rootLocation.endsWith(".jar")) {
@SuppressWarnings("PMD.CloseResource")
FileSystem fs = collector.addZipFile(path);
if (fs == null) {
return;
}
for (Path zipRoot : fs.getRootDirectories()) {
collector.addFileOrDirectory(zipRoot);
}
} else if (Files.isRegularFile(path)) {
collector.addFile(path);
} else {
collector.getLog().trace("Ignoring {0}: not a regular file or directory", path);
}
}
public static void collectDB(FileCollector collector, String uriString) {
try {
collector.getLog().trace("Connecting to {0}", uriString);
DBURI dbUri = new DBURI(uriString);
DBMSMetadata dbmsMetadata = new DBMSMetadata(dbUri);
collector.getLog().trace("DBMSMetadata retrieved");
List<SourceObject> sourceObjectList = dbmsMetadata.getSourceObjectList();
collector.getLog().trace("Located {0} database source objects", sourceObjectList.size());
for (SourceObject sourceObject : sourceObjectList) {
String falseFilePath = sourceObject.getPseudoFileName();
collector.getLog().trace("Adding database source object {0}", falseFilePath);
try (Reader sourceCode = dbmsMetadata.getSourceCode(sourceObject)) {
String source = IOUtils.toString(sourceCode);
collector.addSourceFile(source, falseFilePath);
} catch (SQLException ex) {
collector.getLog().warningEx("Cannot get SourceCode for {0} - skipping ...",
new Object[] { falseFilePath},
ex);
}
}
} catch (ClassNotFoundException e) {
collector.getLog().errorEx("Cannot get files from DB - probably missing database JDBC driver", e);
} catch (Exception e) {
collector.getLog().errorEx("Cannot get files from DB - ''{0}''", new Object[] { uriString }, e);
}
}
}

View File

@ -154,7 +154,7 @@ public abstract class BaseLanguageModule implements Language {
@Override
public String toString() {
return "LanguageModule:" + name + '(' + this.getClass().getSimpleName() + ')';
return getTerseName();
}
@Override

View File

@ -35,7 +35,7 @@ public class LanguageVersion implements Comparable<LanguageVersion> {
private final Language language;
private final String version;
private final LanguageVersionHandler languageVersionHandler;
private final LanguageVersionHandler languageVersionHandler; // note: this is null if this is a cpd-only language...
/**
* @deprecated Use {@link Language#getVersion(String)}. This is only

View File

@ -8,6 +8,11 @@ import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.internal.util.AssertionUtil;
/**
* This class can discover the LanguageVersion of a source file. Further, every
@ -17,6 +22,23 @@ import java.util.Map;
public class LanguageVersionDiscoverer {
private Map<Language, LanguageVersion> languageToLanguageVersion = new HashMap<>();
private LanguageVersion forcedVersion;
public LanguageVersionDiscoverer() {
this(null);
}
/**
* Build a new instance.
*
* @param forcedVersion If non-null, all files should be assigned this version.
* The methods of this class still work as usual and do not
* care about the forced language version.
*/
public LanguageVersionDiscoverer(LanguageVersion forcedVersion) {
this.forcedVersion = forcedVersion;
}
/**
* Set the given LanguageVersion as the current default for it's Language.
*
@ -25,6 +47,7 @@ public class LanguageVersionDiscoverer {
* @return The previous default version for the language.
*/
public LanguageVersion setDefaultLanguageVersion(LanguageVersion languageVersion) {
AssertionUtil.requireParamNotNull("languageVersion", languageVersion);
LanguageVersion currentLanguageVersion = languageToLanguageVersion.put(languageVersion.getLanguage(),
languageVersion);
if (currentLanguageVersion == null) {
@ -41,6 +64,7 @@ public class LanguageVersionDiscoverer {
* @return The current default version for the language.
*/
public LanguageVersion getDefaultLanguageVersion(Language language) {
Objects.requireNonNull(language);
LanguageVersion languageVersion = languageToLanguageVersion.get(language);
if (languageVersion == null) {
languageVersion = language.getDefaultVersion();
@ -81,6 +105,14 @@ public class LanguageVersionDiscoverer {
return languageVersion;
}
public LanguageVersion getForcedVersion() {
return forcedVersion;
}
public void setForcedVersion(LanguageVersion forceLanguageVersion) {
this.forcedVersion = forceLanguageVersion;
}
/**
* Get the Languages of a given source file.
*
@ -106,11 +138,8 @@ public class LanguageVersionDiscoverer {
// Get the extensions from a file
private String getExtension(String fileName) {
String extension = null;
int extensionIndex = 1 + fileName.lastIndexOf('.');
if (extensionIndex > 0) {
extension = fileName.substring(extensionIndex);
}
return extension;
return StringUtils.substringAfterLast(fileName, ".");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import org.apache.commons.io.IOUtils;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.internal.util.AssertionUtil;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.datasource.FileDataSource;
/**
* A {@link TextFile} backed by a file in some {@link FileSystem}.
*/
@Experimental
class NioTextFile implements TextFile {
private final Path path;
private final Charset charset;
private final LanguageVersion languageVersion;
private final String displayName;
private final String pathId;
NioTextFile(Path path, Charset charset, LanguageVersion languageVersion, String displayName) {
AssertionUtil.requireParamNotNull("path", path);
AssertionUtil.requireParamNotNull("charset", charset);
AssertionUtil.requireParamNotNull("language version", languageVersion);
this.displayName = displayName;
this.path = path;
this.charset = charset;
this.languageVersion = languageVersion;
this.pathId = path.toAbsolutePath().toString();
}
@Override
public LanguageVersion getLanguageVersion() {
return languageVersion;
}
@Override
public String getDisplayName() {
return displayName;
}
@Override
public String getPathId() {
return pathId;
}
@Override
public String readContents() throws IOException {
if (!Files.isRegularFile(path)) {
throw new IOException("Not a regular file: " + path);
}
try (BufferedReader br = Files.newBufferedReader(path, charset)) {
return IOUtils.toString(br);
}
}
@Override
public DataSource toDataSourceCompat() {
return new FileDataSource(path.toFile());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NioTextFile that = (NioTextFile) o;
return Objects.equals(path, that.path);
}
@Override
public int hashCode() {
return Objects.hash(pathId);
}
@Override
public String toString() {
return getPathId();
}
}

View File

@ -0,0 +1,94 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document;
import java.io.StringReader;
import java.util.Objects;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.internal.util.AssertionUtil;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.datasource.ReaderDataSource;
/**
* Read-only view on a string.
*
* @author Clément Fournier
*/
@Experimental
class StringTextFile implements TextFile {
private final String content;
private final String pathId;
private final String displayName;
private final LanguageVersion languageVersion;
StringTextFile(String content,
String pathId,
String displayName,
LanguageVersion languageVersion) {
AssertionUtil.requireParamNotNull("source text", content);
AssertionUtil.requireParamNotNull("file name", displayName);
AssertionUtil.requireParamNotNull("file ID", pathId);
AssertionUtil.requireParamNotNull("language version", languageVersion);
this.languageVersion = languageVersion;
this.content = content;
this.pathId = pathId;
this.displayName = displayName;
}
@Override
public LanguageVersion getLanguageVersion() {
return languageVersion;
}
@Override
public String getDisplayName() {
return displayName;
}
@Override
public String getPathId() {
return pathId;
}
@Override
public String readContents() {
return content;
}
@Override
public DataSource toDataSourceCompat() {
return new ReaderDataSource(
new StringReader(content),
pathId
);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
StringTextFile that = (StringTextFile) o;
return Objects.equals(pathId, that.pathId);
}
@Override
public int hashCode() {
return Objects.hash(pathId);
}
@Override
public String toString() {
return getPathId();
}
}

View File

@ -0,0 +1,98 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document;
import java.io.File;
import java.io.IOException;
import net.sourceforge.pmd.PmdAnalysis;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.cpd.SourceCode;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.util.datasource.DataSource;
/**
* Represents some location containing character data. Despite the name,
* it's not necessarily backed by a file in the file-system: it may be
* eg an in-memory buffer, or a zip entry, ie it's an abstraction. Text
* files are the input which PMD and CPD process.
*
* <p>Text files must provide read access, and may provide write access.
* This interface only provides block IO operations, while {@link TextDocument} adds logic
* about incremental edition (eg replacing a single region of text).
*
* <p>This interface is meant to replace {@link DataSource} and {@link SourceCode.CodeLoader}.
* "DataSource" is not an appropriate name for a file which can be written
* to, also, the "data" it provides is text, not bytes.
*
* <h2>Experimental</h2>
* This interface will change in PMD 7 to support read/write operations
* and other things. You don't need to use it in PMD 6, as {@link FileCollector}
* decouples you from this. A file collector is available through {@link PmdAnalysis#files()}.
*/
@Experimental
public interface TextFile {
/**
* The name used for a file that has no name. This is mostly only
* relevant for unit tests.
*/
String UNKNOWN_FILENAME = "(unknown file)";
/**
* Returns the language version which should be used to process this
* file. This is a property of the file, which allows sources for
* several different language versions to be processed in the same
* PMD run. It also makes it so, that the file extension is not interpreted
* to find out the language version after the initial file collection
* phase.
*
* @return A language version
*/
LanguageVersion getLanguageVersion();
/**
* Returns an identifier for the path of this file. This should not
* be interpreted as a {@link File}, it may not be a file on this
* filesystem. The only requirement for this method, is that two
* distinct text files should have distinct path IDs, and that from
* one analysis to the next, the path ID of logically identical files
* be the same.
*
* <p>Basically this may be implemented as a URL, or a file path. It
* is used to index violation caches.
*/
String getPathId();
/**
* Returns a display name for the file. This name is used for
* reporting and should not be interpreted. It may be relative
* to a directory, may use platform-specific path separators,
* may not be normalized. Use {@link #getPathId()} when you
* want an identifier.
*/
String getDisplayName();
/**
* Reads the contents of the underlying character source.
*
* @return The most up-to-date content
*
* @throws IOException If this instance is closed
* @throws IOException If reading causes an IOException
*/
String readContents() throws IOException;
/**
* Compatibility with {@link DataSource} (pmd internals still use DataSource in PMD 6).
*/
@Deprecated
DataSource toDataSourceCompat();
}

View File

@ -0,0 +1,63 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.document.internal;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageRegistry;
/**
* Discovers the languages applicable to a file.
*/
public class LanguageDiscoverer {
private final Language forcedLanguage;
/**
* Build a new instance.
*
* @param forcedLanguage If non-null, all files will be assigned this language.
*/
public LanguageDiscoverer(Language forcedLanguage) {
this.forcedLanguage = forcedLanguage;
}
/**
* Get the Languages of a given source file.
*
* @param sourceFile The file.
*
* @return The Languages for the source file, may be empty.
*/
public List<Language> getLanguagesForFile(Path sourceFile) {
return getLanguagesForFile(sourceFile.getFileName().toString());
}
/**
* Get the Languages of a given source file.
*
* @param fileName The file name.
*
* @return The Languages for the source file, may be empty.
*/
public List<Language> getLanguagesForFile(String fileName) {
if (forcedLanguage != null) {
return Collections.singletonList(forcedLanguage);
}
String extension = getExtension(fileName);
return LanguageRegistry.findByExtension(extension);
}
// Get the extensions from a file
private String getExtension(String fileName) {
return StringUtils.substringAfterLast(fileName, ".");
}
}

View File

@ -75,7 +75,7 @@ public class RuleBuilder {
Language lang = LanguageRegistry.findLanguageByTerseName(languageName);
if (lang == null) {
throw new IllegalArgumentException(
"Unknown Language '" + languageName + "' for rule" + name + ", supported Languages are "
"Unknown Language '" + languageName + "' for rule " + name + ", supported Languages are "
+ LanguageRegistry.getLanguages().stream().map(Language::getTerseName).collect(Collectors.joining(", "))
);
}

View File

@ -20,6 +20,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.internal.util.AssertionUtil;
/**
* Create a ClassLoader which loads classes using a CLASSPATH like String. If
@ -57,17 +58,19 @@ public class ClasspathClassLoader extends URLClassLoader {
return urlList.toArray(new URL[0]);
}
private static URL[] initURLs(String classpath) throws IOException {
if (classpath == null) {
throw new IllegalArgumentException("classpath argument cannot be null");
}
private static URL[] initURLs(String classpath) {
AssertionUtil.requireParamNotNull("classpath", classpath);
final List<URL> urls = new ArrayList<>();
if (classpath.startsWith("file:")) {
// Treat as file URL
addFileURLs(urls, new URL(classpath));
} else {
// Treat as classpath
addClasspathURLs(urls, classpath);
try {
if (classpath.startsWith("file:")) {
// Treat as file URL
addFileURLs(urls, new URL(classpath));
} else {
// Treat as classpath
addClasspathURLs(urls, classpath);
}
} catch (IOException e) {
throw new IllegalArgumentException("Cannot prepend classpath " + classpath + "\n" + e.getMessage(), e);
}
return urls.toArray(new URL[0]);
}

View File

@ -99,7 +99,7 @@ public final class FileUtil {
}
private static List<DataSource> collect(List<DataSource> dataSources, String fileLocation,
FilenameFilter filenameFilter) {
FilenameFilter filenameFilter) {
File file = new File(fileLocation);
if (!file.exists()) {
throw new RuntimeException("File " + file.getName() + " doesn't exist");

View File

@ -0,0 +1,70 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.log;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.internal.util.AssertionUtil;
/**
* Logger façade. Can probably be converted to just SLF4J logger in PMD 7.
*
* @author Clément Fournier
*/
@InternalApi
public interface PmdLogger {
boolean isLoggable(Level level);
void log(Level level, String message, Object... formatArgs);
void logEx(Level level, String message, Object[] formatArgs, Throwable error);
void info(String message, Object... formatArgs);
void trace(String message, Object... formatArgs);
void debug(String message, Object... formatArgs);
void warning(String message, Object... formatArgs);
void warningEx(String message, Throwable error);
void warningEx(String message, Object[] formatArgs, Throwable error);
void error(String message, Object... formatArgs);
void errorEx(String message, Throwable error);
void errorEx(String message, Object[] formatArgs, Throwable error);
int numErrors();
// levels, in sync with SLF4J levels
enum Level {
TRACE,
DEBUG,
INFO,
WARN,
ERROR;
java.util.logging.Level toJutilLevel() {
switch (this) {
case DEBUG:
return java.util.logging.Level.FINE;
case ERROR:
return java.util.logging.Level.SEVERE;
case INFO:
return java.util.logging.Level.INFO;
case TRACE:
return java.util.logging.Level.FINER;
case WARN:
return java.util.logging.Level.WARNING;
default:
throw AssertionUtil.shouldNotReachHere("exhaustive");
}
}
}
}

View File

@ -0,0 +1,115 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.log;
import java.text.MessageFormat;
import java.util.logging.Logger;
import org.apache.commons.lang3.exception.ExceptionUtils;
/**
* A logger based on a {@link Logger}.
*
* @author Clément Fournier
*/
abstract class PmdLoggerBase implements PmdLogger {
private int numErrors;
private Level minLevel = Level.TRACE;
/**
* null level means off.
*/
public final void setLevel(Level minLevel) {
this.minLevel = minLevel;
}
@Override
public final boolean isLoggable(Level level) {
return minLevel != null
&& minLevel.compareTo(level) <= 0
&& isLoggableImpl(level);
}
protected boolean isLoggableImpl(Level level) {
return true;
}
@Override
public void logEx(Level level, String message, Object[] formatArgs, Throwable error) {
if (isLoggable(level)) {
message = MessageFormat.format(message, formatArgs);
log(level, message + ": " + error.getMessage());
if (isLoggable(Level.DEBUG)) {
log(Level.DEBUG, ExceptionUtils.getStackTrace(error));
}
}
}
@Override
public final void log(Level level, String message, Object... formatArgs) {
if (level == Level.ERROR) {
this.numErrors++;
}
if (isLoggable(level)) {
logImpl(level, message, formatArgs);
}
}
/**
* Perform logging assuming {@link #isLoggable(Level)} is true.
*/
protected abstract void logImpl(Level level, String message, Object[] formatArgs);
@Override
public void trace(String message, Object... formatArgs) {
log(Level.TRACE, message, formatArgs);
}
@Override
public void debug(String message, Object... formatArgs) {
log(Level.DEBUG, message, formatArgs);
}
@Override
public void info(String message, Object... formatArgs) {
log(Level.INFO, message, formatArgs);
}
@Override
public void warning(String message, Object... formatArgs) {
log(Level.WARN, message, formatArgs);
}
@Override
public final void warningEx(String message, Throwable error) {
warningEx(message, new Object[0], error);
}
@Override
public void warningEx(String message, Object[] formatArgs, Throwable error) {
logEx(Level.WARN, message, formatArgs, error);
}
@Override
public void error(String message, Object... formatArgs) {
log(Level.ERROR, message, formatArgs);
}
@Override
public final void errorEx(String message, Throwable error) {
errorEx(message, new Object[0], error);
}
@Override
public void errorEx(String message, Object[] formatArgs, Throwable error) {
logEx(Level.ERROR, message, formatArgs, error);
}
@Override
public int numErrors() {
return numErrors;
}
}

View File

@ -0,0 +1,30 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.log;
import net.sourceforge.pmd.annotation.InternalApi;
/**
* A logger that prefixes a scope name to log messages. Also keeps a
* separate error count.
*
* @author Clément Fournier
*/
@InternalApi
public final class PmdLoggerScope extends PmdLoggerBase {
private final PmdLogger backend;
private final String scopePrefix;
public PmdLoggerScope(String scopeName, PmdLogger backend) {
this.backend = backend;
this.scopePrefix = "[" + scopeName + "] ";
}
@Override
protected void logImpl(Level level, String message, Object[] formatArgs) {
backend.log(level, scopePrefix + message, formatArgs);
}
}

View File

@ -0,0 +1,35 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.log;
import java.text.MessageFormat;
import java.util.logging.Logger;
import net.sourceforge.pmd.annotation.InternalApi;
/**
* A {@link Logger} (java.util) based logger impl.
*
* @author Clément Fournier
*/
@InternalApi
public class SimplePmdLogger extends PmdLoggerBase implements PmdLogger {
private final Logger backend;
public SimplePmdLogger(Logger backend) {
this.backend = backend;
}
@Override
protected boolean isLoggableImpl(Level level) {
return backend.isLoggable(level.toJutilLevel());
}
@Override
protected void logImpl(Level level, String message, Object[] formatArgs) {
backend.log(level.toJutilLevel(), MessageFormat.format(message, formatArgs));
}
}

View File

@ -51,10 +51,10 @@ public class ConfigurationTest {
}
@Test
public void testClassLoader() throws IOException {
public void testClassLoader() {
PMDConfiguration configuration = new PMDConfiguration();
assertEquals("Default ClassLoader", PMDConfiguration.class.getClassLoader(), configuration.getClassLoader());
configuration.prependClasspath("some.jar");
configuration.prependAuxClasspath("some.jar");
assertEquals("Prepended ClassLoader class", ClasspathClassLoader.class,
configuration.getClassLoader().getClass());
URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs();
@ -68,31 +68,31 @@ public class ConfigurationTest {
}
@Test
public void auxClasspathWithRelativeFileEmpty() throws IOException {
public void auxClasspathWithRelativeFileEmpty() {
String relativeFilePath = "src/test/resources/net/sourceforge/pmd/cli/auxclasspath-empty.cp";
PMDConfiguration configuration = new PMDConfiguration();
configuration.prependClasspath("file:" + relativeFilePath);
configuration.prependAuxClasspath("file:" + relativeFilePath);
URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs();
Assert.assertEquals(0, urls.length);
}
@Test
public void auxClasspathWithRelativeFileEmpty2() throws IOException {
public void auxClasspathWithRelativeFileEmpty2() {
String relativeFilePath = "./src/test/resources/net/sourceforge/pmd/cli/auxclasspath-empty.cp";
PMDConfiguration configuration = new PMDConfiguration();
configuration.prependClasspath("file:" + relativeFilePath);
configuration.prependAuxClasspath("file:" + relativeFilePath);
URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs();
Assert.assertEquals(0, urls.length);
}
@Test
public void auxClasspathWithRelativeFile() throws IOException, URISyntaxException {
public void auxClasspathWithRelativeFile() throws URISyntaxException {
final String FILE_SCHEME = "file";
String currentWorkingDirectory = new File("").getAbsoluteFile().toURI().getPath();
String relativeFilePath = "src/test/resources/net/sourceforge/pmd/cli/auxclasspath.cp";
PMDConfiguration configuration = new PMDConfiguration();
configuration.prependClasspath("file:" + relativeFilePath);
configuration.prependAuxClasspath("file:" + relativeFilePath);
URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs();
URI[] uris = new URI[urls.length];
for (int i = 0; i < urls.length; i++) {
@ -232,7 +232,7 @@ public class ConfigurationTest {
}
@Test
public void testAnalysisCacheLocation() throws IOException {
public void testAnalysisCacheLocation() {
final PMDConfiguration configuration = new PMDConfiguration();
configuration.setAnalysisCacheLocation(null);

View File

@ -19,6 +19,7 @@ import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matcher;
import org.junit.AfterClass;
import org.junit.Before;
@ -124,6 +125,19 @@ public class CoreCliTest {
assertTrue("Report file should have been created", Files.exists(reportFile));
}
@Test
public void testFileCollectionWithUnknownFiles() throws IOException {
Path reportFile = tempRoot().resolve("out/reportFile.txt");
Files.createFile(srcDir.resolve("foo.not_analysable"));
assertFalse("Report file should not exist", Files.exists(reportFile));
runPmdSuccessfully("--no-cache", "--dir", srcDir, "--rulesets", DUMMY_RULESET, "--report-file", reportFile, "--debug");
assertTrue("Report file should have been created", Files.exists(reportFile));
String reportText = IOUtils.toString(Files.newBufferedReader(reportFile, StandardCharsets.UTF_8));
assertThat(reportText, not(containsStringIgnoringCase("error")));
}
@Test
public void testNonExistentReportFileDeprecatedOptions() {
Path reportFile = tempRoot().resolve("out/reportFile.txt");
@ -240,8 +254,8 @@ public class CoreCliTest {
}
private static void runPmd(int expectedExitCode, Object[] args) {
int actualExitCode = PMD.run(argsToString(args));
assertEquals("Exit code", expectedExitCode, actualExitCode);
StatusCode actualExitCode = PMD.runPmd(argsToString(args));
assertEquals("Exit code", expectedExitCode, actualExitCode.toInt());
}

Some files were not shown because too many files have changed in this diff Show More