diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index a992ec8570..e25aa23c7c 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -127,6 +127,11 @@ pmd-groovy ${project.version} + + net.sourceforge.pmd + pmd-lua + ${project.version} + net.sourceforge.pmd pmd-java diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index 5da0b5f5a1..8caab02442 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -112,7 +112,7 @@ public class BinaryDistributionIT { result = CpdExecutor.runCpd(tempDir, "-h"); - result.assertExecutionResult(1, "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, go, groovy, java, jsp, kotlin, matlab, objectivec, perl, php, plsql, python, ruby, scala, swift, vf]"); + result.assertExecutionResult(1, "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, go, groovy, java, jsp, kotlin, lua, matlab, objectivec, perl, php, plsql, python, ruby, scala, swift, vf]"); result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "10", "--format", "text", "--files", srcDir); result.assertExecutionResult(4, "Found a 10 line (55 tokens) duplication in the following files:"); diff --git a/pmd-lua/pom.xml b/pmd-lua/pom.xml new file mode 100644 index 0000000000..6e3c30cd2f --- /dev/null +++ b/pmd-lua/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + pmd-lua + PMD Lua + + + net.sourceforge.pmd + pmd + 6.16.0-SNAPSHOT + + + + + + org.antlr + antlr4-maven-plugin + + + + maven-resources-plugin + + false + + ${*} + + + + + + + + + org.antlr + antlr4-runtime + + + net.sourceforge.pmd + pmd-core + + + commons-io + commons-io + + + + junit + junit + test + + + net.sourceforge.pmd + pmd-test + test + + + diff --git a/pmd-lua/src/main/antlr4/net/sourceforge/pmd/lang/lua/antlr4/Lua.g4 b/pmd-lua/src/main/antlr4/net/sourceforge/pmd/lang/lua/antlr4/Lua.g4 new file mode 100644 index 0000000000..497f11060e --- /dev/null +++ b/pmd-lua/src/main/antlr4/net/sourceforge/pmd/lang/lua/antlr4/Lua.g4 @@ -0,0 +1,335 @@ +/* +BSD License + +Copyright (c) 2013, Kazunori Sakamoto +Copyright (c) 2016, Alexander Alexeev +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the NAME of Rainer Schuster nor the NAMEs of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +This grammar file derived from: + + Lua 5.3 Reference Manual + http://www.lua.org/manual/5.3/manual.html + + Lua 5.2 Reference Manual + http://www.lua.org/manual/5.2/manual.html + + Lua 5.1 grammar written by Nicolai Mainiero + http://www.antlr3.org/grammar/1178608849736/Lua.g + +Tested by Kazunori Sakamoto with Test suite for Lua 5.2 (http://www.lua.org/tests/5.2/) + +Tested by Alexander Alexeev with Test suite for Lua 5.3 http://www.lua.org/tests/lua-5.3.2-tests.tar.gz +*/ + +grammar Lua; + +chunk + : block EOF + ; + +block + : stat* retstat? + ; + +stat + : ';' + | varlist '=' explist + | functioncall + | label + | 'break' + | 'goto' NAME + | 'do' block 'end' + | 'while' exp 'do' block 'end' + | 'repeat' block 'until' exp + | 'if' exp 'then' block ('elseif' exp 'then' block)* ('else' block)? 'end' + | 'for' NAME '=' exp ',' exp (',' exp)? 'do' block 'end' + | 'for' namelist 'in' explist 'do' block 'end' + | 'function' funcname funcbody + | 'local' 'function' NAME funcbody + | 'local' namelist ('=' explist)? + ; + +retstat + : 'return' explist? ';'? + ; + +label + : '::' NAME '::' + ; + +funcname + : NAME ('.' NAME)* (':' NAME)? + ; + +varlist + : var (',' var)* + ; + +namelist + : NAME (',' NAME)* + ; + +explist + : exp (',' exp)* + ; + +exp + : 'nil' | 'false' | 'true' + | number + | string + | '...' + | functiondef + | prefixexp + | tableconstructor + | exp operatorPower exp + | operatorUnary exp + | exp operatorMulDivMod exp + | exp operatorAddSub exp + | exp operatorStrcat exp + | exp operatorComparison exp + | exp operatorAnd exp + | exp operatorOr exp + | exp operatorBitwise exp + ; + +prefixexp + : varOrExp nameAndArgs* + ; + +functioncall + : varOrExp nameAndArgs+ + ; + +varOrExp + : var | '(' exp ')' + ; + +var + : (NAME | '(' exp ')' varSuffix) varSuffix* + ; + +varSuffix + : nameAndArgs* ('[' exp ']' | '.' NAME) + ; + +nameAndArgs + : (':' NAME)? args + ; + +/* +var + : NAME | prefixexp '[' exp ']' | prefixexp '.' NAME + ; + +prefixexp + : var | functioncall | '(' exp ')' + ; + +functioncall + : prefixexp args | prefixexp ':' NAME args + ; +*/ + +args + : '(' explist? ')' | tableconstructor | string + ; + +functiondef + : 'function' funcbody + ; + +funcbody + : '(' parlist? ')' block 'end' + ; + +parlist + : namelist (',' '...')? | '...' + ; + +tableconstructor + : '{' fieldlist? '}' + ; + +fieldlist + : field (fieldsep field)* fieldsep? + ; + +field + : '[' exp ']' '=' exp | NAME '=' exp | exp + ; + +fieldsep + : ',' | ';' + ; + +operatorOr + : 'or'; + +operatorAnd + : 'and'; + +operatorComparison + : '<' | '>' | '<=' | '>=' | '~=' | '=='; + +operatorStrcat + : '..'; + +operatorAddSub + : '+' | '-'; + +operatorMulDivMod + : '*' | '/' | '%' | '//'; + +operatorBitwise + : '&' | '|' | '~' | '<<' | '>>'; + +operatorUnary + : 'not' | '#' | '-' | '~'; + +operatorPower + : '^'; + +number + : INT | HEX | FLOAT | HEX_FLOAT + ; + +string + : NORMALSTRING | CHARSTRING | LONGSTRING + ; + +// LEXER + +NAME + : [a-zA-Z_][a-zA-Z_0-9]* + ; + +NORMALSTRING + : '"' ( EscapeSequence | ~('\\'|'"') )* '"' + ; + +CHARSTRING + : '\'' ( EscapeSequence | ~('\''|'\\') )* '\'' + ; + +LONGSTRING + : '[' NESTED_STR ']' + ; + +fragment +NESTED_STR + : '=' NESTED_STR '=' + | '[' .*? ']' + ; + +INT + : Digit+ + ; + +HEX + : '0' [xX] HexDigit+ + ; + +FLOAT + : Digit+ '.' Digit* ExponentPart? + | '.' Digit+ ExponentPart? + | Digit+ ExponentPart + ; + +HEX_FLOAT + : '0' [xX] HexDigit+ '.' HexDigit* HexExponentPart? + | '0' [xX] '.' HexDigit+ HexExponentPart? + | '0' [xX] HexDigit+ HexExponentPart + ; + +fragment +ExponentPart + : [eE] [+-]? Digit+ + ; + +fragment +HexExponentPart + : [pP] [+-]? Digit+ + ; + +fragment +EscapeSequence + : '\\' [abfnrtvz"'\\] + | '\\' '\r'? '\n' + | DecimalEscape + | HexEscape + | UtfEscape + ; + +fragment +DecimalEscape + : '\\' Digit + | '\\' Digit Digit + | '\\' [0-2] Digit Digit + ; + +fragment +HexEscape + : '\\' 'x' HexDigit HexDigit + ; + +fragment +UtfEscape + : '\\' 'u{' HexDigit+ '}' + ; + +fragment +Digit + : [0-9] + ; + +fragment +HexDigit + : [0-9a-fA-F] + ; + +COMMENT + : '--[' NESTED_STR ']' -> channel(HIDDEN) + ; + +LINE_COMMENT + : '--' + ( // -- + | '[' '='* // --[== + | '[' '='* ~('='|'['|'\r'|'\n') ~('\r'|'\n')* // --[==AA + | ~('['|'\r'|'\n') ~('\r'|'\n')* // --AAA + ) ('\r\n'|'\r'|'\n'|EOF) + -> channel(HIDDEN) + ; + +WS + : [ \t\u000C\r\n]+ -> skip + ; + +SHEBANG + : '#' '!' ~('\n'|'\r')* -> channel(HIDDEN) + ; diff --git a/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaLanguage.java b/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaLanguage.java new file mode 100644 index 0000000000..e2a87ec878 --- /dev/null +++ b/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaLanguage.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +/** + * Language implementation for Lua + */ +public class LuaLanguage extends AbstractLanguage { + + /** + * Creates a new Lua Language instance. + */ + public LuaLanguage() { + super("Lua", "lua", new LuaTokenizer(), ".lua"); + } +} diff --git a/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaTokenizer.java b/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaTokenizer.java new file mode 100644 index 0000000000..23c292dbe7 --- /dev/null +++ b/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaTokenizer.java @@ -0,0 +1,28 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import org.antlr.v4.runtime.CharStream; + +import net.sourceforge.pmd.cpd.token.AntlrTokenFilter; +import net.sourceforge.pmd.lang.antlr.AntlrTokenManager; +import net.sourceforge.pmd.lang.lua.antlr4.LuaLexer; + +/** + * The Lua Tokenizer + */ +public class LuaTokenizer extends AntlrTokenizer { + + @Override + protected AntlrTokenManager getLexerForSource(SourceCode sourceCode) { + CharStream charStream = AntlrTokenizer.getCharStreamFromSourceCode(sourceCode); + return new AntlrTokenManager(new LuaLexer(charStream), sourceCode.getFileName()); + } + + @Override + protected AntlrTokenFilter getTokenFilter(final AntlrTokenManager tokenManager) { + return new AntlrTokenFilter(tokenManager); + } +} diff --git a/pmd-lua/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-lua/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..ff792867ee --- /dev/null +++ b/pmd-lua/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.cpd.LuaLanguage diff --git a/pmd-lua/src/test/java/net/sourceforge/pmd/cpd/LuaTokenizerTest.java b/pmd-lua/src/test/java/net/sourceforge/pmd/cpd/LuaTokenizerTest.java new file mode 100644 index 0000000000..5328e71823 --- /dev/null +++ b/pmd-lua/src/test/java/net/sourceforge/pmd/cpd/LuaTokenizerTest.java @@ -0,0 +1,56 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import net.sourceforge.pmd.testframework.AbstractTokenizerTest; + +@RunWith(Parameterized.class) +public class LuaTokenizerTest extends AbstractTokenizerTest { + + private final String filename; + private final int nExpectedTokens; + + public LuaTokenizerTest(String filename, int nExpectedTokens) { + this.filename = filename; + this.nExpectedTokens = nExpectedTokens; + } + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[] { "factorial.lua", 44 }, + new Object[] { "helloworld.lua", 5 } + ); + } + + @Before + @Override + public void buildTokenizer() throws IOException { + this.tokenizer = new LuaTokenizer(); + this.sourceCode = new SourceCode(new SourceCode.StringCodeLoader(this.getSampleCode(), this.filename)); + } + + @Override + public String getSampleCode() throws IOException { + return IOUtils.toString(LuaTokenizer.class.getResourceAsStream(this.filename), StandardCharsets.UTF_8); + } + + @Test + public void tokenizeTest() throws IOException { + this.expectedTokenCount = nExpectedTokens; + super.tokenizeTest(); + } +} diff --git a/pmd-lua/src/test/resources/net/sourceforge/pmd/cpd/factorial.lua b/pmd-lua/src/test/resources/net/sourceforge/pmd/cpd/factorial.lua new file mode 100644 index 0000000000..f2f2d82661 --- /dev/null +++ b/pmd-lua/src/test/resources/net/sourceforge/pmd/cpd/factorial.lua @@ -0,0 +1,13 @@ +-- defines a factorial function + function fact (n) + if n == 0 then + return 1 + else + return n * fact(n-1) + end + end + + print("enter a number:") + a = io.read("*number") -- read a number + print(fact(a)) + diff --git a/pmd-lua/src/test/resources/net/sourceforge/pmd/cpd/helloworld.lua b/pmd-lua/src/test/resources/net/sourceforge/pmd/cpd/helloworld.lua new file mode 100644 index 0000000000..45d4840a9f --- /dev/null +++ b/pmd-lua/src/test/resources/net/sourceforge/pmd/cpd/helloworld.lua @@ -0,0 +1,2 @@ + print("Hello World") + diff --git a/pom.xml b/pom.xml index 07fc342ed7..92c9318b5b 100644 --- a/pom.xml +++ b/pom.xml @@ -1141,6 +1141,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code pmd-fortran pmd-go pmd-groovy + pmd-lua pmd-java pmd-javascript pmd-jsp