Merge branch 'master' into pr-4100

This commit is contained in:
Andreas Dangel committed 2022-09-29 18:31:42 +02:00
commit a66cffa55d
33 files changed
+1468 -252

No files matched your search

+49 -2
View File
@@ -492,7 +492,8 @@
"profile": "https://github.com/pzygielo",
"contributions": [
"code",
"bug"
"bug",
"doc"
]
},
{
@@ -6798,7 +6799,8 @@
"contributions": [
"doc"
]
},{
},
{
"login": "pacvz",
"name": "pacvz",
"avatar_url": "https://avatars.githubusercontent.com/u/35453365?v=4",
@@ -6806,6 +6808,51 @@
"contributions": [
"code"
]
},
{
"login": "mohan-chinnappan-n",
"name": "mohan-chinnappan-n",
"avatar_url": "https://avatars.githubusercontent.com/u/5963194?v=4",
"profile": "https://mohan-chinnappan-n.github.io/about/cv.html",
"contributions": [
"code"
]
},
{
"login": "Suvashri",
"name": "Suvashri",
"avatar_url": "https://avatars.githubusercontent.com/u/112872981?v=4",
"profile": "https://github.com/Suvashri",
"contributions": [
"doc"
]
},
{
"login": "osiegmar",
"name": "Oliver Siegmar",
"avatar_url": "https://avatars.githubusercontent.com/u/1918869?v=4",
"profile": "https://github.com/osiegmar",
"contributions": [
"financial"
]
},
{
"login": "OlegAndreych",
"name": "Oleg Andreych",
"avatar_url": "https://avatars.githubusercontent.com/u/2041351?v=4",
"profile": "https://github.com/OlegAndreych",
"contributions": [
"code"
]
},
{
"login": "lfalcantar",
"name": "Luis Alcantar",
"avatar_url": "https://avatars.githubusercontent.com/u/13026131?v=4",
"profile": "https://github.com/lfalcantar",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
+15 -14
View File
@@ -1,8 +1,8 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
claide (1.1.0)
claide-plugins (0.9.2)
cork
@@ -12,7 +12,7 @@ GEM
concurrent-ruby (1.1.10)
cork (0.3.0)
colored2 (~> 3.1)
danger (8.6.1)
danger (9.0.0)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
colored2 (~> 3.1)
@@ -23,12 +23,12 @@ GEM
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.0)
no_proxy_fix
octokit (~> 4.7)
octokit (~> 5.0)
terminal-table (>= 1, < 4)
differ (0.1.2)
et-orbi (1.2.7)
tzinfo
faraday (1.10.0)
faraday (1.10.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -43,7 +43,7 @@ GEM
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-http-cache (2.4.0)
faraday-http-cache (2.4.1)
faraday (>= 0.8)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
@@ -53,25 +53,26 @@ GEM
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
fugit (1.5.3)
fugit (1.7.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
git (1.11.0)
git (1.12.0)
addressable (~> 2.8)
rchardet (~> 1.8)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (5.3.0)
liquid (5.4.0)
logger-colors (1.0.0)
mini_portile2 (2.8.0)
multipart-post (2.2.3)
nap (1.1.0)
no_proxy_fix (0.1.2)
nokogiri (1.13.7)
nokogiri (1.13.8)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
octokit (4.25.1)
octokit (5.6.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
open4 (1.3.4)
@@ -82,12 +83,12 @@ GEM
nokogiri (~> 1.13)
rufus-scheduler (~> 3.8)
slop (~> 4.6)
public_suffix (4.0.7)
public_suffix (5.0.0)
raabro (1.4.0)
racc (1.6.0)
rchardet (1.8.0)
rexml (3.2.5)
rouge (3.29.0)
rouge (4.0.0)
ruby2_keywords (0.0.5)
rufus-scheduler (3.8.2)
fugit (~> 1.1, >= 1.1.6)
@@ -100,7 +101,7 @@ GEM
unicode-display_width (>= 1.1.1, < 3)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unicode-display_width (2.2.0)
unicode-display_width (2.3.0)
PLATFORMS
ruby
+10 -10
View File
@@ -1,20 +1,20 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (6.0.5.1)
activesupport (6.0.6)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.11.1)
colorator (1.1.0)
commonmarker (0.23.5)
commonmarker (0.23.6)
concurrent-ruby (1.1.10)
dnsruby (1.61.9)
simpleidn (~> 0.1)
@@ -25,10 +25,10 @@ GEM
ffi (>= 1.15.0)
eventmachine (1.2.7)
execjs (2.8.1)
faraday (2.3.0)
faraday-net_http (~> 2.0)
faraday (2.5.2)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (2.0.3)
faraday-net_http (3.0.0)
ffi (1.15.5)
forwardable-extended (2.6.0)
gemoji (3.0.1)
@@ -211,8 +211,8 @@ GEM
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.16.2)
nokogiri (1.13.7)
minitest (5.16.3)
nokogiri (1.13.8)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
octokit (4.25.1)
@@ -222,7 +222,7 @@ GEM
forwardable-extended (~> 2.6)
public_suffix (4.0.7)
racc (1.6.0)
rb-fsevent (0.11.1)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.5)
+3
View File
@@ -58,6 +58,9 @@ entries:
- title: PMD Report formats
url: /pmd_userdocs_report_formats.html
output: web, pdf
- title: 3rd party rulesets
output: web, pdf
url: /pmd_userdocs_3rdpartyrulesets.html
- title: null
output: web, pdf
subfolders:
File diff suppressed because it is too large. Load diff
+24 -7
View File
@@ -9,26 +9,43 @@ author: Tom Copeland <tom@infoether.org>
### Salesforce / Apex Language Module
* October 2020 - [Salesforce CLI Scanner Custom XPath Rules - Part 1](https://bobbuzzard.blogspot.com/2020/10/salesforce-cli-scanner-custom-xpath.html),
[Salesforce CLI Scanner Custom XPath Rules - Part 2](http://bobbuzzard.blogspot.com/2020/10/salesforce-cli-scanner-custom-xpath_11.html)
by [Keir Bowden](https://twitter.com/bob_buzzard)
* March 2020 - [Helping Salesforce developers create readable and maintainable Apex code](https://gearset.com/blog/helping-sf-developers-create-readable-and-maintainable-apex-code)
* July 2019 - [Apex PMD \| Static code analysis - Apex Hours](https://youtu.be/34PxAHtAavU)
* June 2019 - [Pluralsight](https://www.pluralsight.com/authors/don-robins) Course about leveraging PMD usage for Salesforce by [Robert Sösemann](https://github.com/rsoesemann) (Apex Language Module Contributor) [Play by Play: Automated Code Analysis in Salesforce - a Tools Deep-Dive](https://www.pluralsight.com/courses/play-by-play-automated-code-analysis-in-salesforce)
* June 2019 - [Pluralsight](https://www.pluralsight.com/authors/don-robins) Course about leveraging PMD usage for
Salesforce by [Robert Sösemann](https://github.com/rsoesemann) (Apex Language Module Contributor)
[Play by Play: Automated Code Analysis in Salesforce - a Tools Deep-Dive](https://www.pluralsight.com/courses/play-by-play-automated-code-analysis-in-salesforce)
* June 2018 - [Salesforce Way Podcast](https://salesforceway.com/podcast/podcast/) with [Robert Sösemann](https://github.com/rsoesemann) [Static Code Analysis with PMD for Apex](https://salesforceway.com/podcast/podcast/static-code-analysis-with-pmd-for-apex/)
* June 2018 - [Salesforce Way Podcast](https://salesforceway.com/podcast/podcast/) with
[Robert Sösemann](https://github.com/rsoesemann) [Static Code Analysis with PMD for Apex](https://salesforceway.com/podcast/podcast/static-code-analysis-with-pmd-for-apex/)
* January 2018 - [Webinar: How to contribute Apex rules to PMD with Robert Sösemann](https://www.youtube.com/watch?v=7_Ex9WWS_3Q)
* January 2018 - [Webinar: How to contribute Apex rules to PMD with Robert Sösemann](https://www.youtube.com/watch?v=7_Ex9WWS_3Q)
* August 2017 - Webinar about how to use PMD with The Welkin Suite Salesforce IDE - Author [Robert Sösemann](https://github.com/rsoesemann) - [Improving your Apex Code Quality with PMD in The Welkin Suite](https://www.youtube.com/watch?v=Ypyiy5b6huc)
* August 2017 - Webinar about how to use PMD with The Welkin Suite Salesforce IDE - Author
[Robert Sösemann](https://github.com/rsoesemann) - [Improving your Apex Code Quality with PMD in The Welkin Suite](https://www.youtube.com/watch?v=Ypyiy5b6huc)
* November 2016 - Recording of [Robert Sösemann](https://github.com/rsoesemann)'s Session at Salesforce Dreamforce Conference about enforcing Clean Code in the Salesforce world using PMD and other tools [Clean Apex Code with Automatic Code Metrics](https://www.youtube.com/watch?v=bW7m6y6bEug)
* November 2016 - Recording of [Robert Sösemann](https://github.com/rsoesemann)'s Session at Salesforce Dreamforce
Conference about enforcing Clean Code in the Salesforce world using PMD and other tools
[Clean Apex Code with Automatic Code Metrics](https://www.youtube.com/watch?v=bW7m6y6bEug)
### PMD in general and other Language Modules
* February 2021 - Artem Krosheninnikov's talk about Quality Assurance Automation: [Artem Krosheninnikov, Wrike - How static analysis can help in QAA processes](https://www.youtube.com/watch?v=L42zH5ne074)
* February 2021 - Artem Krosheninnikov's talk about Quality Assurance Automation:
[Artem Krosheninnikov, Wrike - How static analysis can help in QAA processes](
https://www.youtube.com/watch?v=L42zH5ne074)
* May 2019 - [Code quality assurance with PMD An extensible static code analyser for Java and other languages](https://www.datarespons.com/code-quality-assurance-with-pmd/)
* December 2020 - Jeroen Borgers' talk about finding performance bugs with PMD:
[J-Fall Virtual 2020: Jeroen Borgers - Fixing your performance and concurrency bugs before they bite you](
https://www.youtube.com/watch?v=Z_sT38KTRNk)
* May 2019 - [Code quality assurance with PMD An extensible static code analyser for Java and other languages](
https://www.datarespons.com/code-quality-assurance-with-pmd/)
* February 2012 - Romain Pelisse's lightning talk at FOSDEM 2012 about "PMD5: What can it do for you?".
[Video recording is available](http://video.fosdem.org/2012/lightningtalks/PMD5.webm).
@@ -0,0 +1,27 @@
---
title: 3rd party rulesets
tags: [rule_references, userdocs]
summary: Lists rulesets and rules from the community
permalink: pmd_userdocs_3rdpartyrulesets.html
last_updated: September 2022
---
## For Java
* **jPinpoint rules:** PMD rule set for performance aware Java and Kotlin coding.
* <https://github.com/jborgers/PMD-jPinpoint-rules>
* **arch4u-pmd** is a library with pmd rules that bring new regulations related to known problems in REST API, logging,
monitoring, etc., including reconfigured default pmd rules to decrease false-positive violations during usage of
well-known frameworks like Spring, Quarkus, etc.
* <https://github.com/dgroup/arch4u-pmd>
* Sample ruleset from **maxdocs**, a multi markup wiki engine.
* <https://github.com/bohni/maxdocs/blob/master/src/main/config/pmd/pmd-ruleset.xml>
* Sample ruleset from **geotools**, an open source Java library that provides tools for geospatial data.
* <https://github.com/geotools/geotools/blob/main/build/qa/pmd-ruleset.xml>
* <https://github.com/geotools/geotools/blob/main/build/qa/pmd-junit-ruleset.xml>
## For Apex
* **unhappy-soup**, a repository with problematic Salesforce code to showcase PMD, the SFDX Scanner CLI
* <https://github.com/rsoesemann/unhappy-soup/blob/master/ruleset.xml>
+1 -1
View File
@@ -122,7 +122,7 @@ Novice as much as advanced readers may want to [read on on Refactoring Guru](htt
{% include custom/cli_option_row.html options="--ignore-literal-sequences"
description="Ignore sequences of literals (common e.g. in list initializers)"
default="false"
languages="C#, C++"
languages="C#, C++, Lua"
%}
{% include custom/cli_option_row.html options="--ignore-usings"
description="Ignore `using` directives in C# when comparing text"
+38
View File
@@ -14,11 +14,49 @@ This is a {{ site.pmd.release_type }} release.
### New and noteworthy
#### Lua now supports additionally Luau
This release of PMD adds support for [Luau](https://github.com/Roblox/luau), a gradually typed language derived
from Lua. This means, that the Lua language in PMD can now parse both Lua and Luau.
#### Modified rules
* The Java rule {% rule java/codestyle/CommentDefaultAccessModifier %} now by default ignores JUnit5 annotated
methods. This behavior can be customized using the property `ignoredAnnotations`.
### Fixed Issues
* cli
* [#4118](https://github.com/pmd/pmd/issues/4118): \[cli] run.sh designer reports "integer expression expected"
* core
* [#4116](https://github.com/pmd/pmd/pull/4116): \[core] Missing --file arg in TreeExport CLI example
* doc
* [#4109](https://github.com/pmd/pmd/pull/4109): \[doc] Add page for 3rd party rulesets
* [#4124](https://github.com/pmd/pmd/pull/4124): \[doc] Fix typos in Java rule docs
* java-codestyle
* [#3859](https://github.com/pmd/pmd/issues/3859): \[java] CommentDefaultAccessModifier is triggered in JUnit5 test class
* [#4085](https://github.com/pmd/pmd/issues/4085): \[java] UnnecessaryFullyQualifiedName false positive when nested and non-nested classes with the same name and in the same package are used together
* java-design
* [#4090](https://github.com/pmd/pmd/issues/4090): \[java] FinalFieldCouldBeStatic false positive with non-static synchronized block (regression in 6.48, worked with 6.47)
### API Changes
#### CPD CLI
* CPD now supports the `--ignore-literal-sequences` argument when analyzing Lua code.
### Financial Contributions
Many thanks to our sponsors:
* [Oliver Siegmar](https://github.com/osiegmar) (@osiegmar)
### External Contributions
* [#4066](https://github.com/pmd/pmd/pull/4066): \[lua] Add support for Luau syntax and skipping literal sequences in CPD - [Matt Hargett](https://github.com/matthargett) (@matthargett)
* [#4116](https://github.com/pmd/pmd/pull/4116): \[core] Fix missing --file arg in TreeExport CLI example - [mohan-chinnappan-n](https://github.com/mohan-chinnappan-n) (@mohan-chinnappan-n)
* [#4124](https://github.com/pmd/pmd/pull/4124): \[doc] Fix typos in Java rule docs - [Piotrek Żygieło](https://github.com/pzygielo) (@pzygielo)
* [#4128](https://github.com/pmd/pmd/pull/4128): \[java] Fix False-positive UnnecessaryFullyQualifiedName when nested and non-nest… #4103 - [Oleg Andreych](https://github.com/OlegAndreych) (@OlegAndreych)
* [#4131](https://github.com/pmd/pmd/pull/4131): \[doc] TooFewBranchesForASwitchStatement - Use "if-else" instead of "if-then" - [Suvashri](https://github.com/Suvashri) (@Suvashri)
* [#4137](https://github.com/pmd/pmd/pull/4137): \[java] Fixes 3859: Exclude junit5 test methods from the commentDefaultAccessModifierRule - [Luis Alcantar](https://github.com/lfalcantar) (@lfalcantar)
{% endtocmaker %}
@@ -139,7 +139,7 @@ public class TreeExportCli {
sb.append(System.lineSeparator())
.append(System.lineSeparator());
sb.append("Example: ast-dump --format xml --language java MyFile.java")
sb.append("Example: ast-dump --format xml --language java --file MyFile.java")
.append(System.lineSeparator());
System.err.print(sb);
@@ -4,35 +4,41 @@ set OPTS=
set MAIN_CLASS=net.sourceforge.pmd.util.fxdesigner.DesignerStarter
:: sets the jver variable to the java version, eg 901 for 9.0.1+x or 180 for 1.8.0_171-b11
:: sets the jver variable to the java version, eg 90 for 9.0.1+x or 80 for 1.8.0_171-b11 or 110 for 11.0.6.1
:: sets the jvendor variable to either java (oracle) or openjdk
for /f tokens^=1^,3^,4^,5^ delims^=.-_+^"^ %%j in ('java -version 2^>^&1 ^| find "version"') do (
set jvendor=%%j
if %%l EQU ea (
set /A "jver=%%k00"
set /A "jver=%%k0"
) else (
set /A jver=%%k%%l%%m
if %%k EQU 1 (
:: for java version 1.7.x, 1.8.x, ignore the first 1.
set /A "jver=%%l%%m"
) else (
set /A "jver=%%k%%l"
)
)
)
Set "jreopts="
:: oracle java 9 and 10 has javafx included as a module
if /I "%jvendor%" EQU "java" (
if %jver% GEQ 900 (
if %jver% LSS 1100 (
if /I %jvendor% == java (
if %jver% GEQ 90 (
if %jver% LSS 110 (
:: enable reflection
Set jreopts=--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED
set jreopts=--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED
)
)
)
set "_needjfxlib=0"
if /I "%jvendor%" EQU "openjdk" set _needjfxlib=1
if /I "%jvendor%" EQU "java" (
if %jver% GEQ 1100 set _needjfxlib=1
if /I %jvendor% == openjdk set _needjfxlib=1
if /I %jvendor% == java (
if %jver% GEQ 110 set _needjfxlib=1
)
if %_needjfxlib% EQU 1 (
if %jver% LSS 1000 (
if %jver% LSS 100 (
echo For openjfx at least java 10 is required.
pause
exit
+11 -9
View File
@@ -2,7 +2,7 @@
usage() {
echo "Usage:"
echo " $(basename $0) <application-name> [-h|-v] ..."
echo " $(basename "$0") <application-name> [-h|-v] ..."
echo ""
echo "application-name: valid options are: $(valid_app_options)"
echo "-h print this help"
@@ -60,9 +60,9 @@ java_heapsize_settings() {
set_lib_dir() {
if [ -z ${LIB_DIR} ]; then
if [ -z "${LIB_DIR}" ]; then
# Allow for symlinks to this script
if [ -L $0 ]; then
if [ -L "$0" ]; then
local script_real_loc=$(readlink "$0")
else
local script_real_loc=$0
@@ -83,23 +83,25 @@ check_lib_dir() {
}
function script_exit() {
echo $1 >&2
echo "$1" >&2
exit 1
}
determine_java_version() {
local full_ver=$(java -version 2>&1)
# java_ver is eg "18" for java 1.8, "90" for java 9.0, "100" for java 10.0.x
readonly java_ver=$(echo $full_ver | sed -n '{
# java_ver is eg "80" for java 1.8, "90" for java 9.0, "100" for java 10.0.x
readonly java_ver=$(echo "$full_ver" | sed -n '{
# replace early access versions, e.g. 11-ea with 11.0.0
s/-ea/.0.0/
# replace versions such as 10 with 10.0.0
s/version "\([0-9]\{1,\}\)"/version "\1.0.0"/
# replace old java versions 1.x.* (java 1.7, java 1.8) with x.*
s/version "1\.\(.*\)"/version "\1"/
# extract the major and minor parts of the version
s/^.* version "\(.*\)\.\(.*\)\..*".*$/\1\2/p
s/^.* version "\([0-9]\{1,\}\)\.\([0-9]\{1,\}\).*".*$/\1\2/p
}')
# java_vendor is either java (oracle) or openjdk
readonly java_vendor=$(echo $full_ver | sed -n -e 's/^\(.*\) version .*$/\1/p')
readonly java_vendor=$(echo "$full_ver" | sed -n -e 's/^\(.*\) version .*$/\1/p')
}
jre_specific_vm_options() {
@@ -197,7 +199,7 @@ case "${APPNAME}" in
readonly CLASSNAME="net.sourceforge.pmd.util.treeexport.TreeExportCli"
;;
*)
echo "${APPNAME} is NOT a valid application name, valid options are:$(valid_app_options)"
echo "${APPNAME} is NOT a valid application name, valid options are: $(valid_app_options)"
;;
esac
@@ -0,0 +1,103 @@
@echo off
:: BSD-style license; for more info see http://pmd.sourceforge.net/license.html
::
:: Simple manual test script
:: - code is copied from designer.bat to be tested here (so please check, it might be out of sync)
:: - mostly the function "determine_java_version" is tested here
:: - just run it with "designertest.bat" and look at the output
:: - test cases are at the end of this script
::
GOTO :main
:determine_java_version
:: sets the jver variable to the java version, eg 90 for 9.0.1+x or 80 for 1.8.0_171-b11 or 110 for 11.0.6.1
:: sets the jvendor variable to either java (oracle) or openjdk
for /f tokens^=1^,3^,4^,5^ delims^=.-_+^"^ %%j in (%full_version%) do (
set jvendor=%%j
if %%l EQU ea (
set /A "jver=%%k0"
) else (
if %%k EQU 1 (
:: for java version 1.7.x, 1.8.x, ignore the first 1.
set /A "jver=%%l%%m"
) else (
set /A "jver=%%k%%l"
)
)
)
set detection=
if %jver% GEQ 70 (
if %jver% LSS 80 (
set detection="detected java 7"
)
)
if [%detection%] == [] (
if %jver% GEQ 80 (
if %jver% LSS 90 (
set detection="detected java 8"
)
)
)
if [%detection%] == [] (
if %jver% GEQ 90 (
if %jver% LSS 110 (
if %jvendor% == java (
set detection="detected java 9 or 10 from oracle"
)
)
)
)
if [%detection%] == [] (
if %jvendor% == openjdk (
set detection="detected java 11 from oracle or any openjdk"
)
)
if [%detection%] == [] (
if %jvendor% == java (
if %jver% GEQ 110 (
set detection="detected java 11 from oracle or any openjdk"
)
)
)
EXIT /B
:run_test
set full_version=%1
set expected_vendor=%2
set expected_version=%3
set expected_detection=%4
CALL :determine_java_version
echo full_version: %full_version%
if %jver% == %expected_version% ( echo jver: %jver% OK ) ELSE ( echo jver: %jver% EXPECTED: %expected_version%  )
if %jvendor% == %expected_vendor% ( echo jvendor: %jvendor% OK ) ELSE ( echo jvendor: %jvendor% EXPECTED: %expected_vendor%  )
if [%detection%] == [%expected_detection%] ( echo detection: %detection% OK ) ELSE ( echo detection: %detection% EXPECTED: %expected_detection%  )
echo.
EXIT /B
:main
CALL :run_test "java version ""1.7.0_80""" java 70 "detected java 7"
CALL :run_test "openjdk version ""1.7.0_352""" openjdk 70 "detected java 7"
CALL :run_test "java version ""1.8.0_271""" java 80 "detected java 8"
CALL :run_test "openjdk version ""1.8.0_345""" openjdk 80 "detected java 8"
CALL :run_test "java version ""9.0.4""" java 90 "detected java 9 or 10 from oracle"
CALL :run_test "openjdk version ""9.0.4""" openjdk 90 "detected java 11 from oracle or any openjdk"
CALL :run_test "java version ""10.0.2"" 2018-07-17" java 100 "detected java 9 or 10 from oracle"
CALL :run_test "openjdk version ""11.0.6"" 2022-08-12" openjdk 110 "detected java 11 from oracle or any openjdk"
CALL :run_test "openjdk version ""11.0.6.1"" 2022-08-12" openjdk 110 "detected java 11 from oracle or any openjdk"
CALL :run_test "java version ""11.0.13"" 2021-10-19 LTS" java 110 "detected java 11 from oracle or any openjdk"
CALL :run_test "openjdk version ""17.0.4"" 2022-08-12" openjdk 170 "detected java 11 from oracle or any openjdk"
CALL :run_test "openjdk version ""17.1.4"" 2022-08-12" openjdk 171 "detected java 11 from oracle or any openjdk"
CALL :run_test "openjdk version ""17.0.4.1"" 2022-08-12" openjdk 170 "detected java 11 from oracle or any openjdk"
CALL :run_test "openjdk version ""18.0.2.1"" 2022-08-18" openjdk 180 "detected java 11 from oracle or any openjdk"
CALL :run_test "openjdk version ""19-ea"" 2022-09-20" openjdk 190 "detected java 11 from oracle or any openjdk"
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
# BSD-style license; for more info see http://pmd.sourceforge.net/license.html
#
# Simple manual test script
# - code is copied from run.sh to be tested here (so please check, it might be out of sync)
# - mostly the function "determine_java_version" is tested here
# - just run it with "./runtest.sh" and look at the output
# - test cases are at the end of this script
#
export LANG=en_US.UTF-8
FULL_JAVA_VERSION=""
get_full_java_version() {
#java -version 2>&1
#echo "openjdk version \"11.0.6\" 2022-08-12"
echo "$FULL_JAVA_VERSION"
}
determine_java_version() {
local full_ver=$(get_full_java_version)
# java_ver is eg "80" for java 1.8, "90" for java 9.0, "100" for java 10.0.x
java_ver=$(echo "$full_ver" | sed -n '{
# replace early access versions, e.g. 11-ea with 11.0.0
s/-ea/.0.0/
# replace versions such as 10 with 10.0.0
s/version "\([0-9]\{1,\}\)"/version "\1.0.0"/
# replace old java versions 1.x.* (java 1.7, java 1.8) with x.*
s/version "1\.\(.*\)"/version "\1"/
# extract the major and minor parts of the version
s/^.* version "\([0-9]\{1,\}\)\.\([0-9]\{1,\}\).*".*$/\1\2/p
}')
# java_vendor is either java (oracle) or openjdk
java_vendor=$(echo "$full_ver" | sed -n -e 's/^\(.*\) version .*$/\1/p')
}
jre_specific_vm_options() {
options=""
if [ "$java_ver" -ge 70 ] && [ "$java_ver" -lt 80 ]
then
options="detected java 7"
elif [ "$java_ver" -ge 80 ] && [ "$java_ver" -lt 90 ]
then
options="detected java 8"
elif [ "$java_ver" -ge 90 ] && [ "$java_ver" -lt 110 ] && [ "$java_vendor" = "java" ]
then
options="detected java 9 or 10 from oracle"
elif [ "$java_vendor" = "openjdk" ] || ( [ "$java_vendor" = "java" ] && [ "$java_ver" -ge 110 ] )
then
options="detected java 11 from oracle or any openjdk"
fi
echo $options
}
run_test() {
FULL_JAVA_VERSION="$1"
EXPECTED_VENDOR="$2"
EXPECTED_VER="$3"
EXPECTED="$4"
echo "Testing: '${FULL_JAVA_VERSION}'"
determine_java_version
java_opts="$(jre_specific_vm_options)"
echo -n "java_ver: $java_ver "
if [ "$EXPECTED_VER" = "$java_ver" ]; then echo -e "\e[32mOK\e[0m"; else echo -e "\e[31mFAILED\e[0m"; fi
echo -n "java_vendor: $java_vendor "
if [ "$EXPECTED_VENDOR" = "$java_vendor" ]; then echo -e "\e[32mOK\e[0m"; else echo -e "\e[31mFAILED\e[0m"; fi
echo -n "java_opts: $java_opts "
if [ "$EXPECTED" = "$java_opts" ]; then echo -e "\e[32mOK\e[0m"; else echo -e "\e[31mFAILED\e[0m - expected: ${EXPECTED}"; fi
echo
}
run_test "java version \"1.7.0_80\"" "java" "70" "detected java 7"
run_test "openjdk version \"1.7.0_352\"" "openjdk" "70" "detected java 7"
run_test "java version \"1.8.0_271\"" "java" "80" "detected java 8"
run_test "openjdk version \"1.8.0_345\"" "openjdk" "80" "detected java 8"
run_test "java version \"9.0.4\"" "java" "90" "detected java 9 or 10 from oracle"
run_test "openjdk version \"9.0.4\"" "openjdk" "90" "detected java 11 from oracle or any openjdk"
run_test "java version \"10.0.2\" 2018-07-17" "java" "100" "detected java 9 or 10 from oracle"
run_test "openjdk version \"11.0.6\" 2022-08-12" "openjdk" "110" "detected java 11 from oracle or any openjdk"
run_test "openjdk version \"11.0.6.1\" 2022-08-12" "openjdk" "110" "detected java 11 from oracle or any openjdk"
run_test "java version \"11.0.13\" 2021-10-19 LTS" "java" "110" "detected java 11 from oracle or any openjdk"
run_test "openjdk version \"17.0.4\" 2022-08-12" "openjdk" "170" "detected java 11 from oracle or any openjdk"
run_test "openjdk version \"17.1.4\" 2022-08-12" "openjdk" "171" "detected java 11 from oracle or any openjdk"
run_test "openjdk version \"17.0.4.1\" 2022-08-12" "openjdk" "170" "detected java 11 from oracle or any openjdk"
run_test "openjdk version \"18.0.2.1\" 2022-08-18" "openjdk" "180" "detected java 11 from oracle or any openjdk"
run_test "openjdk version \"19-ea\" 2022-09-20" "openjdk" "190" "detected java 11 from oracle or any openjdk"
+1 -1
View File
@@ -31,7 +31,7 @@
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
<version>1.15.3</version>
</dependency>
<dependency>
@@ -57,6 +57,15 @@ public class CommentDefaultAccessModifierRule extends AbstractIgnoredAnnotationR
Collection<String> ignoredStrings = new ArrayList<>();
ignoredStrings.add("com.google.common.annotations.VisibleForTesting");
ignoredStrings.add("android.support.annotation.VisibleForTesting");
ignoredStrings.add("org.junit.jupiter.api.Test");
ignoredStrings.add("org.junit.jupiter.api.ParameterizedTest");
ignoredStrings.add("org.junit.jupiter.api.RepeatedTest");
ignoredStrings.add("org.junit.jupiter.api.TestFactory");
ignoredStrings.add("org.junit.jupiter.api.TestTemplate");
ignoredStrings.add("org.junit.jupiter.api.BeforeEach");
ignoredStrings.add("org.junit.jupiter.api.BeforeAll");
ignoredStrings.add("org.junit.jupiter.api.AfterEach");
ignoredStrings.add("org.junit.jupiter.api.AfterAll");
return ignoredStrings;
}
@@ -17,7 +17,9 @@ import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTNameList;
@@ -186,9 +188,12 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule {
if (matches.isEmpty()) {
if (isJavaLangImplicit(node)) {
addViolation(data, node, new Object[] { node.getImage(), "java.lang.*", "implicit "});
asCtx(data).addViolation(node,
node.getImage(), "java.lang.*", "implicit ");
} else if (isSamePackage(node, name)) {
addViolation(data, node, new Object[] { node.getImage(), currentPackage + ".*", "same package "});
if (!hasSameSimpleNameInScope(node)) {
asCtx(data).addViolation(node, node.getImage(), currentPackage + ".*", "same package ");
}
}
} else {
ASTImportDeclaration firstMatch = findFirstMatch(matches);
@@ -199,11 +204,29 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule {
String importStr = firstMatch.getImportedName() + (firstMatch.isImportOnDemand() ? ".*" : "");
String type = firstMatch.isStatic() ? "static " : "";
addViolation(data, node, new Object[] { node.getImage(), importStr, type });
asCtx(data).addViolation(node, node.getImage(), importStr, type);
}
}
}
private boolean hasSameSimpleNameInScope(TypeNode node) {
final ASTCompilationUnit root = node.getRoot();
final List<ASTClassOrInterfaceDeclaration> declarationDescendants = root.findDescendantsOfType(ASTClassOrInterfaceDeclaration.class);
final Class<?> nodeType = node.getType();
if (nodeType == null) {
return false;
}
for (ASTClassOrInterfaceDeclaration declarationDescendant : declarationDescendants) {
if (nodeType.getSimpleName().equals(declarationDescendant.getSimpleName())
&& !nodeType.getName().equals(declarationDescendant.getQualifiedName().toString())) {
return true;
}
}
return false;
}
private ASTImportDeclaration findFirstMatch(List<ASTImportDeclaration> imports) {
// first search only static imports
ASTImportDeclaration result = null;
@@ -404,7 +427,7 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule {
// Is it a conflict with a class in the same file?
final Set<String> qualifiedTypes = node.getScope().getEnclosingScope(SourceFileScope.class)
.getQualifiedTypeNames().keySet();
.getQualifiedTypeNames().keySet();
for (final String qualified : qualifiedTypes) {
int fullLength = qualified.length();
if (qualified.endsWith(unqualifiedName)
@@ -19,7 +19,7 @@ Rules which enforce generally accepted best practices.
The abstract class does not contain any abstract methods. An abstract class suggests
an incomplete implementation, which is to be completed by subclasses implementing the
abstract methods. If the class is intended to be used as a base class only (not to be instantiated
directly) a protected constructor can be provided prevent direct instantiation.
directly) a protected constructor can be provided to prevent direct instantiation.
</description>
<priority>3</priority>
<example>
@@ -436,9 +436,10 @@ public class Éléphant {}
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_codestyle.html#commentdefaultaccessmodifier">
<description>
To avoid mistakes if we want that an Annotation, Class, Enum, Method, Constructor or Field have a default access modifier
we must add a comment at the beginning of it's declaration.
By default the comment must be `/* default */` or `/* package */`, if you want another, you have to provide a regular expression.
This rule ignores by default all cases that have a @VisibleForTesting annotation. Use the
we must add a comment at the beginning of its declaration.
By default, the comment must be `/* default */` or `/* package */`, if you want another, you have to provide a regular expression.
This rule ignores by default all cases that have a `@VisibleForTesting` annotation or any JUnit5 annotation. Use the
property "ignoredAnnotations" to customize the recognized annotations.
</description>
<priority>3</priority>
@@ -17,7 +17,7 @@ Rules that help you discover design issues.
message="No abstract method which means that the keyword is most likely used to prevent instantiation. Use a private or protected constructor instead."
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_design.html#abstractclasswithoutanymethod">
<description>
If an abstract class does not provides any methods, it may be acting as a simple data container
If an abstract class does not provide any methods, it may be acting as a simple data container
that is not meant to be instantiated. In this case, it is probably better to use a private or
protected constructor in order to prevent instantiation than make the class misleadingly abstract.
</description>
@@ -53,7 +53,7 @@ public abstract class Example {
class="net.sourceforge.pmd.lang.rule.XPathRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_design.html#avoidcatchinggenericexception">
<description>
Avoid catching generic exceptions such as NullPointerException, RuntimeException, Exception in try-catch block
Avoid catching generic exceptions such as NullPointerException, RuntimeException, Exception in try-catch block.
</description>
<priority>3</priority>
<properties>
@@ -836,6 +836,8 @@ in each object at runtime.
]
]
/VariableDeclaratorId
[not(@Image = //MethodDeclaration[@Static = false()]//SynchronizedStatement/Expression/PrimaryExpression/
(PrimaryPrefix/Name|PrimarySuffix[preceding-sibling::PrimaryPrefix[@ThisModifier = true()]])/@Image)]
]]>
</value>
</property>
@@ -844,8 +844,8 @@ private String baz() {
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_performance.html#toofewbranchesforaswitchstatement">
<description>
Switch statements are intended to be used to support complex branching behaviour. Using a switch for only a few
cases is ill-advised, since switches are not as easy to understand as if-then statements. In these cases use the
if-then statement to increase code readability.
cases is ill-advised, since switches are not as easy to understand as if-else statements. In these cases use the
if-else statement to increase code readability.
</description>
<priority>3</priority>
<properties>
@@ -381,6 +381,20 @@ public interface MyInterface {
public enum MyEnum {
FOO;
class MyNestedClass {}
}
]]></code>
</test-code>
<test-code>
<description>#3859 [java] CommentDefaultAccessModifier is triggered in JUnit5 method and it was conflicting with rule JUnit5TestShouldBePackagePrivate</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
import org.junit.jupiter.api.Test;
class SomeTest {
@Test
void test() {}
}
]]></code>
</test-code>
@@ -622,6 +622,40 @@ public class UnnecessaryFullyQualifiedName {
]]></code>
</test-code>
<test-code>
<description>False positive when same package inner class is referenced (not enum) #4085</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
package net.sourceforge.pmd.lang.java.rule.codestyle.unnecessaryfullyqualifiedname;
public class OuterTestClass {
public static class TestClass{
private final net.sourceforge.pmd.lang.java.rule.codestyle.unnecessaryfullyqualifiedname.TestClass test;
public TestClass(net.sourceforge.pmd.lang.java.rule.codestyle.unnecessaryfullyqualifiedname.TestClass test){
this.test = test;
}
}
}
]]></code>
</test-code>
<test-code>
<description>Should report fully-qualified name usage of a class in itself #4085</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<code><![CDATA[
package net.sourceforge.pmd.lang.java.rule.codestyle.unnecessaryfullyqualifiedname;
public class TestClass {
public static net.sourceforge.pmd.lang.java.rule.codestyle.unnecessaryfullyqualifiedname.TestClass INSTANCE(){
return new TestClass();
}
}
]]></code>
</test-code>
<test-code>
<description>#2098 false positive with annotated package</description>
<expected-problems>0</expected-problems>
@@ -231,4 +231,34 @@ public class Foo {
}
]]></code>
</test-code>
<test-code>
<description>[java] FinalFieldCouldBeStatic false positive with non-static synchronized block (regression in 6.48, worked with 6.47) #4090</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<code><![CDATA[
class foo {
private final Object[] lock = new Object[0];
private final Object[] lock2 = new Object[0];
private final Object[] o = new Object[0];
void init() {
synchronized (lock) {
// do init stuff
}
}
public void close() {
synchronized (this.lock2) {
// do close stuff
}
}
void foo() {
System.out.println(o);
}
}
]]></code>
</test-code>
</test-data>
File diff suppressed because it is too large. Load diff
@@ -4,15 +4,28 @@
package net.sourceforge.pmd.cpd;
import java.util.Properties;
/**
* Language implementation for Lua
*/
public class LuaLanguage extends AbstractLanguage {
public LuaLanguage() {
this(System.getProperties());
}
/**
* Creates a new Lua Language instance.
*/
public LuaLanguage() {
public LuaLanguage(Properties properties) {
super("Lua", "lua", new LuaTokenizer(), ".lua");
setProperties(properties);
}
@Override
public final void setProperties(Properties properties) {
LuaTokenizer tokenizer = (LuaTokenizer) getTokenizer();
tokenizer.setProperties(properties);
}
}
@@ -4,8 +4,11 @@
package net.sourceforge.pmd.cpd;
import java.util.Properties;
import org.antlr.v4.runtime.CharStream;
import net.sourceforge.pmd.cpd.token.AntlrToken;
import net.sourceforge.pmd.cpd.token.AntlrTokenFilter;
import net.sourceforge.pmd.lang.antlr.AntlrTokenManager;
import net.sourceforge.pmd.lang.lua.antlr4.LuaLexer;
@@ -15,6 +18,22 @@ import net.sourceforge.pmd.lang.lua.antlr4.LuaLexer;
*/
public class LuaTokenizer extends AntlrTokenizer {
private boolean ignoreLiteralSequences = false;
/**
* Sets the possible options for the Lua tokenizer.
*
* @param properties the properties
* @see #OPTION_IGNORE_LITERAL_SEQUENCES
*/
public void setProperties(Properties properties) {
ignoreLiteralSequences = getBooleanProperty(properties, OPTION_IGNORE_LITERAL_SEQUENCES);
}
private boolean getBooleanProperty(final Properties properties, final String property) {
return Boolean.parseBoolean(properties.getProperty(property, Boolean.FALSE.toString()));
}
@Override
protected AntlrTokenManager getLexerForSource(SourceCode sourceCode) {
CharStream charStream = AntlrTokenizer.getCharStreamFromSourceCode(sourceCode);
@@ -23,6 +42,151 @@ public class LuaTokenizer extends AntlrTokenizer {
@Override
protected AntlrTokenFilter getTokenFilter(final AntlrTokenManager tokenManager) {
return new AntlrTokenFilter(tokenManager);
return new LuaTokenFilter(tokenManager, ignoreLiteralSequences);
}
/**
* The {@link LuaTokenFilter} extends the {@link AntlrTokenFilter} to discard
* Lua-specific tokens.
* <p>
* By default, it discards semicolons, require statements, and
* enables annotation-based CPD suppression.
* </p>
*/
private static class LuaTokenFilter extends AntlrTokenFilter {
private final boolean ignoreLiteralSequences;
private boolean discardingRequires = false;
private boolean discardingNL = false;
private AntlrToken discardingLiteralsUntil = null;
private boolean discardCurrent = false;
LuaTokenFilter(final AntlrTokenManager tokenManager, boolean ignoreLiteralSequences) {
super(tokenManager);
this.ignoreLiteralSequences = ignoreLiteralSequences;
}
@Override
protected void analyzeToken(final AntlrToken currentToken) {
skipNewLines(currentToken);
}
@Override
protected void analyzeTokens(final AntlrToken currentToken, final Iterable<AntlrToken> remainingTokens) {
discardCurrent = false;
skipRequires(currentToken);
skipLiteralSequences(currentToken, remainingTokens);
}
private void skipRequires(final AntlrToken currentToken) {
final int type = currentToken.getKind();
if (type == LuaLexer.REQUIRE) {
discardingRequires = true;
} else if (type == LuaLexer.CLOSE_PARENS && discardingRequires) {
discardingRequires = false;
discardCurrent = true;
}
}
private void skipNewLines(final AntlrToken currentToken) {
discardingNL = currentToken.getKind() == LuaLexer.NL;
}
private void skipLiteralSequences(final AntlrToken currentToken, final Iterable<AntlrToken> remainingTokens) {
if (ignoreLiteralSequences) {
final int type = currentToken.getKind();
if (isDiscardingLiterals()) {
if (currentToken == discardingLiteralsUntil) { // NOPMD - intentional check for reference equality
discardingLiteralsUntil = null;
discardCurrent = true;
}
} else if (type == LuaLexer.OPEN_BRACE
|| type == LuaLexer.OPEN_BRACKET
|| type == LuaLexer.OPEN_PARENS) {
final AntlrToken finalToken = findEndOfSequenceOfLiterals(remainingTokens);
discardingLiteralsUntil = finalToken;
}
}
}
private AntlrToken findEndOfSequenceOfLiterals(final Iterable<AntlrToken> remainingTokens) {
boolean seenLiteral = false;
int braceCount = 0;
int bracketCount = 0;
int parenCount = 0;
for (final AntlrToken token : remainingTokens) {
switch (token.getKind()) {
case LuaLexer.INT:
case LuaLexer.NORMAL_STRING:
case LuaLexer.INTERPOLATED_STRING:
case LuaLexer.LONG_STRING:
case LuaLexer.HEX_FLOAT:
case LuaLexer.HEX:
case LuaLexer.FLOAT:
case LuaLexer.NIL:
case LuaLexer.BOOLEAN:
seenLiteral = true;
break; // can be skipped; continue to the next token
case LuaLexer.COMMA:
break; // can be skipped; continue to the next token
case LuaLexer.NL:
// this helps skip large multi-line data table sequences in Lua
break; // can be skipped; continue to the next token
case LuaLexer.ASSIGNMENT:
// this helps skip large data table sequences in Lua: { ["bob"] = "uncle", ["alice"] = "enby" }
break; // can be skipped; continue to the next token
case LuaLexer.OPEN_BRACE:
braceCount++;
break; // curly braces are allowed, as long as they're balanced
case LuaLexer.CLOSE_BRACE:
braceCount--;
if (braceCount < 0) {
// end of the list in the braces; skip all contents
return seenLiteral ? token : null;
} else {
// curly braces are not yet balanced; continue to the next token
break;
}
case LuaLexer.OPEN_BRACKET:
bracketCount++;
break; // brackets are allowed, as long as they're balanced
case LuaLexer.CLOSE_BRACKET:
bracketCount--;
if (bracketCount < 0) {
// end of the list in the brackets; skip all contents
return seenLiteral ? token : null;
} else {
// brackets are not yet balanced; continue to the next token
break;
}
case LuaLexer.OPEN_PARENS:
parenCount++;
break; // parens are allowed, as long as they're balanced
case LuaLexer.CLOSE_PARENS:
parenCount--;
if (parenCount < 0) {
// end of the list in the parens; skip all contents
return seenLiteral ? token : null;
} else {
// parens are not yet balanced; continue to the next token
break;
}
default:
// some other token than the expected ones; this is not a sequence of literals
return null;
}
}
return null;
}
public boolean isDiscardingLiterals() {
return discardingLiteralsUntil != null;
}
@Override
protected boolean isLanguageSpecificDiscarding() {
return discardingRequires || discardingNL || isDiscardingLiterals() || discardCurrent;
}
}
}
@@ -39,4 +39,14 @@ public class LuaTokenizerTest extends CpdTextComparisonTest {
public void testTabWidth() {
doTest("tabWidth");
}
@Test
public void testLuauTypes() {
doTest("luauTypes");
}
@Test
public void testComment() {
doTest("comment");
}
}
@@ -0,0 +1,13 @@
-- inline comment ("long comment")
print(1 --[[, 2]])
-- line comment ("short comment")
print(1) -- comment
-- inline comment with multiple lines ("long comment")
print(1 --[[comment line 1
comment line 2]])
-- line comment without any content
print(1) --
@@ -0,0 +1,23 @@
[Image] or [Truncated image[ Bcol Ecol
L3
[print] 1 5
[(] 6 6
[1] 7 7
[)] 18 18
L6
[print] 1 5
[(] 6 6
[1] 7 7
[)] 8 8
L9
[print] 1 5
[(] 6 6
[1] 7 7
L10
[)] 17 17
L13
[print] 1 5
[(] 6 6
[1] 7 7
[)] 8 8
EOF
Loaded 30 of 33 files, more files were not shown because too many files have changed in this diff. Show more