forked from phoedos/pmd
Merge branch 'master' into 7.0.x
This commit is contained in:
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
@ -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);
|
||||
}
|
||||
}
|
||||
|
272
pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java
Normal file
272
pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -154,7 +154,7 @@ public abstract class BaseLanguageModule implements Language {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LanguageModule:" + name + '(' + this.getClass().getSimpleName() + ')';
|
||||
return getTerseName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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
|
||||
|
@ -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
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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, ".");
|
||||
}
|
||||
}
|
@ -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(", "))
|
||||
);
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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
Reference in New Issue
Block a user