Merge branch 'pr-1236'

This commit is contained in:
Juan Martín Sotuyo Dodero
2018-07-30 02:01:49 -03:00
15 changed files with 486 additions and 2 deletions

View File

@ -13,16 +13,25 @@ This is a minor release.
### Table Of Contents
* [New and noteworthy](#new-and-noteworthy)
* [New Rules](#new-rules)
* [Fixed Issues](#fixed-issues)
* [API Changes](#api-changes)
* [External Contributions](#external-contributions)
### New and noteworthy
#### New Rules
* The new PL/SQL rule [`ForLoopNaming`](pmd_rules_plsql_codestyle.html#forloopnaming) (`plsql-codestyle`)
enforces a naming convention for "for loops". Both "cursor for loops" and "index for loops" are covered.
The rule can be customized via patterns. By default, short variable names are reported.
### Fixed Issues
* java-errorprone
* [#1078](https://github.com/pmd/pmd/issues/1078): \[java] MissingSerialVersionUID rule does not seem to catch inherited classes
* plsql
* [#681](https://github.com/pmd/pmd/issues/681): \[plsql] Parse error with Cursor For Loop
### API Changes

View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<ruleset name="670"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>
This ruleset contains links to rules that are new in PMD v6.7.0
</description>
<rule ref="category/plsql/codestyle.xml/ForLoopNaming"/>
</ruleset>

View File

@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Added more complete support for CREATE TABLE
* Added ASTCursorForLoop and ASTSelectStatement
*
* Andreas Dangel 07/2018
*====================================================================
@ -1176,6 +1177,7 @@ ASTUnlabelledStatement UnlabelledStatement() :
| LOOKAHEAD(3) ContinueStatement() ";" // CONTINUE keyword was added in 11G, so Oracle compilation supports CONTINUE as a variable name
| CaseStatement() ";"
| IfStatement() ";"
| LOOKAHEAD(<FOR> ID() <IN> "(" <SELECT>) CursorForLoopStatement() ";"
| ForStatement() ";"
| ForAllStatement() ";"
| LoopStatement() ";"
@ -1273,6 +1275,20 @@ ASTLoopStatement LoopStatement() :
{ return jjtThis ; }
}
ASTCursorForLoopStatement CursorForLoopStatement() :
{}
{
<FOR> ForIndex() <IN> "(" SelectStatement() ")" <LOOP> (Statement())+ <END> <LOOP> [<IDENTIFIER>]
{ return jjtThis ; }
}
ASTSelectStatement SelectStatement() :
{}
{
SqlStatement("(", ")")
{ return jjtThis ; }
}
/** Scope rule: the loop index only exists within the Loop */
ASTForStatement ForStatement() :
{}
@ -3732,7 +3748,7 @@ TOKEN [IGNORE_CASE]:
*/
TOKEN :
{
< #GERMAN_SPECIAL_CHARACTERS: "Ä" | "Ö" | "Ü" | "ä" | "ü" | "ö" | "ß" >
< #GERMAN_SPECIAL_CHARACTERS: "Ä" | "Ö" | "Ü" | "ä" | "ü" | "ö" | "ß" >
|
< #LETTER: ["A"-"Z"] | ["a"-"z"] | <GERMAN_SPECIAL_CHARACTERS> >
|
@ -3745,7 +3761,7 @@ TOKEN :
| "$" | "&" | "_" | "|" | "{" | "}" | "?" | "[" | "]"
| " " | "\t" >
|
< #SPECIAL_CHARACTERS: "á" | "Ž" | "™" | "š" | "„" | "”" | "ý" | "²" | "€" | "³" | "µ">
< #SPECIAL_CHARACTERS: "á" | "Ž" | "™" | "š" | "„" | "”" | "ý" | "²" | "€" | "³" | "µ">
|
< #DELIMITER: "+" | "%" | "'" | "\"" | "." | "/" | "(" | ")" | ":" | "," | "*" | "=" | "<" | ">" | "@" | ";" | "-">
|

View File

@ -710,4 +710,14 @@ public class PLSQLParserVisitorAdapter implements PLSQLParserVisitor {
public Object visit(ASTReferencesClause node, Object data) {
return visit((PLSQLNode) node, data);
}
@Override
public Object visit(ASTCursorForLoopStatement node, Object data) {
return visit((PLSQLNode) node, data);
}
@Override
public Object visit(ASTSelectStatement node, Object data) {
return visit((PLSQLNode) node, data);
}
}

View File

@ -805,6 +805,16 @@ public abstract class AbstractPLSQLRule extends AbstractRule implements PLSQLPar
return visit((PLSQLNode) node, data);
}
@Override
public Object visit(ASTCursorForLoopStatement node, Object data) {
return visit((PLSQLNode) node, data);
}
@Override
public Object visit(ASTSelectStatement node, Object data) {
return visit((PLSQLNode) node, data);
}
/*
* Treat all Executable Code
*/

View File

@ -52,4 +52,64 @@ end inline_pragma_error;
</example>
</rule>
<rule name="ForLoopNaming"
language="plsql"
since="6.7.0"
message="Use meaningful names for loop variables"
class="net.sourceforge.pmd.lang.rule.XPathRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codestyle.html#forloopnaming">
<description>
In case you have loops please name the loop variables more meaningful.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>
<![CDATA[
//CursorForLoopStatement[
$allowSimpleLoops = 'false' or
(Statement//CursorForLoopStatement or ancestor::CursorForLoopStatement)
]
/ForIndex[not(matches(@Image, $cursorPattern))]
|
//ForStatement[
$allowSimpleLoops = 'false' or
(Statement//ForStatement or ancestor::ForStatement)
]
/ForIndex[not(matches(@Image, $indexPattern))]
]]>
</value>
</property>
<property name="allowSimpleLoops" type="Boolean" description="Ignore simple loops, that are not nested" value="false" />
<property name="cursorPattern" type="Regex" description="The pattern used for the curosr loop variable" value="[a-zA-Z_0-9]{5,}" />
<property name="indexPattern" type="Regex" description="The pattern used for the index loop variable" value="[a-zA-Z_0-9]{5,}" />
</properties>
<example>
<![CDATA[
-- good example
BEGIN
FOR company IN (SELECT * FROM companies) LOOP
FOR contact IN (SELECT * FROM contacts) LOOP
FOR party IN (SELECT * FROM parties) LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
-- bad example
BEGIN
FOR c1 IN (SELECT * FROM companies) LOOP
FOR c2 IN (SELECT * FROM contacts) LOOP
FOR c3 IN (SELECT * FROM parties) LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
]]>
</example>
</rule>
</ruleset>

View File

@ -4,6 +4,7 @@
package net.sourceforge.pmd.lang.plsql;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
@ -15,6 +16,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.LanguageVersionHandler;
@ -127,4 +130,12 @@ public abstract class AbstractPLSQLParserTst {
return languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()).parse(null,
new StringReader(code));
}
public String loadTestResource(String name) {
try {
return IOUtils.toString(this.getClass().getResourceAsStream(name));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,64 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.plsql.ast;
import org.junit.Assert;
import org.junit.Test;
import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst;
public class CursorForLoopTest extends AbstractPLSQLParserTst {
@Test
public void parseCursorForLoopSimple() {
String code = loadTestResource("CursorForLoopSimple.pls");
ASTInput input = parsePLSQL(code);
ASTCursorForLoopStatement forloop = input.getFirstDescendantOfType(ASTCursorForLoopStatement.class);
Assert.assertNotNull(forloop);
ASTForIndex forindex = forloop.getFirstChildOfType(ASTForIndex.class);
Assert.assertNotNull(forindex);
Assert.assertEquals("someone", forindex.getImage());
}
@Test
public void parseCursorForLoopNested() {
String code = loadTestResource("CursorForLoopNested.pls");
ASTInput input = parsePLSQL(code);
ASTCursorForLoopStatement forloop = input.getFirstDescendantOfType(ASTCursorForLoopStatement.class);
Assert.assertNotNull(forloop);
ASTForIndex forindex = forloop.getFirstChildOfType(ASTForIndex.class);
Assert.assertNotNull(forindex);
Assert.assertEquals("c_cmp", forindex.getImage());
ASTCursorForLoopStatement forloop2 = forloop.getFirstDescendantOfType(ASTCursorForLoopStatement.class);
ASTForIndex forindex2 = forloop2.getFirstChildOfType(ASTForIndex.class);
Assert.assertEquals("c_con", forindex2.getImage());
ASTCursorForLoopStatement forloop3 = forloop2.getFirstDescendantOfType(ASTCursorForLoopStatement.class);
ASTForIndex forindex3 = forloop3.getFirstChildOfType(ASTForIndex.class);
Assert.assertEquals("c_pa", forindex3.getImage());
}
@Test
public void parseCursorForLoop1047a() {
String code = loadTestResource("CursorForLoop1047a.pls");
ASTInput input = parsePLSQL(code);
Assert.assertNotNull(input);
}
@Test
public void parseCursorForLoop1047b() {
String code = loadTestResource("CursorForLoop1047b.pls");
ASTInput input = parsePLSQL(code);
Assert.assertNotNull(input);
}
@Test
public void parseCursorForLoop681() {
String code = loadTestResource("CursorForLoop681.pls");
ASTInput input = parsePLSQL(code);
Assert.assertNotNull(input);
}
}

View File

@ -13,5 +13,6 @@ public class CodeStyleRulesTest extends SimpleAggregatorTst {
@Override
public void setUp() {
addRule(RULESET, "MisplacedPragma");
addRule(RULESET, "ForLoopNaming");
}
}

View File

@ -0,0 +1,16 @@
--
-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html
--
CREATE OR REPLACE PROCEDURE test2 ( a OUT number, b OUT number )
AS
BEGIN
FOR registro IN ( SELECT SUM(field1),
MAX(field2)
INTO a,b
FROM test_tbl
GROUP BY fieldx)
LOOP
null;
END LOOP;
END test2;

View File

@ -0,0 +1,13 @@
--
-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html
--
-- this one was processed without errors
CREATE OR REPLACE PROCEDURE test2 ( a OUT number, b OUT number )
AS
BEGIN
FOR registro IN ( SELECT SUM(field1),MAX(field2) INTO a,b FROM test_tbl GROUP BY fieldx )
LOOP
null;
END LOOP;
END test2;

View File

@ -0,0 +1,20 @@
--
-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html
--
BEGIN
FOR sum_res IN (SELECT SUM(loss_reserve) loss_reserve,
SUM(expense_reserve) exp_reserve
FROM gicl_clm_reserve a, gicl_item_peril b
WHERE a.claim_id = b.claim_id
AND a.item_no = b.item_no
AND a.peril_cd = b.peril_cd
AND a.claim_id = p_claim_id
AND (NVL(b.close_flag, 'AP') IN ('AP','CC','CP') OR
NVL(b.close_flag2, 'AP') IN ('AP','CC','CP')))
LOOP
v_loss_res_amt := sum_res.loss_reserve;
v_exp_res_amt := sum_res.exp_reserve;
END LOOP;
END;
/

View File

@ -0,0 +1,14 @@
--
-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html
--
BEGIN
FOR c_cmp IN (SELECT * FROM companies) LOOP
FOR c_con IN (SELECT * FROM contacts) LOOP
FOR c_pa IN (SELECT * FROM parties) LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/

View File

@ -0,0 +1,16 @@
--
-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html
--
BEGIN
FOR someone IN (
SELECT * FROM employees
WHERE employee_id < 120
ORDER BY employee_id
)
LOOP
DBMS_OUTPUT.PUT_LINE('First name = ' || someone.first_name ||
', Last name = ' || someone.last_name);
END LOOP;
END;
/

View File

@ -0,0 +1,211 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data
xmlns="http://pmd.sourceforge.net/rule-tests"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd">
<test-code>
<description>CursorForLoop Simple case - no nesting</description>
<expected-problems>1</expected-problems>
<code><![CDATA[
BEGIN
FOR c IN (SELECT * FROM companies) LOOP
NULL;
END LOOP;
END;
/
]]></code>
<source-type>plsql</source-type>
</test-code>
<test-code>
<description>CursorForLoop Simple case - no nesting - allowSimpleLoops</description>
<rule-property name="allowSimpleLoops">true</rule-property>
<expected-problems>0</expected-problems>
<code><![CDATA[
BEGIN
FOR c IN (SELECT * FROM companies) LOOP
NULL;
END LOOP;
END;
/
]]></code>
<source-type>plsql</source-type>
</test-code>
<test-code>
<description>CursorForLoop Nested loops - good example, default pattern</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
BEGIN
FOR company IN (SELECT * FROM companies) LOOP
FOR contact IN (SELECT * FROM contacts) LOOP
FOR party IN (SELECT * FROM parties) LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
]]></code>
<source-type>plsql</source-type>
</test-code>
<test-code>
<description>CursorForLoop Nested loops - good example, custom pattern</description>
<rule-property name="cursorPattern">c_[a-z]+</rule-property>
<expected-problems>0</expected-problems>
<code><![CDATA[
BEGIN
FOR c_cmp IN (SELECT * FROM companies) LOOP
FOR c_con IN (SELECT * FROM contacts) LOOP
FOR c_pa IN (SELECT * FROM parties) LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
]]></code>
<source-type>plsql</source-type>
</test-code>
<test-code>
<description>CursorForLoop Nested loops - bad example, default pattern</description>
<expected-problems>3</expected-problems>
<expected-linenumbers>2,3,4</expected-linenumbers>
<code><![CDATA[
BEGIN
FOR cmp IN (SELECT * FROM companies) LOOP
FOR con IN (SELECT * FROM contacts) LOOP
FOR pa IN (SELECT * FROM parties) LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
]]></code>
<source-type>plsql</source-type>
</test-code>
<test-code>
<description>CursorForLoop Nested loops - bad example, custom pattern</description>
<rule-property name="cursorPattern">c_[a-z]+</rule-property>
<expected-problems>3</expected-problems>
<expected-linenumbers>2,3,4</expected-linenumbers>
<code><![CDATA[
BEGIN
FOR c1 IN (SELECT * FROM companies) LOOP
FOR c2 IN (SELECT * FROM contacts) LOOP
FOR c3 IN (SELECT * FROM parties) LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
]]></code>
<source-type>plsql</source-type>
</test-code>
<test-code>
<description>ForLoop Simple case - no nesting</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>2</expected-linenumbers>
<code><![CDATA[
BEGIN
FOR i IN 1..v_companies.COUNT LOOP
NULL;
END LOOP;
END;
/
]]></code>
</test-code>
<test-code>
<description>ForLoop Simple case - no nesting - allowSimpleLoops</description>
<rule-property name="allowSimpleLoops">true</rule-property>
<expected-problems>0</expected-problems>
<code><![CDATA[
BEGIN
FOR i IN 1..v_companies.COUNT LOOP
NULL;
END LOOP;
END;
/
]]></code>
</test-code>
<test-code>
<description>ForLoop Nested loops - good example, default pattern</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
BEGIN
FOR company IN 1..v_companies.COUNT LOOP
FOR contact IN 1..v_contacts.COUNT LOOP
FOR party IN 1..v_parties.COUNT LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
]]></code>
</test-code>
<test-code>
<description>ForLoop Nested loops - good example, custom pattern</description>
<rule-property name="indexPattern">i_[a-z]+</rule-property>
<expected-problems>0</expected-problems>
<code><![CDATA[
BEGIN
FOR i_cmp IN 1..v_companies.COUNT LOOP
FOR i_con IN 1..v_contacts.COUNT LOOP
FOR i_pa IN 1..v_parties.COUNT LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
]]></code>
</test-code>
<test-code>
<description>ForLoop Nested Loops - bad example, default pattern</description>
<expected-problems>3</expected-problems>
<expected-linenumbers>2,3,4</expected-linenumbers>
<code><![CDATA[
BEGIN
FOR i IN 1..v_companies.COUNT LOOP
FOR j IN 1..v_contacts.COUNT LOOP
FOR k IN 1..v_parties.COUNT LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
]]></code>
</test-code>
<test-code>
<description>ForLoop Nested Loops - bad example, custom pattern</description>
<rule-property name="indexPattern">i_[a-z]+</rule-property>
<expected-problems>3</expected-problems>
<expected-linenumbers>2,3,4</expected-linenumbers>
<code><![CDATA[
BEGIN
FOR i IN 1..v_companies.COUNT LOOP
FOR j IN 1..v_contacts.COUNT LOOP
FOR k IN 1..v_parties.COUNT LOOP
NULL;
END LOOP;
END LOOP;
END LOOP;
END;
/
]]></code>
</test-code>
</test-data>