diff --git a/pmd-cs/pom.xml b/pmd-cs/pom.xml new file mode 100644 index 0000000000..3679f4c65d --- /dev/null +++ b/pmd-cs/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + pmd-cs + PMD C# + + + net.sourceforge.pmd + pmd + 5.5.0-SNAPSHOT + + + + ${basedir}/../pmd-core + + + + + + maven-resources-plugin + + false + + ${*} + + + + + + + + net.sourceforge.pmd + pmd-core + + + + net.sourceforge.pmd + pmd-test + test + + + diff --git a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsLanguage.java b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsLanguage.java new file mode 100644 index 0000000000..f6a1bd145f --- /dev/null +++ b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsLanguage.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.util.Properties; + +/** + * Language implementation for C# + */ +public class CsLanguage extends AbstractLanguage { + + public CsLanguage() { + this(System.getProperties()); + } + + public CsLanguage(Properties properties) { + super("C#", "cs", new CsTokenizer(), ".cs"); + setProperties(properties); + } + + public final void setProperties(Properties properties) { + CsTokenizer tokenizer = (CsTokenizer)getTokenizer(); + tokenizer.setProperties(properties); + } +} diff --git a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java new file mode 100644 index 0000000000..c9dcce4a4e --- /dev/null +++ b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java @@ -0,0 +1,306 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.io.BufferedReader; +import java.io.CharArrayReader; +import java.io.Closeable; +import java.io.IOException; +import java.io.PushbackReader; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.RandomStringUtils; + +/** + * This class does a best-guess try-anything tokenization. + * + * @author jheintz + */ +public class CsTokenizer implements Tokenizer { + + private boolean ignoreUsings = false; + + public void setProperties(Properties properties) { + if (properties.containsKey(IGNORE_USINGS)) { + ignoreUsings = Boolean.parseBoolean(properties.getProperty(IGNORE_USINGS, "false")); + } + } + + @Override + public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { + Tokenizer tokenizer = + new Tokenizer(sourceCode.getCodeBuffer().toString()); + Token token = tokenizer.getNextToken(); + + while (!token.equals(Token.EOF)) { + Token lookAhead = tokenizer.getNextToken(); + + // Ignore using directives + // Only using directives should be ignored, because these are used to import namespaces + // + // Using directive: 'using System.Math;' + // Using statement: 'using (Font font1 = new Font(..)) { .. }' + if (ignoreUsings && + "using".equals(token.image) && + !"(".equals(lookAhead.image) + ) { + // We replace the 'using' token by a random token, because it should not be part of + // any duplication block. When we omit it from the token stream, there is a change that + // we get a duplication block that starts before the 'using' directives and ends afterwards. + String randomTokenText = + RandomStringUtils.randomAlphanumeric(20); + + token = new Token(randomTokenText, token.lineNumber); + //Skip all other tokens of the using directive to prevent a partial matching + while (!";".equals(lookAhead.image) && !lookAhead.equals(Token.EOF)) { + lookAhead = tokenizer.getNextToken(); + } + } + if (!";".equals(token.image)) { + tokenEntries.add(new TokenEntry(token.image, sourceCode.getFileName(), token.lineNumber)); + } + token = lookAhead; + } + tokenEntries.add(TokenEntry.getEOF()); + IOUtils.closeQuietly(tokenizer); + } + + public void setIgnoreUsings(boolean ignoreUsings) { + this.ignoreUsings = ignoreUsings; + } + + + private static class Tokenizer implements Closeable { + private boolean endOfFile; + private int line; + private final PushbackReader reader; + + public Tokenizer(String sourceCode) { + endOfFile = false; + line = 1; + reader = new PushbackReader(new BufferedReader(new CharArrayReader(sourceCode.toCharArray()))); + } + + public Token getNextToken() { + if (endOfFile) { + return Token.EOF; + } + + try { + int ic = reader.read(); + char c; + StringBuilder b; + while (ic != -1) { + c = (char) ic; + switch (c) { + // new line + case '\n': + line++; + ic = reader.read(); + break; + + // white space + case ' ': + case '\t': + case '\r': + ic = reader.read(); + break; + + case ';': + return new Token(";", line); + + // < << <= <<= > >> >= >>= + case '<': + case '>': + ic = reader.read(); + if (ic == '=') { + return new Token(c + "=", line); + } else if (ic == c) { + ic = reader.read(); + if (ic == '=') { + return new Token(c + c + "=", line); + } else { + reader.unread(ic); + return new Token(String.valueOf(c) + c, line); + } + } else { + reader.unread(ic); + return new Token(String.valueOf(c), line); + } + + // = == & &= && | |= || + += ++ - -= -- + case '=': + case '&': + case '|': + case '+': + case '-': + ic = reader.read(); + if (ic == '=' || ic == c) { + return new Token(c + String.valueOf((char) ic), line); + } else { + reader.unread(ic); + return new Token(String.valueOf(c), line); + } + + // ! != * *= % %= ^ ^= ~ ~= + case '!': + case '*': + case '%': + case '^': + case '~': + ic = reader.read(); + if (ic == '=') { + return new Token(c + "=", line); + } else { + reader.unread(ic); + return new Token(String.valueOf(c), line); + } + + // strings & chars + case '"': + case '\'': + int beginLine = line; + b = new StringBuilder(); + b.append(c); + while ((ic = reader.read()) != c) { + if (ic == -1) { + break; + } + b.append((char) ic); + if (ic == '\\') { + int next = reader.read(); + if (next != -1) { + b.append((char) next); + + if (next == '\n') { + line++; + } + } + } else if (ic == '\n') { + line++; + } + } + if (ic != -1) { + b.append((char) ic); + } + return new Token(b.toString(), beginLine); + + // / /= /*...*/ //... + case '/': + switch (c = (char) (ic = reader.read())) { + case '*': + //int beginLine = line; + int state = 1; + b = new StringBuilder(); + b.append("/*"); + + while ((ic = reader.read()) != -1) { + c = (char) ic; + b.append(c); + + if (c == '\n') { + line++; + } + + if (state == 1) { + if (c == '*') { + state = 2; + } + } else { + if (c == '/') { + ic = reader.read(); + break; + } else if (c != '*') { + state = 1; + } + } + } + // ignore the /* comment + // tokenEntries.add(new TokenEntry(b.toString(), + // sourceCode.getFileName(), beginLine)); + break; + + case '/': + b = new StringBuilder(); + b.append("//"); + while ((ic = reader.read()) != '\n') { + if (ic == -1) { + break; + } + b.append((char) ic); + } + // ignore the // comment + // tokenEntries.add(new TokenEntry(b.toString(), + // sourceCode.getFileName(), line)); + break; + + case '=': + return new Token("/=", line); + + default: + reader.unread(ic); + return new Token("/", line); + } + break; + + default: + // [a-zA-Z_][a-zA-Z_0-9]* + if (Character.isJavaIdentifierStart(c)) { + b = new StringBuilder(); + do { + b.append(c); + c = (char) (ic = reader.read()); + } while (Character.isJavaIdentifierPart(c)); + reader.unread(ic); + return new Token(b.toString(), line); + } + // numbers + else if (Character.isDigit(c) || c == '.') { + b = new StringBuilder(); + do { + b.append(c); + if (c == 'e' || c == 'E') { + c = (char) (ic = reader.read()); + if ("1234567890-".indexOf(c) == -1) { + break; + } + b.append(c); + } + c = (char) (ic = reader.read()); + } while ("1234567890.iIlLfFdDsSuUeExX".indexOf(c) != -1); + reader.unread(ic); + return new Token(b.toString(), line); + } + // anything else + else { + return new Token(String.valueOf(c), line); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + endOfFile = true; + return Token.EOF; + } + + @Override + public void close() throws IOException { + reader.close(); + } + } + + private static class Token { + public static final Token EOF = new Token("EOF", -1); + + public final String image; + public final int lineNumber; + + public Token(String image, int lineNumber) { + this.image = image; + this.lineNumber = lineNumber; + } + } +} diff --git a/pmd-cs/src/main/java/net/sourceforge/pmd/lang/cs/CsLanguageModule.java b/pmd-cs/src/main/java/net/sourceforge/pmd/lang/cs/CsLanguageModule.java new file mode 100644 index 0000000000..c24e2ffdbd --- /dev/null +++ b/pmd-cs/src/main/java/net/sourceforge/pmd/lang/cs/CsLanguageModule.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.cs; + +import net.sourceforge.pmd.lang.BaseLanguageModule; + +/** + * Language Module for C# + */ +public class CsLanguageModule extends BaseLanguageModule { + + /** The name. */ + public static final String NAME = "C#"; + /** The terse name. */ + public static final String TERSE_NAME = "cs"; + + /** + * Create a new instance of C# Language Module. + */ + public CsLanguageModule() { + super(NAME, null, TERSE_NAME, null, "cs"); + addVersion("", null, true); + } +} diff --git a/pmd-cs/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-cs/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..76459b4741 --- /dev/null +++ b/pmd-cs/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.cpd.CsLanguage diff --git a/pmd-cs/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language b/pmd-cs/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language new file mode 100644 index 0000000000..1b979f896f --- /dev/null +++ b/pmd-cs/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.lang.cs.CsLanguageModule diff --git a/pmd-cs/src/site/markdown/index.md b/pmd-cs/src/site/markdown/index.md new file mode 100644 index 0000000000..4a4bca4837 --- /dev/null +++ b/pmd-cs/src/site/markdown/index.md @@ -0,0 +1,3 @@ +# PMD C## + +Only CPD is supported. There are no PMD rules for C#. diff --git a/pmd-cs/src/site/site.xml b/pmd-cs/src/site/site.xml new file mode 100644 index 0000000000..e7be177e12 --- /dev/null +++ b/pmd-cs/src/site/site.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pmd-cs/src/test/java/net/sourceforge/pmd/cpd/CsTokenizerTest.java b/pmd-cs/src/test/java/net/sourceforge/pmd/cpd/CsTokenizerTest.java new file mode 100644 index 0000000000..9c1a9a9014 --- /dev/null +++ b/pmd-cs/src/test/java/net/sourceforge/pmd/cpd/CsTokenizerTest.java @@ -0,0 +1,165 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +public class CsTokenizerTest { + + private CsTokenizer tokenizer; + + private Tokens tokens; + + @Before + public void init() { + tokenizer = new CsTokenizer(); + tokens = new Tokens(); + TokenEntry.clearImages(); + } + + @Test + public void testSimpleClass() { + tokenizer.tokenize(toSourceCode("class Foo {}"), tokens); + assertEquals(5, tokens.size()); + } + + @Test + public void testSimpleClassDuplicatedTokens() { + tokenizer.tokenize(toSourceCode("class Foo { class Foo { } }"), tokens); + assertEquals(9, tokens.size()); + List tokenList = tokens.getTokens(); + assertEquals(tokenList.get(0).getIdentifier(), tokenList.get(3).getIdentifier()); + assertEquals(tokenList.get(1).getIdentifier(), tokenList.get(4).getIdentifier()); + assertEquals(tokenList.get(2).getIdentifier(), tokenList.get(5).getIdentifier()); + assertEquals(tokenList.get(6).getIdentifier(), tokenList.get(7).getIdentifier()); + } + + @Test + public void testSimpleClassMethodMultipleLines() { + tokenizer.tokenize(toSourceCode( + "class Foo {\n" + + " public String foo(int a) {\n" + + " int i = a;\n" + + " return \"x\" + a;\n" + + " }\n" + + "}"), tokens); + assertEquals(22, tokens.size()); + List tokenList = tokens.getTokens(); + assertEquals(1, tokenList.get(0).getBeginLine()); + assertEquals(2, tokenList.get(4).getBeginLine()); + assertEquals(3, tokenList.get(11).getBeginLine()); + } + + @Test + public void testStrings() { + tokenizer.tokenize(toSourceCode("String s =\"aaa \\\"b\\n\";"), tokens); + assertEquals(5, tokens.size()); + } + + @Test + public void testOpenString() { + tokenizer.tokenize(toSourceCode("String s =\"aaa \\\"b\\"), tokens); + assertEquals(5, tokens.size()); + } + + + @Test + public void testCommentsIgnored1() { + tokenizer.tokenize(toSourceCode("class Foo { /* class * ** X */ }"), tokens); + assertEquals(5, tokens.size()); + } + + @Test + public void testCommentsIgnored2() { + tokenizer.tokenize(toSourceCode("class Foo { // class X /* aaa */ \n }"), tokens); + assertEquals(5, tokens.size()); + } + + @Test + public void testCommentsIgnored3() { + tokenizer.tokenize(toSourceCode("class Foo { /// class X /* aaa */ \n }"), tokens); + assertEquals(5, tokens.size()); + } + + @Test + public void testMoreTokens() { + tokenizer.tokenize(toSourceCode( + "class Foo {\n" + + " void bar() {\n" + + " int a = 1 >> 2; \n" + + " a += 1; \n" + + " a++; \n" + + " a /= 3e2; \n" + + " float f = -3.1; \n" + + " f *= 2; \n" + + " bool b = ! (f == 2.0 || f >= 1.0 && f <= 2.0) \n" + + " }\n" + + "}" + ), tokens); + assertEquals(50, tokens.size()); + } + + @Test + public void testLineNumberAfterMultilineComment() { + tokenizer.tokenize(toSourceCode( + "/* This is a multiline comment \n" + + " * \n" + + " * Lorem ipsum dolor sit amet, \n" + + " * consectetur adipiscing elit \n" + + " */\n" + + "\n" + + "class Foo {\n" + + "\n" + + "}" + ), tokens); + assertEquals(5, tokens.size()); + assertEquals(7, tokens.getTokens().get(0).getBeginLine()); + } + + @Test + public void testLineNumberAfterMultilineString() { + tokenizer.tokenize(toSourceCode( + "class Foo {\n" + + " void bar() {\n" + + " String query = \n" + + " @\"SELECT foo, bar\n" + + " FROM table \n" + + " WHERE id = 42\"; \n" + + " }\n" + + "}" + ), tokens); + assertEquals(16, tokens.size()); + assertEquals(8, tokens.getTokens().get(14).getBeginLine()); + } + + @Test + public void testIgnoreUsingDirectives() { + tokenizer.setIgnoreUsings(true); + tokenizer.tokenize(toSourceCode("using System.Text;\n"), tokens); + assertNotEquals("using", tokens.getTokens().get(0).toString()); + assertEquals(2, tokens.size()); + } + + @Test + public void testUsingStatementsAreNotIgnored() { + tokenizer.setIgnoreUsings(true); + tokenizer.tokenize(toSourceCode( + "using (Font font1 = new Font(\"Arial\", 10.0f)) {\n" + + " byte charset = font1.GdiCharSet;\n" + + "}\n" + ), tokens); + assertEquals("using", tokens.getTokens().get(0).toString()); + } + + private SourceCode toSourceCode(String source) { + return new SourceCode(new SourceCode.StringCodeLoader(source)); + } +} diff --git a/pmd-fortran/pom.xml b/pmd-fortran/pom.xml new file mode 100644 index 0000000000..88368bd27e --- /dev/null +++ b/pmd-fortran/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + pmd-fortran + PMD Fortran + + + net.sourceforge.pmd + pmd + 5.5.0-SNAPSHOT + + + + ${basedir}/../pmd-core + + + + + + maven-resources-plugin + + false + + ${*} + + + + + + + + net.sourceforge.pmd + pmd-core + + + + net.sourceforge.pmd + pmd-test + test + + + diff --git a/pmd-fortran/src/main/java/net/sourceforge/pmd/cpd/FortranLanguage.java b/pmd-fortran/src/main/java/net/sourceforge/pmd/cpd/FortranLanguage.java new file mode 100644 index 0000000000..6e6faa0342 --- /dev/null +++ b/pmd-fortran/src/main/java/net/sourceforge/pmd/cpd/FortranLanguage.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +/** + * Language implementation for Fortran + * @author Romain PELISSE belaran@gmail.com + */ +public class FortranLanguage extends AbstractLanguage { + /** + * Create a Fotran Language instance. + */ + public FortranLanguage() { + super("Fortran", "fortran", new FortranTokenizer(), ".for", ".f", ".f66", ".f77", ".f90"); + } +} diff --git a/pmd-fortran/src/main/java/net/sourceforge/pmd/cpd/FortranTokenizer.java b/pmd-fortran/src/main/java/net/sourceforge/pmd/cpd/FortranTokenizer.java new file mode 100644 index 0000000000..8cae493bc2 --- /dev/null +++ b/pmd-fortran/src/main/java/net/sourceforge/pmd/cpd/FortranTokenizer.java @@ -0,0 +1,38 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.util.ArrayList; + +/** + * Tokenizer implementation for Fortran + * + * @author Romain PELISSE - romain.pelisse@atosorigin.com + */ +public class FortranTokenizer extends AbstractTokenizer implements Tokenizer { + + /** + * Creates a new instance of {@link FortranTokenizer}. + */ + public FortranTokenizer() { + this.spanMultipleLinesString = false; // No such thing in Fortran ! + // setting markers for "string" in Fortran + this.stringToken = new ArrayList(); + this.stringToken.add("\'"); + // setting markers for 'ignorable character' in Fortran + this.ignorableCharacter = new ArrayList(); + this.ignorableCharacter.add("("); + this.ignorableCharacter.add(")"); + this.ignorableCharacter.add(","); + + // setting markers for 'ignorable string' in Fortran + this.ignorableStmt = new ArrayList(); + this.ignorableStmt.add("do"); + this.ignorableStmt.add("while"); + this.ignorableStmt.add("end"); + this.ignorableStmt.add("if"); + // Fortran comment start with an ! + this.oneLineCommentChar = '!'; + } +} diff --git a/pmd-fortran/src/main/java/net/sourceforge/pmd/lang/fortran/FortranLanguageModule.java b/pmd-fortran/src/main/java/net/sourceforge/pmd/lang/fortran/FortranLanguageModule.java new file mode 100644 index 0000000000..b41d56c418 --- /dev/null +++ b/pmd-fortran/src/main/java/net/sourceforge/pmd/lang/fortran/FortranLanguageModule.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.fortran; + +import net.sourceforge.pmd.lang.BaseLanguageModule; + +/** + * Implementation for the Fortran Language Module + */ +public class FortranLanguageModule extends BaseLanguageModule { + + /** The name */ + public static final String NAME = "Fortran"; + /** The terse name */ + public static final String TERSE_NAME = "fortran"; + + /** + * Creates a new instance of {@link FortranLanguageModule} + */ + public FortranLanguageModule() { + super(NAME, null, TERSE_NAME, null, "for", "f", "f66", "f77", "f90"); + addVersion("", null, true); + } + +} diff --git a/pmd-fortran/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-fortran/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..f78952ce23 --- /dev/null +++ b/pmd-fortran/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.cpd.FortranLanguage diff --git a/pmd-fortran/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language b/pmd-fortran/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language new file mode 100644 index 0000000000..bf4133fb47 --- /dev/null +++ b/pmd-fortran/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.lang.fortran.FortranLanguageModule diff --git a/pmd-fortran/src/site/markdown/index.md b/pmd-fortran/src/site/markdown/index.md new file mode 100644 index 0000000000..808bc4fa7b --- /dev/null +++ b/pmd-fortran/src/site/markdown/index.md @@ -0,0 +1,3 @@ +# PMD Fortran + +Only CPD is supported. There are no PMD rules for Fortran. diff --git a/pmd-fortran/src/site/site.xml b/pmd-fortran/src/site/site.xml new file mode 100644 index 0000000000..156ddcb354 --- /dev/null +++ b/pmd-fortran/src/site/site.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pmd-fortran/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java b/pmd-fortran/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java new file mode 100644 index 0000000000..041ba4887e --- /dev/null +++ b/pmd-fortran/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java @@ -0,0 +1,28 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd; + +import java.util.Arrays; +import java.util.Collection; + +import net.sourceforge.pmd.AbstractLanguageVersionTest; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.fortran.FortranLanguageModule; + +import org.junit.runners.Parameterized.Parameters; + +public class LanguageVersionTest extends AbstractLanguageVersionTest { + + public LanguageVersionTest(String name, String terseName, String version, LanguageVersion expected) { + super(name, terseName, version, expected); + } + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + { FortranLanguageModule.NAME, FortranLanguageModule.TERSE_NAME, "", LanguageRegistry.getLanguage(FortranLanguageModule.NAME).getDefaultVersion() } + }); + } +} diff --git a/pmd-fortran/src/test/java/net/sourceforge/pmd/cpd/FortranTokenizerTest.java b/pmd-fortran/src/test/java/net/sourceforge/pmd/cpd/FortranTokenizerTest.java new file mode 100644 index 0000000000..03d9d3c74e --- /dev/null +++ b/pmd-fortran/src/test/java/net/sourceforge/pmd/cpd/FortranTokenizerTest.java @@ -0,0 +1,208 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.io.IOException; + +import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.testframework.AbstractTokenizerTest; + +import org.junit.Before; +import org.junit.Test; + + +/** + * @author rpelisse + * + */ +public class FortranTokenizerTest extends AbstractTokenizerTest { + + @Before + @Override + public void buildTokenizer() { + this.tokenizer = new FortranTokenizer(); + this.sourceCode = new SourceCode(new SourceCode.StringCodeLoader(this.getSampleCode(), "sample.for")); + } + + @Override + public String getSampleCode() { + return " options/extend_source" + PMD.EOL + + " program tp3" + PMD.EOL + + " implicit none" + PMD.EOL + + "" + PMD.EOL + + "! Ce programme va demander la saisie de la commande, puis on va separer les differentes" + PMD.EOL + + "!parties de la chaine en plusieurs variables, ensuite selon l'action demandee on appelera le" + PMD.EOL + + "!sous programme correspondant." + PMD.EOL + + "" + PMD.EOL + + " character*60 COMMANDE" + PMD.EOL + + " integer*4 IOS," + PMD.EOL + + " 1 COMPTEUR," + PMD.EOL + + " 1 SORTIE," + PMD.EOL + + " 1 ERRONE," + PMD.EOL + + " 1 CONF," + PMD.EOL + + " 1 POSITION_ESPACE," + PMD.EOL + + " 1 DEBUT_MOT," + PMD.EOL + + " 1 FIN_MOT," + PMD.EOL + + " 1 NB_MOTS," + PMD.EOL + + " 1 NB_MOTS_MAX," + PMD.EOL + + " 1 FIN_CHAINE," + PMD.EOL + + " 1 TROUVER_FIN," + PMD.EOL + + " 1 NUM_CARACTERE," + PMD.EOL + + " 1 ACTION," + PMD.EOL + + " 1 PREMIERE_LETTRE," + PMD.EOL + + " 1 DERNIERE_LETTRE," + PMD.EOL + + " 1 INTERVALLE_MAJ_MIN," + PMD.EOL + + " 1 APRES_MAJ," + PMD.EOL + + " 1 TAILLE_COLONNE," + PMD.EOL + + " 1 TAILLE_LIGNE," + PMD.EOL + + " 1 LIGNES_DESC" + PMD.EOL + + "" + PMD.EOL + + " parameter(NB_MOTS_MAX = 9) !une saisie correcte ne contient pas plus de 8 mots, si" + PMD.EOL + + "!elle en contient 9, alors la saisie sera jugee incorrecte." + PMD.EOL + + " parameter(ERRONE = 1)" + PMD.EOL + + " parameter(SORTIE = - 1)" + PMD.EOL + + " parameter(ACTION = 1) !il s'agit du 1er mot de la chaine de caracteres" + PMD.EOL + + " parameter(PREMIERE_LETTRE = 1) !correspond a la 1ere lettre d'un mot" + PMD.EOL + + " parameter(DERNIERE_LETTRE = 18) !correspond a la derniere lettre d'un mot" + PMD.EOL + + " parameter(INTERVALLE_MAJ_MIN = 32) !nombre separant un meme caractere" + PMD.EOL + + "!minuscule de son majuscule" + PMD.EOL + + " parameter(APRES_MAJ = 96) !correspond au dernier caractere avant les MIN" + PMD.EOL + + " parameter(TAILLE_COLONNE = 7)" + PMD.EOL + + " parameter(TAILLE_LIGNE = 12)" + PMD.EOL + + " parameter(LIGNES_DESC = 11)" + PMD.EOL + + "" + PMD.EOL + + " character*19 N(TAILLE_COLONNE,TAILLE_LIGNE)" + PMD.EOL + + " character*19 MOTS_COMMANDE(NB_MOTS_MAX)" + PMD.EOL + + " character*60 DESC(LIGNES_DESC)" + PMD.EOL + + "" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) ' -----------------------------------------------------'" + PMD.EOL + + " write(*,*) ' | Bonjour, et bienvenue dans le programme DASHBOARD |'" + PMD.EOL + + " write(*,*) ' -----------------------------------------------------'" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) ' Voici un rappel des fonctions disponibles pour ce DASHBOARD : '" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) ' _ TASK pour creer une tache (ex : TASK IDTACHE CIBLE AUTEUR)'" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) ' _ SHOW pour voir la description (ex : SHOW IDTACHE)'" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) ' _ REMOVE pour enlever une tache (ex : REMOVE IDTACHE)'" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) ' _ CLEAR pour effacer le DASHBOARD (ex : CLEAR)'" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) ' _ CANCEL, DONE, TODO pour modifier lHEREetat de la tache (ex : DONE IDTACHE)'" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + "" + PMD.EOL + + "! La boucle de sortie pour quitter si l'on appuie sur F10" + PMD.EOL + + " do while (IOS .ne. SORTIE)" + PMD.EOL + + "" + PMD.EOL + + "! Initialisons les variables, afin de ne pas garder les anciennes valeurs pour chaque variable." + PMD.EOL + + " POSITION_ESPACE = 0" + PMD.EOL + + " DEBUT_MOT = 0" + PMD.EOL + + " FIN_MOT = 0" + PMD.EOL + + " NB_MOTS = 0" + PMD.EOL + + " FIN_CHAINE = 0" + PMD.EOL + + "" + PMD.EOL + + "! Initialisons aussi le tableau des MOTS_COMMANDE" + PMD.EOL + + " do COMPTEUR = ACTION, NB_MOTS_MAX" + PMD.EOL + + " MOTS_COMMANDE (COMPTEUR) = ' '" + PMD.EOL + + " end do" + PMD.EOL + + "" + PMD.EOL + + "! Appelons le sous prgramme qui gere la saisie de la commande et aussi la sortie, si " + PMD.EOL + + "!l'utilisateur le demande" + PMD.EOL + + " call SAISIE(COMMANDE, IOS)" + PMD.EOL + + "" + PMD.EOL + + " if (IOS .eq. 0) then" + PMD.EOL + + "" + PMD.EOL + + "! Trouvons la fin de la chaine" + PMD.EOL + + " FIN_CHAINE = TROUVER_FIN (COMMANDE)" + PMD.EOL + + " COMPTEUR = 1" + PMD.EOL + + " do while (POSITION_ESPACE .lt. FIN_CHAINE .and. NB_MOTS .lt. NB_MOTS_MAX)" + PMD.EOL + + " DEBUT_MOT = POSITION_ESPACE + 1" + PMD.EOL + + "" + PMD.EOL + + "! Decoupons les mots" + PMD.EOL + + " POSITION_ESPACE = POSITION_ESPACE + index (COMMANDE (DEBUT_MOT:), ' ')" + PMD.EOL + + " FIN_MOT = POSITION_ESPACE - 1" + PMD.EOL + + "" + PMD.EOL + + "! Ensuite on les enregistre dans MOTS_COMMANDE" + PMD.EOL + + " MOTS_COMMANDE (COMPTEUR) = COMMANDE (DEBUT_MOT : FIN_MOT)" + PMD.EOL + + "" + PMD.EOL + + "! Comptons les mots" + PMD.EOL + + " if (MOTS_COMMANDE (COMPTEUR) .ne. ' ') then" + PMD.EOL + + " NB_MOTS = NB_MOTS + 1" + PMD.EOL + + " COMPTEUR = COMPTEUR + 1" + PMD.EOL + + " end if" + PMD.EOL + + " end do" + PMD.EOL + + "" + PMD.EOL + + "! Le programme ne doit pas tenir compte de la casse, ainsi peu importe la maniere" + PMD.EOL + + "!dont est ecrit le mot, il sera mis en majuscule" + PMD.EOL + + " do COMPTEUR = 1, NB_MOTS" + PMD.EOL + + " do NUM_CARACTERE = PREMIERE_LETTRE, DERNIERE_LETTRE" + PMD.EOL + + " if (ichar(MOTS_COMMANDE (COMPTEUR)(NUM_CARACTERE:NUM_CARACTERE))" + PMD.EOL + + " 1 .gt. APRES_MAJ) then" + PMD.EOL + + " MOTS_COMMANDE (COMPTEUR)(NUM_CARACTERE:NUM_CARACTERE) =" + PMD.EOL + + " 1 char(ichar(MOTS_COMMANDE (COMPTEUR)(NUM_CARACTERE:NUM_CARACTERE)) - INTERVALLE_MAJ_MIN)" + PMD.EOL + + " end if" + PMD.EOL + + " end do" + PMD.EOL + + " end do" + PMD.EOL + + "" + PMD.EOL + + "!! Affichons les mots (provisoire)" + PMD.EOL + + "!! do COMPTEUR = 1, NB_MOTS" + PMD.EOL + + "!! write(*,*) COMPTEUR, ': ', MOTS_COMMANDE (COMPTEUR)" + PMD.EOL + + "!! end do" + PMD.EOL + + "!!" + PMD.EOL + + "!! Testons si le mot est bien en majuscule (etape provisoire)" + PMD.EOL + + "!! write(*,*) MOTS_COMMANDE (ACTION), ': voila lHEREaction'" + PMD.EOL + + "" + PMD.EOL + + "" + PMD.EOL + + "! Si la commande contient plus de 8 mots, on demande de recommencer" + PMD.EOL + + "" + PMD.EOL + + " if (NB_MOTS .eq. NB_MOTS_MAX) then" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) 'ERR> Trop de mot, veuillez ressaisir'" + PMD.EOL + + " else" + PMD.EOL + + "" + PMD.EOL + + "! Maintenant, en fonction du premier mot entre, on va appeler le sous programme correspondant" + PMD.EOL + + " if (MOTS_COMMANDE (ACTION) .eq. 'TASK') then" + PMD.EOL + + " call TACHE(MOTS_COMMANDE, DESC, N)" + PMD.EOL + + " else if (MOTS_COMMANDE (ACTION) .eq. 'SHOW') then" + PMD.EOL + + "! write(*,*) 'on appelle le sous prgrm SHOW'" + PMD.EOL + + " call SHOW(MOTS_COMMANDE, N)" + PMD.EOL + + " else if (MOTS_COMMANDE (ACTION) .eq. 'REMOVE') then" + PMD.EOL + + "! write(*,*) 'on appelle le sous prgrm REMOVE'" + PMD.EOL + + " call REMOVE(MOTS_COMMANDE, DESC, N)" + PMD.EOL + + " else if (MOTS_COMMANDE (ACTION) .eq. 'CLEAR') then" + PMD.EOL + + "! write(*,*) 'on appelle le sous prgrm CLEAR'" + PMD.EOL + + " call CLEAR(MOTS_COMMANDE, N)" + PMD.EOL + + " else if (MOTS_COMMANDE (ACTION) .eq. 'CANCEL') then" + PMD.EOL + + "! write(*,*) 'on appelle le sous prgrm CANCEL'" + PMD.EOL + + " call CANCEL(MOTS_COMMANDE, N)" + PMD.EOL + + " else if (MOTS_COMMANDE (ACTION) .eq. 'DONE') then" + PMD.EOL + + "! write(*,*) 'on appelle le sous prgrm DONE'" + PMD.EOL + + " call DONE(MOTS_COMMANDE, N)" + PMD.EOL + + " else if (MOTS_COMMANDE (ACTION) .eq. 'TODO') then" + PMD.EOL + + "! write(*,*) 'on appelle le sous prgrm TODO'" + PMD.EOL + + " call TODO(MOTS_COMMANDE, N)" + PMD.EOL + + " else" + PMD.EOL + + " write(*,*) ' '" + PMD.EOL + + " write(*,*) 'L''action suivante n''a pas ete'," + PMD.EOL + + " 1 ' comprise: ', MOTS_COMMANDE (ACTION)" + PMD.EOL + + " end if" + PMD.EOL + + " end if" + PMD.EOL + + " end if" + PMD.EOL + + " end do" + PMD.EOL + + " end" + PMD.EOL; + } + + @Test + public void tokenizeTest() throws IOException { + this.expectedTokenCount = 434; + super.tokenizeTest(); + } + + public static junit.framework.Test suite() { + return new junit.framework.JUnit4TestAdapter(FortranTokenizerTest.class); + } +} diff --git a/pmd-go/pom.xml b/pmd-go/pom.xml new file mode 100644 index 0000000000..279266e39b --- /dev/null +++ b/pmd-go/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + pmd-go + PMD Go + + + net.sourceforge.pmd + pmd + 5.5.0-SNAPSHOT + + + + ${basedir}/../pmd-core + + + + + + maven-resources-plugin + + false + + ${*} + + + + + + + + net.sourceforge.pmd + pmd-core + + + + net.sourceforge.pmd + pmd-test + test + + + diff --git a/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoLanguage.java b/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoLanguage.java new file mode 100644 index 0000000000..7b8e535db6 --- /dev/null +++ b/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoLanguage.java @@ -0,0 +1,19 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +/** + * Implements the Go Language + * + * @author oinume@gmail.com + */ +public class GoLanguage extends AbstractLanguage { + + /** + * Creates a new instance of {@link GoLanguage} + */ + public GoLanguage() { + super("Go", "go", new GoTokenizer(), ".go"); + } +} diff --git a/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoTokenizer.java b/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoTokenizer.java new file mode 100644 index 0000000000..89aea50694 --- /dev/null +++ b/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoTokenizer.java @@ -0,0 +1,31 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.util.ArrayList; + +/** + * Implements a tokenizer for the Go Language. + * + * @author oinume@gmail.com + */ +public class GoTokenizer extends AbstractTokenizer { + + /** + * Creates a new {@link GoTokenizer} + */ + public GoTokenizer() { + // setting markers for "string" in Go + this.stringToken = new ArrayList(); + this.stringToken.add("\""); + this.stringToken.add("`"); + + // setting markers for 'ignorable character' in Go + this.ignorableCharacter = new ArrayList(); + this.ignorableCharacter.add(";"); + + // setting markers for 'ignorable string' in Go + this.ignorableStmt = new ArrayList(); + } +} diff --git a/pmd-go/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-go/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..7d97e302eb --- /dev/null +++ b/pmd-go/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.cpd.GoLanguage diff --git a/pmd-go/src/site/markdown/index.md b/pmd-go/src/site/markdown/index.md new file mode 100644 index 0000000000..15872a4ff7 --- /dev/null +++ b/pmd-go/src/site/markdown/index.md @@ -0,0 +1,3 @@ +# PMD Go + +Only CPD is supported. There are no PMD rules for [Go](https://golang.org/). diff --git a/pmd-go/src/site/site.xml b/pmd-go/src/site/site.xml new file mode 100644 index 0000000000..80bb338f06 --- /dev/null +++ b/pmd-go/src/site/site.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pmd-jsp/etc/grammar/JspParser.jjt b/pmd-jsp/etc/grammar/JspParser.jjt new file mode 100644 index 0000000000..d4022677d4 --- /dev/null +++ b/pmd-jsp/etc/grammar/JspParser.jjt @@ -0,0 +1,728 @@ +/* + * Added capability for Tracking Tokens. + * + * Amit Kumar Prasad 10/2015 + *==================================================================== + * JSP Parser for PMD. + * It supports supports more-or-less well written JSP files. + * The JSP Document style is supported, except for inline DTD. + * The JSP Page style (<% ... %>) is supported. + * Java code is not parsed. + * Script code inside is not parsed. + */ + +options { + USER_CHAR_STREAM = true; + NODE_USES_PARSER=true; + UNICODE_INPUT=true; + FORCE_LA_CHECK = false; + IGNORE_CASE = true; + STATIC = false; + + MULTI=true; + VISITOR=true; + TRACK_TOKENS = true; +} + +PARSER_BEGIN(JspParser) +package net.sourceforge.pmd.lang.jsp.ast; + +import net.sourceforge.pmd.lang.ast.CharStream; +import net.sourceforge.pmd.lang.ast.TokenMgrError; + +/** + * JSP Parser for PMD. + * @author Pieter, Application Engineers NV/SA, http://www.ae.be + */ +public class JspParser { + + + /** + * Counter used to keep track of unclosed tags + */ + private OpenTagRegister tagRegister = new OpenTagRegister(); + + /** + * Return the contents of a quote. + * @param quote String - starting and ending with " or ' + * @return String a substring of quote: quote without the first and list + * character. + */ + private static String quoteContent(String quote) { + return quote.substring(1, quote.length()-1); + } + + /** + * Return the contents of a EL expression or a Value Binding expression. + * @param expression String - starting with ${ or #{ and ending with } + * @return String a substring of expression: expression without the first two and list + * characters. + */ + private static String expressionContent(String expression) { + return expression.substring(2, expression.length()-1).trim(); + } +} + +PARSER_END(JspParser) + + +/** ******************************************************************** */ +/** ************************* JSP LEXICON **************************** */ +/** ******************************************************************** */ + + +/* This JavaCC lexicon has the following states: + * - StartTagState : this is the state entered after the "<" of a tag, until a + * non-whitespace is found. + * This is only for tags, not for xml-comments, declarations, etc. + * - AfterTagState : this is the state entered after the closing ">" of a tag, + * or xml-comment or declaration, until some non-whitespace is found + * - CommentState : the state between "" + * - DeclarationState : the state between "" + * - CDataState : the state between "" + * - InTagState : the state when inside a tag + * - AttrValueStatue : the state when starting an attribute value, before the starting single or double quote + * - DocTypeState : the state when inside a doctype declaration + * - ElExpressionState : the state when inside a ElExpression + * - DocTypeState : inside a document type declaration + * - DocTypeExternalIdState : inside an "external id" part of a dtd + * - AttrValueBetweenSingleQuotesState : inside an attribute that is surrounded by single quotes (') + * - AttrValueBetweenDoubleQuotesState : inside an attribute that is surrounded by double quotes (") + * - JspDirectiveState : inside a JSP directive not yet reaching the attributes of the directive + * - JspDirectiveAttributesState : inside the attributes part of a directive + * - JspScriptletState : inside a scriptlet <% ... %> + * - JspExpressionState : inside an expression <%= ... %> + * - JspDeclarationState : inside a declaration <%! ... %> + * - JspCommentState : inside a comment <%-- ... --%> + * - HtmlScriptContentState : inside an HTML script + */ + + +<*> TOKEN : +{ + <#ALPHA_CHAR: [ + "\u0024", + "\u0041"-"\u005a", + "\u005f", + "\u0061"-"\u007a", + "\u00c0"-"\u00d6", + "\u00d8"-"\u00f6", + "\u00f8"-"\u00ff" + ] > +| <#NUM_CHAR: [ + "\u0030"-"\u0039" + ] > +| <#ALPHANUM_CHAR: ( | ) > +| <#IDENTIFIER_CHAR: ( | [ "_", "-", ".", ":" ] ) > +| <#IDENTIFIER: ()* > +| <#XMLNAME: ( | "_" | ":") ()* > +| <#QUOTED_STRING_NO_BREAKS: ( "'" ( ~["'", "\r", "\n"] )* "'" ) + | ( "\"" ( ~["\"", "\r", "\n"] )* "\"" ) > +| <#QUOTED_STRING: ( "'" ( ~["'"] )* "'" ) | ( "\"" ( ~["\""] )* "\"" ) > +| <#WHITESPACE: ( " " | "\t" | "\n" | "\r" ) > +| <#NEWLINE: ( "\r\n" | "\r" | "\n" ) > +| <#QUOTE: ( "'" | "\"" )> +| <#NO_WHITESPACE_OR_LT_OR_DOLLAR: (~[" ", "\t", "\n", "\r", "<", "$", "#"])> +| <#DOLLAR_OR_HASH: ("$" | "#")> +| <#NO_OPENBRACE: (~["{"]) > +| <#NO_LT_OR_DOLLAR_OR_HASH: (~["<","$","#"])> +| <#NO_ENDTAG_START: (~["<"]~["/"]) > +| <#TEXT_IN_EL: (~["}", "'", "\""])+ > +| <#EL_ESCAPE: ("\\${" | "\\#{") > + + // anything but --%> +| <#NO_JSP_COMMENT_END: (~["-"] | "-" ~["-"] | "--" ~["%"] | "--%" ~[">"])+ > +| <#NO_JSP_TAG_END: ( ~["%"] | ("%" ~[">"]) )+ > +} + + + SKIP : +{ + < ()+ > +} + + SPECIAL_TOKEN : +{ + < ()+ > +} + + TOKEN : +{ + : StartTagState +| : StartTagState +| : CommentState +| : StartTagState +| : DocTypeState +| : CDataState +| : JspCommentState +| : JspDeclarationState +| : JspScriptletState +| : JspDirectiveState +| : InTagState +} + + TOKEN : +{ + | )* "}") + | + ("#{" ( | )* "}") + > +| | + | + )+ > +} + + TOKEN : +{ + > : JspDirectiveAttributesState +} + + TOKEN : +{ + > +| " > : AfterTagState +} + + TOKEN : +{ + " > : AfterTagState +| > +} + + TOKEN : +{ + " > : AfterTagState +| > +} + + TOKEN : +{ + " > : AfterTagState +| > +} + + TOKEN : +{ + " > : AfterTagState +| > +} + + TOKEN : +{ + )+ > +} + + TOKEN: +{ + ) > : DocTypeExternalIdState +} + + TOKEN: +{ + +| +| " > : AfterTagState +| ) > +} + + + TOKEN : +{ + + | ") > : AfterTagState +} + + TOKEN : +{ + > : InTagState +| : DEFAULT +} + + TOKEN : +{ + > +| " > : AfterTagState +| " | "!>") > : AfterTagState +| " > : AfterTagState +| : AttrValueBetweenSingleQuotesState +| : AttrValueBetweenDoubleQuotesState +| { input_stream.backup(1);} : AttrValueNoQuotesState +| : InTagState //support for empty attributes +} + + TOKEN: +{ + | )* "}" > +| | )* "}" > +| " > +} + + TOKEN : +{ + : InTagState +| )+ > + +} + + TOKEN : +{ + : InTagState +| )+ > +| : InTagState +} + + TOKEN : +{ + : InTagState +| )+ > +| : InTagState +} + + TOKEN : +{ + < COMMENT_END: ("--" (" ")* ">" | "->") > : AfterTagState +| < COMMENT_TEXT: (~[]) > +} + + TOKEN : +{ + + | : AfterTagState +} + +/** ******************************************************************** */ +/** ************************* JSP GRAMMAR **************************** */ +/** ******************************************************************** */ + +/** + * The root of the AST of a JSP. + */ +ASTCompilationUnit CompilationUnit() : +{} +{ + Prolog() + + Content() + { return jjtThis; } +} + +/** + * The optional prolog of a JSP, including (xml) declarations and DTD. + */ +void Prolog() #void : +{} +{ + ( + LOOKAHEAD( ( CommentTag() | JspComment() )* Declaration() ) + ( CommentTag() | JspComment() )* + Declaration() + )? + + ( + LOOKAHEAD( ( CommentTag() | JspComment() )* DoctypeDeclaration() ) + ( CommentTag() | JspComment() )* + DoctypeDeclaration() + )? +} + +/** + * Everything between a start-tag and the corresponding end-tag of an element (if an end tag exists). + */ +void Content() : +{} +{ + ( Text() | ContentElement() )* +} + +/** + * A single (non-text) element that can occur between a start-tag and end-tag of an element. + * + */ +void ContentElement() #void : +{} +{ + ( + CommentTag() + | Element() + | CData() + | JspComment() + | JspDeclaration() + | JspExpression() + | JspScriptlet() + | JspDirective() + | HtmlScript() + ) +} + +void JspDirective() : +{ Token t; } +{ + + t = { jjtThis.setName(t.image); } + + ( + JspDirectiveAttribute() + )* + +} + +void JspDirectiveAttribute() : +{ Token t; } +{ + t = { jjtThis.setName(t.image); } + + t = { jjtThis.setValue(quoteContent(t.image)); } +} + +void JspScriptlet() : +{ Token t; } +{ + + t = { jjtThis.setImage(t.image.trim()); } + +} + +void JspExpression() : +{ Token t; } +{ + + t = { jjtThis.setImage(t.image.trim()); } + +} + +void JspDeclaration() : +{ Token t; } +{ + + t = { jjtThis.setImage(t.image.trim()); } + +} + +void JspComment() : +{ Token t; } +{ + + t = { jjtThis.setImage(t.image.trim()); } + +} + +/** + * This production groups all characters between two tags, where + * tag is an xml-tag "<...>" or a jsp-page-tag "<%...%>" or CDATA "". + * Text consists of unparsed text and/or Expression Language expressions. + */ +void Text() : +{ + StringBuffer content = new StringBuffer(); + String tmp; +} +{ + ( + tmp = UnparsedText() { content.append(tmp); } + | tmp = ElExpression() { content.append(tmp); } + )+ + {jjtThis.setImage(content.toString());} + +} + +String UnparsedText() : +{ Token t; } +{ + t = + { + jjtThis.setImage(t.image); + return t.image; + } +} + +String UnparsedTextNoWhitespace() #UnparsedText : +{ Token t;} +{ + ( + t = + ) + { + jjtThis.setImage(t.image); + return t.image; + } +} + + + +/** + * Text that contains no single quotes, and that does not contain the start + * of a EL expression or value binding. + */ +String UnparsedTextNoSingleQuotes() #UnparsedText : +{ Token t; } +{ + t = + { + jjtThis.setImage(t.image); + return t.image; + } +} + +/** + * Text that contains no double quotes, and that does not contain the start + * of a EL expression or value binding. + */ +String UnparsedTextNoDoubleQuotes() #UnparsedText : +{ Token t; } +{ + t = + { + jjtThis.setImage(t.image); + return t.image; + } +} + +/** + * An EL expression, not within an attribute value. + */ +String ElExpression() : +{ Token t; } +{ + t = + { + jjtThis.setImage(expressionContent(t.image)); + return t.image; + } +} + +String ValueBindingInAttribute() #ValueBinding : +{ Token t; } +{ + t = + { + jjtThis.setImage(expressionContent(t.image)); + return t.image; + } +} + +String ElExpressionInAttribute() #ElExpression : +{ Token t; } +{ + t = + { + jjtThis.setImage(expressionContent(t.image)); + return t.image; + } +} + +void CData() : +{ + StringBuffer content = new StringBuffer(); + Token t; +} +{ + ( t = { content.append(t.image); } )* + { + jjtThis.setImage(content.toString()); + } +} + +/** + * A XML element, either with a single empty tag, or with a starting and closing tag + * with optional contained content. + */ +void Element() : +{ + Token startTag; + Token endTag; + String tagName; +} +{ + ( + ( + + startTag = { tagName = startTag.image; + jjtThis.setName(tagName); + tagRegister.openTag(jjtThis); + } + ) + (Attribute())* + ( + ( + { jjtThis.setEmpty(false);} + + (Content()) + + ( + endTag = {tagRegister.closeTag(endTag.image);} + )? + ) + | + ( { jjtThis.setEmpty(true); + jjtThis.setUnclosed(false); + } + ) + ) + ) +} + +void Attribute() : +{ Token t; } +{ + t = { jjtThis.setName(t.image); } + ( + + AttributeValue() + ) +} + +/** + * The value of an attribute of an element. + * EL expressions, JSF value bindings, and JSP expressions + * are parsed as sub-nodes of the AttributeValue node. + */ +void AttributeValue() : +{ + StringBuffer content = new StringBuffer(); + String tmp; + Token t = null ; +} +{ + ( + ( + ( ( tmp = UnparsedTextNoDoubleQuotes() + | tmp = QuoteIndependentAttributeValueContent() + ) { content.append(tmp); } )* + ( + | t = { content.append(t.image.substring(0, 1)); } + ) + ) + | + ( + ( ( tmp = UnparsedTextNoSingleQuotes() | tmp = QuoteIndependentAttributeValueContent() ) + { content.append(tmp); } )* + ( + | t = { content.append(t.image.substring(0, 1)); } + ) + ) + | + ( + ( ( tmp = UnparsedTextNoWhitespace() | tmp = QuoteIndependentAttributeValueContent() ) + { content.append(tmp); } + )* + ( ) + ) + | + ) + { jjtThis.setImage( content.toString() ); + } +} + +/** + * Partial content of an attribute value that can contain all quotes. + * This groups EL expressions, value bindings, and JSP expressions. + */ +String QuoteIndependentAttributeValueContent() #void : +{ String tmp; } +{ + ( tmp = ElExpressionInAttribute() + | tmp = ValueBindingInAttribute() + | tmp = JspExpressionInAttribute() + ) + { return tmp; } +} + +String JspExpressionInAttribute() : +{ Token t; } +{ + t = + { + jjtThis.setImage(t.image.substring(3, t.image.length()-2).trim()); // without <% and %> + return t.image; + } +} + +void CommentTag() : +{ + StringBuffer content = new StringBuffer(); + Token t; +} +{ + + ( t = { content.append(t.image); } )* + + { + jjtThis.setImage(content.toString().trim()); + } +} + +void Declaration() : +{ Token t; } +{ + + t = { jjtThis.setName(t.image); } + (Attribute())* + +} + +void DoctypeDeclaration() : +{ Token t; } +{ + + + t = { jjtThis.setName(t.image); } + ()? + (DoctypeExternalId() ()?)? + +} + +void DoctypeExternalId() : +{ + Token systemLiteral; + Token pubIdLiteral; +} +{ + ( + + systemLiteral = + { jjtThis.setUri(quoteContent(systemLiteral.image)); } + ) + | + ( + + pubIdLiteral = + { jjtThis.setPublicId(quoteContent(pubIdLiteral.image)); } + + systemLiteral = + { jjtThis.setUri(quoteContent(systemLiteral.image)); } + ) +} + +void HtmlScript() : +{ + StringBuffer content = new StringBuffer(); + String tagName; + Token t; +} +{ + {} + (Attribute() )* {} + ( + ( + {token_source.SwitchTo(HtmlScriptContentState);} + (t = { content.append(t.image); })* + { jjtThis.setImage(content.toString().trim());} + ) + | + ( + + ) + ) +} diff --git a/pmd-jsp/pom.xml b/pmd-jsp/pom.xml new file mode 100644 index 0000000000..f419692d9a --- /dev/null +++ b/pmd-jsp/pom.xml @@ -0,0 +1,115 @@ + + + 4.0.0 + pmd-jsp + PMD JSP + + + net.sourceforge.pmd + pmd + 5.5.0-SNAPSHOT + + + + ${basedir}/../pmd-core + + + + + + ${basedir}/src/main/resources + true + + + + + maven-resources-plugin + + false + + ${*} + + + + + + org.apache.maven.plugins + maven-antrun-plugin + true + + + generate-sources + generate-sources + + + + + + + + + + run + + + + pmd-clean + clean + + + PMD specific tasks: cleaning generated markdown + + + + + + + + + run + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-javacc-generated-sources + + add-source + + + + ${project.build.directory}/generated-sources/javacc + + + + + + + + net.sourceforge.pmd + pmd-build + + + + + + net.java.dev.javacc + javacc + + + net.sourceforge.pmd + pmd-core + + + + net.sourceforge.pmd + pmd-test + test + + + diff --git a/pmd-jsp/src/main/ant/alljavacc.xml b/pmd-jsp/src/main/ant/alljavacc.xml new file mode 100644 index 0000000000..da338412b9 --- /dev/null +++ b/pmd-jsp/src/main/ant/alljavacc.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + public class + + + + public class Token + + + + + + + + + + + diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java new file mode 100644 index 0000000000..65ed20b772 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java @@ -0,0 +1,10 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +public class JSPLanguage extends AbstractLanguage { + public JSPLanguage() { + super("JSP", "jsp", new JSPTokenizer(), ".jsp", ".jspx"); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java new file mode 100644 index 0000000000..f4301169f8 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java @@ -0,0 +1,43 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.io.Reader; +import java.io.StringReader; + +import org.apache.commons.io.IOUtils; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.TokenManager; +import net.sourceforge.pmd.lang.jsp.JspLanguageModule; +import net.sourceforge.pmd.lang.jsp.ast.Token; +import net.sourceforge.pmd.util.IOUtil; + +public class JSPTokenizer implements Tokenizer { + + public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { + StringBuilder buffer = sourceCode.getCodeBuffer(); + LanguageVersionHandler languageVersionHandler = + LanguageRegistry.getLanguage(JspLanguageModule.NAME).getDefaultVersion().getLanguageVersionHandler(); + Reader reader = null; + + try { + reader = new StringReader(buffer.toString()); + reader = IOUtil.skipBOM(reader); + TokenManager tokenMgr = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()) + .getTokenManager(sourceCode.getFileName(), reader); + Token currentToken = (Token) tokenMgr.getNextToken(); + + while (currentToken.image.length() > 0) { + tokenEntries.add(new TokenEntry(String.valueOf(currentToken.kind), sourceCode.getFileName(), + currentToken.beginLine)); + currentToken = (Token) tokenMgr.getNextToken(); + } + } finally { + IOUtils.closeQuietly(reader); + } + tokenEntries.add(TokenEntry.getEOF()); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java new file mode 100644 index 0000000000..df2434f560 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java @@ -0,0 +1,55 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp; + +import java.io.Writer; + +import net.sf.saxon.sxpath.IndependentContext; +import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler; +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.ParserOptions; +import net.sourceforge.pmd.lang.VisitorStarter; +import net.sourceforge.pmd.lang.XPathHandler; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.AbstractASTXPathHandler; +import net.sourceforge.pmd.lang.jsp.ast.DumpFacade; +import net.sourceforge.pmd.lang.jsp.ast.JspNode; +import net.sourceforge.pmd.lang.jsp.rule.JspRuleViolationFactory; +import net.sourceforge.pmd.lang.rule.RuleViolationFactory; + +/** + * Implementation of LanguageVersionHandler for the JSP parser. + * + * @author pieter_van_raemdonck - Application Engineers NV/SA - www.ae.be + */ +public class JspHandler extends AbstractLanguageVersionHandler { + + @Override + public XPathHandler getXPathHandler() { + return new AbstractASTXPathHandler() { + public void initialize() { + } + + public void initialize(IndependentContext context) { + } + }; + } + + public RuleViolationFactory getRuleViolationFactory() { + return JspRuleViolationFactory.INSTANCE; + } + + public Parser getParser(ParserOptions parserOptions) { + return new JspParser(parserOptions); + } + + @Override + public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { + return new VisitorStarter() { + public void start(Node rootNode) { + new DumpFacade().initializeWith(writer, prefix, recurse, (JspNode) rootNode); + } + }; + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java new file mode 100644 index 0000000000..2f974d73d2 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java @@ -0,0 +1,19 @@ +package net.sourceforge.pmd.lang.jsp; + +import net.sourceforge.pmd.lang.BaseLanguageModule; +import net.sourceforge.pmd.lang.jsp.rule.JspRuleChainVisitor; + +/** + * Created by christoferdutz on 20.09.14. + */ +public class JspLanguageModule extends BaseLanguageModule { + + public static final String NAME = "Java Server Pages"; + public static final String TERSE_NAME = "jsp"; + + public JspLanguageModule() { + super(NAME, "JSP", TERSE_NAME, JspRuleChainVisitor.class, "jsp"); + addVersion("", new JspHandler(), true); + } + +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspParser.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspParser.java new file mode 100644 index 0000000000..9892c95e53 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspParser.java @@ -0,0 +1,44 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp; + +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; + +import net.sourceforge.pmd.lang.AbstractParser; +import net.sourceforge.pmd.lang.ParserOptions; +import net.sourceforge.pmd.lang.TokenManager; +import net.sourceforge.pmd.lang.ast.AbstractTokenManager; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.ParseException; +import net.sourceforge.pmd.lang.ast.SimpleCharStream; + +/** + * Adapter for the JspParser. + */ +public class JspParser extends AbstractParser { + + public JspParser(ParserOptions parserOptions) { + super(parserOptions); + } + + @Override + public TokenManager createTokenManager(Reader source) { + return new JspTokenManager(source); + } + + public boolean canParse() { + return true; + } + + public Node parse(String fileName, Reader source) throws ParseException { + AbstractTokenManager.setFileName(fileName); + return new net.sourceforge.pmd.lang.jsp.ast.JspParser(new SimpleCharStream(source)).CompilationUnit(); + } + + public Map getSuppressMap() { + return new HashMap<>(); // FIXME + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspTokenManager.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspTokenManager.java new file mode 100644 index 0000000000..d62c75ae00 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspTokenManager.java @@ -0,0 +1,29 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp; + +import java.io.Reader; + +import net.sourceforge.pmd.lang.TokenManager; +import net.sourceforge.pmd.lang.ast.JavaCharStream; +import net.sourceforge.pmd.lang.jsp.ast.JspParserTokenManager; + +/** + * JSP Token Manager implementation. + */ +public class JspTokenManager implements TokenManager { + private final JspParserTokenManager tokenManager; + + public JspTokenManager(Reader source) { + tokenManager = new JspParserTokenManager(new JavaCharStream(source)); + } + + public Object getNextToken() { + return tokenManager.getNextToken(); + } + + public void setFileName(String fileName) { + JspParserTokenManager.setFileName(fileName); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttribute.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttribute.java new file mode 100644 index 0000000000..807ed503a9 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttribute.java @@ -0,0 +1,73 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTAttribute.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTAttribute extends AbstractJspNode { + /* BEGIN CUSTOM CODE */ + private String name; + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } + + + /** + * @return boolean - true if the element has a namespace-prefix, false otherwise + */ + public boolean isHasNamespacePrefix() { + return name.indexOf(':') >= 0; + } + + /** + * @return String - the part of the name that is before the (first) colon (":") + */ + public String getNamespacePrefix() { + int colonIndex = name.indexOf(':'); + return colonIndex >= 0 + ? name.substring(0, colonIndex) + : ""; + } + + /** + * @return String - The part of the name that is after the first colon (":"). + * If the name does not contain a colon, the full name is returned. + */ + public String getLocalName() { + int colonIndex = name.indexOf(':'); + return colonIndex >= 0 + ? name.substring(colonIndex + 1) + : name; + } + +/* END CUSTOM CODE */ + + + public ASTAttribute(int id) { + super(id); + } + + public ASTAttribute(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttributeValue.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttributeValue.java new file mode 100644 index 0000000000..de01b30acd --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttributeValue.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTAttributeValue.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTAttributeValue extends AbstractJspNode { + public ASTAttributeValue(int id) { + super(id); + } + + public ASTAttributeValue(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCData.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCData.java new file mode 100644 index 0000000000..d991feb046 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCData.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTCData.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTCData extends AbstractJspNode { + public ASTCData(int id) { + super(id); + } + + public ASTCData(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCommentTag.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCommentTag.java new file mode 100644 index 0000000000..3b1a6fabb7 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCommentTag.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTCommentTag.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTCommentTag extends AbstractJspNode { + public ASTCommentTag(int id) { + super(id); + } + + public ASTCommentTag(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCompilationUnit.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCompilationUnit.java new file mode 100644 index 0000000000..d72ed983b1 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCompilationUnit.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTCompilationUnit.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +import net.sourceforge.pmd.lang.ast.RootNode; + +public class ASTCompilationUnit extends AbstractJspNode implements RootNode { + public ASTCompilationUnit(int id) { + super(id); + } + + public ASTCompilationUnit(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTContent.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTContent.java new file mode 100644 index 0000000000..4091cac310 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTContent.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTContent.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTContent extends AbstractJspNode { + public ASTContent(int id) { + super(id); + } + + public ASTContent(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTDeclaration.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTDeclaration.java new file mode 100644 index 0000000000..6f4019e13f --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTDeclaration.java @@ -0,0 +1,44 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTDeclaration.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTDeclaration extends AbstractJspNode { + +/* BEGIN CUSTOM CODE */ + private String name; + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } +/* END CUSTOM CODE */ + + + public ASTDeclaration(int id) { + super(id); + } + + public ASTDeclaration(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTDoctypeDeclaration.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTDoctypeDeclaration.java new file mode 100644 index 0000000000..e09e3450a8 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTDoctypeDeclaration.java @@ -0,0 +1,47 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTDoctypeDeclaration.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTDoctypeDeclaration extends AbstractJspNode { + + /* BEGIN CUSTOM CODE */ + + /** + * Name of the document type. Cannot be null. + */ + private String name; + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } +/* END CUSTOM CODE */ + + public ASTDoctypeDeclaration(int id) { + super(id); + } + + public ASTDoctypeDeclaration(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTDoctypeExternalId.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTDoctypeExternalId.java new file mode 100644 index 0000000000..02eb84cf15 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTDoctypeExternalId.java @@ -0,0 +1,72 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTDoctypeExternalId.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTDoctypeExternalId extends AbstractJspNode { + +/* BEGIN CUSTOM CODE */ + + /** + * URI of the external entity. Cannot be null. + */ + private String uri; + + /** + * Public ID of the external entity. This is optional. + */ + private String publicId; + + public boolean isHasPublicId() { + return null != publicId; + } + + /** + * @return Returns the name. + */ + public String getUri() { + return uri; + } + + /** + * @param name The name to set. + */ + public void setUri(String name) { + this.uri = name; + } + + /** + * @return Returns the publicId (or an empty string if there is none + * for this external entity id). + */ + public String getPublicId() { + return null == publicId ? "" : publicId; + } + + /** + * @param publicId The publicId to set. + */ + public void setPublicId(String publicId) { + this.publicId = publicId; + } +/* END CUSTOM CODE */ + + + public ASTDoctypeExternalId(int id) { + super(id); + } + + public ASTDoctypeExternalId(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElExpression.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElExpression.java new file mode 100644 index 0000000000..2f1f3debff --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElExpression.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTElExpression.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTElExpression extends AbstractJspNode { + public ASTElExpression(int id) { + super(id); + } + + public ASTElExpression(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElement.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElement.java new file mode 100644 index 0000000000..5240044d34 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElement.java @@ -0,0 +1,110 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTElement.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTElement extends AbstractJspNode { + +/* BEGIN CUSTOM CODE */ + + /** + * Name of the element-tag. Cannot be null. + */ + private String name; + + /** + * Flag indicating that the element consists of one tag ("<... />"). + */ + private boolean empty; // + + /** + * Flag indicating that the parser did not find a proper ending marker + * or ending tag for this element + */ + private boolean unclosed; + + /** + * @return boolean - true if the element has a namespace-prefix, false otherwise + */ + public boolean isHasNamespacePrefix() { + return name.indexOf(':') >= 0; + } + + /** + * @return String - the part of the name that is before the (first) colon (":") + */ + public String getNamespacePrefix() { + int colonIndex = name.indexOf(':'); + return colonIndex >= 0 + ? name.substring(0, colonIndex) + : ""; + } + + /** + * @return String - The part of the name that is after the first colon (":"). + * If the name does not contain a colon, the full name is returned. + */ + public String getLocalName() { + int colonIndex = name.indexOf(':'); + return colonIndex >= 0 + ? name.substring(colonIndex + 1) + : name; + } + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return Returns the empty. + */ + public boolean isEmpty() { + return empty; + } + + public boolean isUnclosed() { + return unclosed; + } + + public void setUnclosed(boolean unclosed) { + this.unclosed = unclosed; + } + + /** + * @param empty The empty to set. + */ + public void setEmpty(boolean empty) { + this.empty = empty; + } +/* END CUSTOM CODE */ + + + + public ASTElement(int id) { + super(id); + } + + public ASTElement(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTHtmlScript.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTHtmlScript.java new file mode 100644 index 0000000000..20a1d205bc --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTHtmlScript.java @@ -0,0 +1,22 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTHtmlScript.java Version 4.1 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY= */ +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTHtmlScript extends AbstractJspNode { + public ASTHtmlScript(int id) { + super(id); + } + + public ASTHtmlScript(JspParser p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + @Override + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspComment.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspComment.java new file mode 100644 index 0000000000..716a6cace1 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspComment.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTJspComment.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTJspComment extends AbstractJspNode { + public ASTJspComment(int id) { + super(id); + } + + public ASTJspComment(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclaration.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclaration.java new file mode 100644 index 0000000000..d7ba941429 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclaration.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTJspDeclaration.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTJspDeclaration extends AbstractJspNode { + public ASTJspDeclaration(int id) { + super(id); + } + + public ASTJspDeclaration(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclarations.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclarations.java new file mode 100644 index 0000000000..85d5e4b9f2 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclarations.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTJspDeclarations.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTJspDeclarations extends AbstractJspNode { + public ASTJspDeclarations(int id) { + super(id); + } + + public ASTJspDeclarations(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDirective.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDirective.java new file mode 100644 index 0000000000..9c0bbc5dfc --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDirective.java @@ -0,0 +1,47 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTJspDirective.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTJspDirective extends AbstractJspNode { + + /* BEGIN CUSTOM CODE */ + + /** + * Name of the element-tag. Cannot be null. + */ + private String name; + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } +/* END CUSTOM CODE */ + + public ASTJspDirective(int id) { + super(id); + } + + public ASTJspDirective(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDirectiveAttribute.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDirectiveAttribute.java new file mode 100644 index 0000000000..dcfc3ee885 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDirectiveAttribute.java @@ -0,0 +1,58 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTJspDirectiveAttribute.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTJspDirectiveAttribute extends AbstractJspNode { + + /* BEGIN CUSTOM CODE */ + private String name; + private String value; + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return Returns the value. + */ + public String getValue() { + return value; + } + + /** + * @param value The value to set. + */ + public void setValue(String value) { + this.value = value; + } +/* END CUSTOM CODE */ + + public ASTJspDirectiveAttribute(int id) { + super(id); + } + + public ASTJspDirectiveAttribute(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDocument.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDocument.java new file mode 100644 index 0000000000..04b4178779 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDocument.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTJspDocument.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTJspDocument extends AbstractJspNode { + public ASTJspDocument(int id) { + super(id); + } + + public ASTJspDocument(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpression.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpression.java new file mode 100644 index 0000000000..f2c3a70c85 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpression.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTJspExpression.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTJspExpression extends AbstractJspNode { + public ASTJspExpression(int id) { + super(id); + } + + public ASTJspExpression(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpressionInAttribute.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpressionInAttribute.java new file mode 100644 index 0000000000..77a3987a56 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpressionInAttribute.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTJspExpressionInAttribute.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTJspExpressionInAttribute extends AbstractJspNode { + public ASTJspExpressionInAttribute(int id) { + super(id); + } + + public ASTJspExpressionInAttribute(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspScriptlet.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspScriptlet.java new file mode 100644 index 0000000000..730d75422e --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspScriptlet.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTJspScriptlet.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTJspScriptlet extends AbstractJspNode { + public ASTJspScriptlet(int id) { + super(id); + } + + public ASTJspScriptlet(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTText.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTText.java new file mode 100644 index 0000000000..667dcfda9c --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTText.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTText.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTText extends AbstractJspNode { + public ASTText(int id) { + super(id); + } + + public ASTText(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTUnparsedText.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTUnparsedText.java new file mode 100644 index 0000000000..c50c6e6401 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTUnparsedText.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTUnparsedText.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTUnparsedText extends AbstractJspNode { + public ASTUnparsedText(int id) { + super(id); + } + + public ASTUnparsedText(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTValueBinding.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTValueBinding.java new file mode 100644 index 0000000000..e6baa83cd4 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTValueBinding.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTValueBinding.java */ + +package net.sourceforge.pmd.lang.jsp.ast; + +public class ASTValueBinding extends AbstractJspNode { + public ASTValueBinding(int id) { + super(id); + } + + public ASTValueBinding(JspParser p, int id) { + super(p, id); + } + + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNode.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNode.java new file mode 100644 index 0000000000..b6a20c2f40 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNode.java @@ -0,0 +1,61 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; + +import net.sourceforge.pmd.lang.ast.AbstractNode; + +public class AbstractJspNode extends AbstractNode implements JspNode { + + protected JspParser parser; + + public AbstractJspNode(int id) { + super(id); + } + + public AbstractJspNode(JspParser parser, int id) { + super(id); + this.parser = parser; + } + + public void jjtOpen() { + if (beginLine == -1 && parser.token.next != null) { + beginLine = parser.token.next.beginLine; + beginColumn = parser.token.next.beginColumn; + } + } + + public void jjtClose() { + if (beginLine == -1 && (children == null || children.length == 0)) { + beginColumn = parser.token.beginColumn; + } + if (beginLine == -1) { + beginLine = parser.token.beginLine; + } + endLine = parser.token.endLine; + endColumn = parser.token.endColumn; + } + + /** + * Accept the visitor. * + */ + public Object jjtAccept(JspParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + /** + * Accept the visitor. * + */ + public Object childrenAccept(JspParserVisitor visitor, Object data) { + if (children != null) { + for (int i = 0; i < children.length; ++i) { + ((JspNode) children[i]).jjtAccept(visitor, data); + } + } + return data; + } + + public String toString() { + return JspParserTreeConstants.jjtNodeName[id]; + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/DumpFacade.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/DumpFacade.java new file mode 100644 index 0000000000..2da772e466 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/DumpFacade.java @@ -0,0 +1,103 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +import net.sourceforge.pmd.lang.ast.Node; + +public class DumpFacade extends JspParserVisitorAdapter { + + private PrintWriter writer; + private boolean recurse; + + public void initializeWith(Writer writer, String prefix, boolean recurse, JspNode node) { + this.writer = writer instanceof PrintWriter ? (PrintWriter) writer : new PrintWriter(writer); + this.recurse = recurse; + this.visit(node, prefix); + try { + writer.flush(); + } catch (IOException e) { + throw new RuntimeException("Problem flushing PrintWriter.", e); + } + } + + @Override + public Object visit(JspNode node, Object data) { + dump(node, (String) data); + if (recurse) { + return super.visit(node, data + " "); + } else { + return data; + } + } + + private void dump(Node node, String prefix) { + // + // Dump format is generally composed of the following items... + // + + // 1) Dump prefix + writer.print(prefix); + + // 2) JJT Name of the Node + writer.print(node.toString()); + + // + // If there are any additional details, then: + // 1) A colon + // 2) The Node.getImage() if it is non-empty + // 3) Extras in parentheses + // + + // Standard image handling + String image = node.getImage(); + + // Extras + List extras = new ArrayList<>(); + + // Other extras + if (node instanceof ASTAttribute) { + extras.add("name=[" + ((ASTAttribute) node).getName() + "]"); + } else if (node instanceof ASTDeclaration) { + extras.add("name=[" + ((ASTDeclaration) node).getName() + "]"); + } else if (node instanceof ASTDoctypeDeclaration) { + extras.add("name=[" + ((ASTDoctypeDeclaration) node).getName() + "]"); + } else if (node instanceof ASTDoctypeExternalId) { + extras.add("uri=[" + ((ASTDoctypeExternalId) node).getUri() + "]"); + if (((ASTDoctypeExternalId) node).getPublicId().length() > 0) { + extras.add("publicId=[" + ((ASTDoctypeExternalId) node).getPublicId() + "]"); + } + } else if (node instanceof ASTElement) { + extras.add("name=[" + ((ASTElement) node).getName() + "]"); + if (((ASTElement) node).isEmpty()) { + extras.add("empty"); + } + } else if (node instanceof ASTJspDirective) { + extras.add("name=[" + ((ASTJspDirective) node).getName() + "]"); + } else if (node instanceof ASTJspDirectiveAttribute) { + extras.add("name=[" + ((ASTJspDirectiveAttribute) node).getName() + "]"); + extras.add("value=[" + ((ASTJspDirectiveAttribute) node).getValue() + "]"); + } + + // Output image and extras + if (image != null || !extras.isEmpty()) { + writer.print(':'); + if (image != null) { + writer.print(image); + } + for (String extra : extras) { + writer.print('('); + writer.print(extra); + writer.print(')'); + } + } + + writer.println(); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspNode.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspNode.java new file mode 100644 index 0000000000..f13963477b --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspNode.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; + +import net.sourceforge.pmd.lang.ast.Node; + +public interface JspNode extends Node { + /** + * Accept the visitor. * + */ + Object jjtAccept(JspParserVisitor visitor, Object data); + + /** + * Accept the visitor. * + */ + Object childrenAccept(JspParserVisitor visitor, Object data); +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspParserVisitorAdapter.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspParserVisitorAdapter.java new file mode 100644 index 0000000000..b29ee4fe54 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspParserVisitorAdapter.java @@ -0,0 +1,100 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; + +public class JspParserVisitorAdapter implements JspParserVisitor { + + public Object visit(JspNode node, Object data) { + node.childrenAccept(this, data); + return null; + } + + public Object visit(ASTCompilationUnit node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTContent node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspDirective node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspDirectiveAttribute node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspScriptlet node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspExpression node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspDeclaration node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspComment node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTText node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTUnparsedText node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTElExpression node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTValueBinding node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTCData node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTElement node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTAttribute node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTAttributeValue node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspExpressionInAttribute node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTCommentTag node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTDeclaration node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTDoctypeDeclaration node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTDoctypeExternalId node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTHtmlScript node, Object data) { + return visit((JspNode) node, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegister.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegister.java new file mode 100644 index 0000000000..c4a84b3c94 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegister.java @@ -0,0 +1,95 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; + +import java.util.ArrayList; +import java.util.List; + +import net.sourceforge.pmd.util.StringUtil; + +/** + * Utility class to keep track of unclosed tags. The mechanism is rather simple. + * If a end tag (</x>) is encountered, it will iterate through the open + * tag list and it will mark the first tag named 'x' as closed. If other tags + * have been opened after 'x' ( <x> <y> <z> </x>) it + * will mark y and z as unclosed. + * + * @author Victor Bucutea + * + */ +public class OpenTagRegister { + + private List tagList = new ArrayList<>(); + + public void openTag(ASTElement elm) { + if (elm == null || StringUtil.isEmpty(elm.getName())) { + throw new IllegalStateException( + "Tried to open a tag with empty name"); + } + + tagList.add(elm); + } + + /** + * + * @param closingTagName + * @return true if a matching tag was found. False if no tag with this name + * was ever opened ( or registered ) + */ + public boolean closeTag(String closingTagName) { + if (StringUtil.isEmpty(closingTagName)) { + throw new IllegalStateException( + "Tried to close a tag with empty name"); + } + + int lastRegisteredTagIdx = tagList.size() - 1; + /* + * iterate from top to bottom and look for the last tag with the same + * name as element + */ + boolean matchingTagFound = false; + List processedElmnts = new ArrayList<>(); + for (int i = lastRegisteredTagIdx; i >= 0; i--) { + ASTElement parent = tagList.get(i); + String parentName = parent.getName(); + + processedElmnts.add(parent); + if (parentName.equals(closingTagName)) { + // mark this tag as being closed + parent.setUnclosed(false); + // tag has children it cannot be empty + parent.setEmpty(false); + matchingTagFound = true; + break; + } else { + // only mark as unclosed if tag is not + // empty (e.g. is empty and properly closed) + if ( !parent.isEmpty()) { + parent.setUnclosed(true); + } + + parent.setEmpty(true); + } + } + + /* + * remove all processed tags. We should look for rogue tags which have + * no start (unopened tags) e.g. " " if "" has + * no open tag in the list (and in the whole document) we will consider + * as the closing tag for .If on the other hand tags are + * interleaved: then we will consider the + * closing tag of and a rogue tag or the closing tag of a + * potentially open parent tag ( but not the one after the ) + */ + if (matchingTagFound) { + tagList.removeAll(processedElmnts); + } + + return matchingTagFound; + } + + public void closeTag(ASTElement z) { + closeTag(z.getName()); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/StartAndEndTagMismatchException.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/StartAndEndTagMismatchException.java new file mode 100644 index 0000000000..868aeb2f68 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/StartAndEndTagMismatchException.java @@ -0,0 +1,81 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; + +/** + * @author Pieter_Van_Raemdonck + * @since Created on 11-jan-2006 + */ +public class StartAndEndTagMismatchException extends SyntaxErrorException { + + private static final long serialVersionUID = 5434485938487458692L; + + public static final String START_END_TAG_MISMATCH_RULE_NAME + = "Start and End Tags of an XML Element must match."; + + private int startLine, endLine, startColumn, endColumn; + private String startTagName, endTagName; + + /** + * Public constructor. + * + * @param startLine + * @param startColumn + * @param startTagName + * @param endLine + * @param endColumn + * @param endTagName + */ + public StartAndEndTagMismatchException(int startLine, int startColumn, String startTagName, + int endLine, int endColumn, String endTagName) { + super(endLine, START_END_TAG_MISMATCH_RULE_NAME); + this.startLine = startLine; + this.startColumn = startColumn; + this.startTagName = startTagName; + + this.endLine = endLine; + this.endColumn = endColumn; + this.endTagName = endTagName; + } + + + /** + * @return Returns the endColumn. + */ + public int getEndColumn() { + return endColumn; + } + + /** + * @return Returns the endLine. + */ + public int getEndLine() { + return endLine; + } + + /** + * @return Returns the startColumn. + */ + public int getStartColumn() { + return startColumn; + } + + /** + * @return Returns the startLine. + */ + public int getStartLine() { + return startLine; + } + + /* (non-Javadoc) + * @see java.lang.Throwable#getMessage() + */ + public String getMessage() { + return "The start-tag of element \"" + startTagName + "\" (line " + + startLine + ", column " + startColumn + + ") does not correspond to the end-tag found: \"" + + endTagName + "\" (line " + endLine + + ", column " + endColumn + ")."; + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/SyntaxErrorException.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/SyntaxErrorException.java new file mode 100644 index 0000000000..48edadc05c --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/SyntaxErrorException.java @@ -0,0 +1,41 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; + +/** + * Exception indicating that a syntactic error has been found. + * + * @author Pieter_Van_Raemdonck + * @since Created on 11-jan-2006 + */ +public abstract class SyntaxErrorException extends ParseException { + private static final long serialVersionUID = -6702683724078264059L; + + private int line; + private String ruleName; + + /** + * @param line + * @param ruleName + */ + public SyntaxErrorException(int line, String ruleName) { + super(); + this.line = line; + this.ruleName = ruleName; + } + + /** + * @return Returns the line. + */ + public int getLine() { + return line; + } + + /** + * @return Returns the ruleName. + */ + public String getRuleName() { + return ruleName; + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java new file mode 100644 index 0000000000..4a612199fd --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java @@ -0,0 +1,159 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.rule; + +import java.util.List; + +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.jsp.JspLanguageModule; +import net.sourceforge.pmd.lang.jsp.ast.ASTAttribute; +import net.sourceforge.pmd.lang.jsp.ast.ASTAttributeValue; +import net.sourceforge.pmd.lang.jsp.ast.ASTCData; +import net.sourceforge.pmd.lang.jsp.ast.ASTCommentTag; +import net.sourceforge.pmd.lang.jsp.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.jsp.ast.ASTContent; +import net.sourceforge.pmd.lang.jsp.ast.ASTDeclaration; +import net.sourceforge.pmd.lang.jsp.ast.ASTDoctypeDeclaration; +import net.sourceforge.pmd.lang.jsp.ast.ASTDoctypeExternalId; +import net.sourceforge.pmd.lang.jsp.ast.ASTElExpression; +import net.sourceforge.pmd.lang.jsp.ast.ASTElement; +import net.sourceforge.pmd.lang.jsp.ast.ASTHtmlScript; +import net.sourceforge.pmd.lang.jsp.ast.ASTJspComment; +import net.sourceforge.pmd.lang.jsp.ast.ASTJspDeclaration; +import net.sourceforge.pmd.lang.jsp.ast.ASTJspDirective; +import net.sourceforge.pmd.lang.jsp.ast.ASTJspDirectiveAttribute; +import net.sourceforge.pmd.lang.jsp.ast.ASTJspExpression; +import net.sourceforge.pmd.lang.jsp.ast.ASTJspExpressionInAttribute; +import net.sourceforge.pmd.lang.jsp.ast.ASTJspScriptlet; +import net.sourceforge.pmd.lang.jsp.ast.ASTText; +import net.sourceforge.pmd.lang.jsp.ast.ASTUnparsedText; +import net.sourceforge.pmd.lang.jsp.ast.ASTValueBinding; +import net.sourceforge.pmd.lang.jsp.ast.JspNode; +import net.sourceforge.pmd.lang.jsp.ast.JspParserVisitor; +import net.sourceforge.pmd.lang.rule.AbstractRule; +import net.sourceforge.pmd.lang.rule.ImmutableLanguage; + +public abstract class AbstractJspRule extends AbstractRule implements JspParserVisitor, ImmutableLanguage { + + public AbstractJspRule() { + super.setLanguage(LanguageRegistry.getLanguage(JspLanguageModule.NAME)); + } + + @Override + public void setUsesTypeResolution() { + // No Type resolution for JSP rules? + } + + public void apply(List nodes, RuleContext ctx) { + visitAll(nodes, ctx); + } + + protected void visitAll(List nodes, RuleContext ctx) { + for (Object element : nodes) { + JspNode node = (JspNode) element; + visit(node, ctx); + } + } + + // + // The following APIs are identical to those in JspParserVisitorAdapter. + // Due to Java single inheritance, it preferred to extend from the more + // complex Rule base class instead of from relatively simple Visitor. + // + + public Object visit(JspNode node, Object data) { + node.childrenAccept(this, data); + return null; + } + + public Object visit(ASTCompilationUnit node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTContent node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspDirective node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspDirectiveAttribute node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspScriptlet node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspExpression node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspDeclaration node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspComment node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTText node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTUnparsedText node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTElExpression node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTValueBinding node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTCData node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTElement node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTAttribute node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTAttributeValue node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTJspExpressionInAttribute node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTCommentTag node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTDeclaration node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTDoctypeDeclaration node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTDoctypeExternalId node, Object data) { + return visit((JspNode) node, data); + } + + public Object visit(ASTHtmlScript node, Object data) { + return visit((JspNode) node, data); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java new file mode 100644 index 0000000000..51e4d3eaee --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java @@ -0,0 +1,43 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.rule; + +import java.util.List; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.jsp.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.jsp.ast.JspNode; +import net.sourceforge.pmd.lang.jsp.ast.JspParserVisitor; +import net.sourceforge.pmd.lang.jsp.ast.JspParserVisitorAdapter; +import net.sourceforge.pmd.lang.rule.AbstractRuleChainVisitor; +import net.sourceforge.pmd.lang.rule.XPathRule; + +public class JspRuleChainVisitor extends AbstractRuleChainVisitor { + + protected void indexNodes(List nodes, RuleContext ctx) { + JspParserVisitor jspParserVisitor = new JspParserVisitorAdapter() { + // Perform a visitation of the AST to index nodes which need + // visiting by type + public Object visit(JspNode node, Object data) { + indexNode(node); + return super.visit(node, data); + } + }; + + for (int i = 0; i < nodes.size(); i++) { + jspParserVisitor.visit((ASTCompilationUnit)nodes.get(i), ctx); + } + } + + protected void visit(Rule rule, Node node, RuleContext ctx) { + // Rule better either be a JspParserVisitor, or a XPathRule + if (rule instanceof JspParserVisitor) { + ((JspNode) node).jjtAccept((JspParserVisitor) rule, ctx); + } else { + ((XPathRule) rule).evaluate(node, ctx); + } + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleViolationFactory.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleViolationFactory.java new file mode 100644 index 0000000000..a33f8d9f2f --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleViolationFactory.java @@ -0,0 +1,30 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.rule; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.RuleViolation; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.jsp.ast.JspNode; +import net.sourceforge.pmd.lang.rule.AbstractRuleViolationFactory; +import net.sourceforge.pmd.lang.rule.ParametricRuleViolation; +import net.sourceforge.pmd.lang.rule.RuleViolationFactory; + +public final class JspRuleViolationFactory extends AbstractRuleViolationFactory { + + public static final RuleViolationFactory INSTANCE = new JspRuleViolationFactory(); + + private JspRuleViolationFactory() { + } + + @Override + protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message) { + return new ParametricRuleViolation<>(rule, ruleContext, (JspNode) node, message); + } + + protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message, int beginLine, int endLine) { + return null; // FIXME + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/DuplicateJspImportsRule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/DuplicateJspImportsRule.java new file mode 100644 index 0000000000..5b719ebb1c --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/DuplicateJspImportsRule.java @@ -0,0 +1,53 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.rule.basic; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; + +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.jsp.ast.ASTJspDirectiveAttribute; +import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule; +import net.sourceforge.pmd.lang.rule.ImportWrapper; + +public class DuplicateJspImportsRule extends AbstractJspRule { + + private Set imports = new HashSet<>(); + + @Override + public void apply(List nodes, RuleContext ctx) { + /* + * TODO: This method is a hack! It's overriding the parent's method + * because the JSP parsing doesn't seem to hit ASTCompilationUnit + * properly + */ + imports.clear(); + super.apply(nodes, ctx); + } + + @Override + public Object visit(ASTJspDirectiveAttribute node, Object data) { + + if (!"import".equals(node.getName())) { + return super.visit(node, data); + } + String values = node.getValue(); + StringTokenizer st = new StringTokenizer(values, ","); + int count = st.countTokens(); + for (int ix = 0; ix < count; ix++) { + String token = st.nextToken(); + ImportWrapper wrapper = new ImportWrapper(token, token, node); + if (imports.contains(wrapper)) { + addViolation(data, node, node.getImage()); + } else { + imports.add(wrapper); + } + } + return super.visit(node, data); + } + +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoInlineStyleInformationRule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoInlineStyleInformationRule.java new file mode 100644 index 0000000000..994daaa69b --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoInlineStyleInformationRule.java @@ -0,0 +1,91 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.rule.basic; + +import java.util.Set; + +import net.sourceforge.pmd.lang.jsp.ast.ASTAttribute; +import net.sourceforge.pmd.lang.jsp.ast.ASTElement; +import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule; +import net.sourceforge.pmd.util.CollectionUtil; + +/** + * This rule checks that no "style" elements (like , , ...) are used, and that no + * "style" attributes (like "font", "size", "align") are used. + * + * @author pieter_van_raemdonck + */ +public class NoInlineStyleInformationRule extends AbstractJspRule { + + // These lists should probably be extended + + /** + * List of HTML element-names that define style. + */ + private static final Set STYLE_ELEMENT_NAMES = CollectionUtil.asSet( + new String[]{"B", "I", "FONT", "BASEFONT", "U", "CENTER"} + ); + + /** + * List of HTML element-names that can have attributes defining style. + */ + private static final Set ELEMENT_NAMES_THAT_CAN_HAVE_STYLE_ATTRIBUTES = CollectionUtil.asSet( + new String[]{"P", "TABLE", "THEAD", "TBODY", "TFOOT", "TR", "TD", "COL", "COLGROUP"} + ); + + /** + * List of attributes that define style when they are attributes of HTML elements with + * names in ELEMENT_NAMES_THAT_CAN_HAVE_STYLE_ATTRIBUTES. + */ + private static final Set STYLE_ATTRIBUTES = CollectionUtil.asSet( + new String[]{"STYLE", "FONT", "SIZE", "COLOR", "FACE", "ALIGN", "VALIGN", "BGCOLOR"} + ); + + public Object visit(ASTAttribute node, Object data) { + if (isStyleAttribute(node)) { + addViolation(data, node); + } + + return super.visit(node, data); + } + + public Object visit(ASTElement node, Object data) { + if (isStyleElement(node)) { + addViolation(data, node); + } + + return super.visit(node, data); + } + + /** + * Checks whether the name of the elementNode argument is one of STYLE_ELEMENT_NAMES. + * + * @param elementNode + * @return boolean + */ + private boolean isStyleElement(ASTElement elementNode) { + return STYLE_ELEMENT_NAMES.contains(elementNode.getName().toUpperCase()); + } + + /** + * Checks whether the attributeNode argument is a style attribute of a HTML element + * that can have style attributes. + * + * @param attributeNode The attribute node. + * @return true if a style attribute, false otherwise. + */ + private boolean isStyleAttribute(ASTAttribute attributeNode) { + if (STYLE_ATTRIBUTES.contains(attributeNode.getName().toUpperCase())) { + if (attributeNode.jjtGetParent() instanceof ASTElement) { + ASTElement parent = (ASTElement) attributeNode.jjtGetParent(); + if (ELEMENT_NAMES_THAT_CAN_HAVE_STYLE_ATTRIBUTES.contains(parent + .getName().toUpperCase())) { + return true; + } + } + } + + return false; + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoUnsanitizedJSPExpressionRule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoUnsanitizedJSPExpressionRule.java new file mode 100644 index 0000000000..e4cc627ef2 --- /dev/null +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoUnsanitizedJSPExpressionRule.java @@ -0,0 +1,36 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.rule.basic; + +import net.sourceforge.pmd.lang.jsp.ast.ASTElExpression; +import net.sourceforge.pmd.lang.jsp.ast.ASTElement; +import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule; + +/** + * This rule detects unsanitized JSP Expressions (can lead to Cross Site Scripting (XSS) attacks) + * + * @author maxime_robert + */ +public class NoUnsanitizedJSPExpressionRule extends AbstractJspRule { + @Override + public Object visit(ASTElExpression node, Object data) { + if (elOutsideTaglib(node)) { + addViolation(data, node); + } + + return super.visit(node, data); + } + + private boolean elOutsideTaglib(ASTElExpression node) { + ASTElement parentASTElement = node.getFirstParentOfType(ASTElement.class); + + boolean elInTaglib = parentASTElement != null && parentASTElement.getName() != null + && parentASTElement.getName().contains(":"); + + boolean elWithFnEscapeXml = node.getImage() != null && node.getImage().matches("^fn:escapeXml\\(.+\\)$"); + + return !elInTaglib && !elWithFnEscapeXml; + } + +} diff --git a/pmd-jsp/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-jsp/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..a644d2d185 --- /dev/null +++ b/pmd-jsp/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.cpd.JSPLanguage diff --git a/pmd-jsp/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language b/pmd-jsp/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language new file mode 100644 index 0000000000..a171cc649e --- /dev/null +++ b/pmd-jsp/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.lang.jsp.JspLanguageModule diff --git a/pmd-jsp/src/main/resources/rulesets/jsp/basic-jsf.xml b/pmd-jsp/src/main/resources/rulesets/jsp/basic-jsf.xml new file mode 100644 index 0000000000..84dc4f3cb2 --- /dev/null +++ b/pmd-jsp/src/main/resources/rulesets/jsp/basic-jsf.xml @@ -0,0 +1,42 @@ + + + + +Rules concerning basic JSF guidelines. + + + + +Do not nest JSF component custom actions inside a custom action that iterates over its body. + + 3 + + + + + + + + +
    + +
  • +
    +
+ ]]> +
+
+ +
+ diff --git a/pmd-jsp/src/main/resources/rulesets/jsp/basic.xml b/pmd-jsp/src/main/resources/rulesets/jsp/basic.xml new file mode 100644 index 0000000000..68de40f2c1 --- /dev/null +++ b/pmd-jsp/src/main/resources/rulesets/jsp/basic.xml @@ -0,0 +1,315 @@ + + + + + Rules concerning basic JSP guidelines. + + + +Scripts should be part of Tag Libraries, rather than part of JSP pages. + + 2 + + + + 10) ] + ]]> + + + + + + + + + + + ]]> + + + + + +Scriptlets should be factored into Tag Libraries or JSP declarations, rather than being part of JSP pages. + + 3 + + + + + + + + + + +<% +response.setHeader("Pragma", "No-cache"); +%> + + + String title = "Hello world!"; + + + ]]> + + + + + or <FONT> tags, or attributes like "align='center'". ]]> + + 3 + +

text

+ ]]> +
+
+ + + + +Do not use an attribute called 'class'. Use "styleclass" for CSS styles. + + 2 + + + + + + + + + +

Some text

+ + ]]> +
+
+ + + +Do not do a forward from within a JSP file. + + 3 + + + + + + + + + + ]]> + + + + + +IFrames which are missing a src element can cause security information popups in IE if you are accessing the page +through SSL. See http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q261188 + + 2 + + + + + + + + + bad example><BODY> +<iframe></iframe> +</BODY> </HTML> + +<HTML><title>good example><BODY> +<iframe src="foo"></iframe> +</BODY> </HTML> + ]]> + </example> + </rule> + + <rule name="NoHtmlComments" language="jsp" since="3.6" + message="Use JSP comments instead of HTML comments" + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/rules/jsp/basic.html#NoHtmlComments"> + <description> + In a production system, HTML comments increase the payload + between the application server to the client, and serve + little other purpose. Consider switching to JSP comments. + </description> + <priority>2</priority> + <properties> + <property name="xpath"> + <value> + <![CDATA[ + //CommentTag + ]]> + </value> + </property> + </properties> + <example> + <![CDATA[ +<HTML><title>bad example><BODY> +<!-- HTML comment --> +</BODY> </HTML> + +<HTML><title>good example><BODY> +<%-- JSP comment --%> +</BODY> </HTML> + ]]> + </example> + </rule> + + <rule name="DuplicateJspImports" since="3.7" + message="Avoid duplicate imports such as ''{0}''" + class="net.sourceforge.pmd.lang.jsp.rule.basic.DuplicateJspImportsRule" + externalInfoUrl="${pmd.website.baseurl}/rules/jsp/basic.html#DuplicateJspImports"> + <description><![CDATA[Avoid duplicate import statements inside JSP's. ]]> + </description> + <priority>3</priority> + <example> + <![CDATA[ +<%@ page import=\"com.foo.MyClass,com.foo.MyClass\"%><html><body><b><img src=\"<%=Some.get()%>/foo\">xx</img>text</b></body></html> + ]]> + </example> + </rule> + + <rule name="JspEncoding" language="jsp" since="4.0" + class="net.sourceforge.pmd.lang.rule.XPathRule" + message="JSP file should use UTF-8 encoding" + externalInfoUrl="${pmd.website.baseurl}/rules/jsp/basic.html#JspEncoding"> + <description> + <![CDATA[ +A missing 'meta' tag or page directive will trigger this rule, as well as a non-UTF-8 charset. + ]]> + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> + <![CDATA[ +//Content[ +not(Element[@Name="meta"][ + Attribute[@Name="content"]/AttributeValue[contains(lower-case(@Image),"charset=utf-8")] +]) +and + not(JspDirective[@Name='page']/JspDirectiveAttribute[@Name='contentType'][contains(lower-case(@Value),"charset=utf-8")]) +] + ]]> + </value> + </property> + </properties> + <example> + <![CDATA[ + Most browsers should be able to interpret the following headers: + + <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> + + <meta http-equiv="Content-Type"  content="text/html; charset=UTF-8" /> + ]]> + </example> + </rule> + + <rule + name="NoInlineScript" language="jsp" since="4.0" + class="net.sourceforge.pmd.lang.rule.XPathRule" + message="Avoiding inlining HTML script content" + externalInfoUrl="${pmd.website.baseurl}/rules/jsp/basic.html#NoInlineScript"> + <description> +Avoid inlining HTML script content. Consider externalizing the HTML script using the 'src' attribute on the "script" element. +Externalized script could be reused between pages. Browsers can also cache the script, reducing overall download bandwidth. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> + <![CDATA[ +//HtmlScript[@Image != ''] + ]]> + </value> + </property> + </properties> + <example> + <![CDATA[ + Most browsers should be able to interpret the following headers: + + <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> + + <meta http-equiv="Content-Type"  content="text/html; charset=UTF-8" /> + ]]> + </example> + </rule> + + <rule + name="NoUnsanitizedJSPExpression" since="5.1.4" + class="net.sourceforge.pmd.lang.jsp.rule.basic.NoUnsanitizedJSPExpressionRule" + message="Using unsanitized JSP expression can lead to Cross Site Scripting (XSS) attacks" + externalInfoUrl="${pmd.website.baseurl}/rules/jsp/basic.html#NoUnsanitizedJSPExpression"> + <description> +Avoid using expressions without escaping / sanitizing. This could lead to cross site scripting - as the expression +would be interpreted by the browser directly (e.g. "<script>alert('hello');</script>"). + </description> + <priority>3</priority> + <example> + <![CDATA[ +<%@ page contentType="text/html; charset=UTF-8" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +${expression} <!-- don't use this --> +${fn:escapeXml(expression)} <!-- instead, escape it --> +<c:out value="${expression}" /> <!-- or use c:out --> + ]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-jsp/src/main/resources/rulesets/jsp/rulesets.properties b/pmd-jsp/src/main/resources/rulesets/jsp/rulesets.properties new file mode 100644 index 0000000000..dd44e8e40c --- /dev/null +++ b/pmd-jsp/src/main/resources/rulesets/jsp/rulesets.properties @@ -0,0 +1,5 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=rulesets/jsp/basic.xml,rulesets/jsp/basic-jsf.xml diff --git a/pmd-jsp/src/site/markdown/index.md b/pmd-jsp/src/site/markdown/index.md new file mode 100644 index 0000000000..d8112908ac --- /dev/null +++ b/pmd-jsp/src/site/markdown/index.md @@ -0,0 +1,5 @@ +# PMD JSP + +Contains the PMD implementation to support Java Server Pages. + +For the available rules, see <a href="rules/index.html">rulesets index</a> page. diff --git a/pmd-jsp/src/site/site.pre.xml b/pmd-jsp/src/site/site.pre.xml new file mode 100644 index 0000000000..f012ef958a --- /dev/null +++ b/pmd-jsp/src/site/site.pre.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project + xmlns="http://maven.apache.org/DECORATION/1.1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/DECORATION/1.1.0 http://maven.apache.org/xsd/decoration-1.1.0.xsd" + name="PMD JSP"> + + <body> + <menu ref="parent"/> + + <!-- The rulesets part of navigation will be added during pre-site and the + list is build dynamically based on rulesets folder directory layout --> + + + <menu name="Rule Sets"/> + + + <!-- *********** --> + + <menu ref="reports"/> + + </body> +</project> diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/LanguageVersionDiscovererTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/LanguageVersionDiscovererTest.java new file mode 100644 index 0000000000..c6152cee3d --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/LanguageVersionDiscovererTest.java @@ -0,0 +1,32 @@ +package net.sourceforge.pmd; + + +import static org.junit.Assert.assertEquals; + +import java.io.File; + +import junit.framework.JUnit4TestAdapter; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; +import net.sourceforge.pmd.lang.jsp.JspLanguageModule; + +import org.junit.Test; + +public class LanguageVersionDiscovererTest { + + /** + * Test on JSP file. + */ + @Test + public void testJspFile() { + LanguageVersionDiscoverer discoverer = new LanguageVersionDiscoverer(); + File jspFile = new File("/path/to/MyPage.jsp"); + LanguageVersion languageVersion = discoverer.getDefaultLanguageVersionForFile(jspFile); + assertEquals("LanguageVersion must be JSP!", LanguageRegistry.getLanguage(JspLanguageModule.NAME).getDefaultVersion(), languageVersion); + } + + public static junit.framework.Test suite() { + return new JUnit4TestAdapter(LanguageVersionDiscovererTest.class); + } +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java new file mode 100644 index 0000000000..a97648fcd6 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java @@ -0,0 +1,27 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd; + +import java.util.Arrays; +import java.util.Collection; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.jsp.JspLanguageModule; + +import org.junit.runners.Parameterized.Parameters; + +public class LanguageVersionTest extends AbstractLanguageVersionTest { + + public LanguageVersionTest(String name, String terseName, String version, LanguageVersion expected) { + super(name, terseName, version, expected); + } + + @Parameters + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { + { JspLanguageModule.NAME, JspLanguageModule.TERSE_NAME, "", LanguageRegistry.getLanguage(JspLanguageModule.NAME).getDefaultVersion() } + }); + } +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java new file mode 100644 index 0000000000..c1b168ab16 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java @@ -0,0 +1,12 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd; + + +/** + * Test jsp's rulesets + */ +public class RuleSetFactoryTest extends AbstractRuleSetFactoryTest { + // no additional tests yet +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java new file mode 100644 index 0000000000..8f3b4c6c77 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java @@ -0,0 +1,37 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp; + +import java.io.StringReader; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.ast.Node; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit test for JSP parsing. + * + */ +public class JspParserTest { + + /** + * Verifies bug #939 Jsp parser fails on $ + */ + @Test + public void testParseDollar() { + Node node = parse("<span class=\"CostUnit\">$</span><span class=\"CostMain\">129</span><span class=\"CostFrac\">.00</span>"); + Assert.assertNotNull(node); + } + + private Node parse(String code) { + LanguageVersionHandler jspLang = LanguageRegistry.getLanguage(JspLanguageModule.NAME).getDefaultVersion().getLanguageVersionHandler(); + Parser parser = jspLang.getParser(jspLang.getDefaultParserOptions()); + Node node = parser.parse(null, new StringReader(code)); + return node; + } +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNodesTst.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNodesTst.java new file mode 100644 index 0000000000..195a844ccc --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNodesTst.java @@ -0,0 +1,70 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; + +import static org.junit.Assert.assertEquals; + +import java.io.StringReader; +import java.util.HashSet; +import java.util.Set; + +import net.sourceforge.pmd.lang.ast.JavaCharStream; +import net.sourceforge.pmd.lang.ast.Node; + +public abstract class AbstractJspNodesTst { + + public <T extends JspNode> void assertNumberOfNodes(Class<T> clazz, String source, int number) { + Set<T> nodes = getNodes(clazz, source); + assertEquals("Exactly " + number + " element(s) expected", number, nodes.size()); + } + + /** + * Run the JSP parser on the source, and return the nodes of type clazz. + * + * @param clazz + * @param source + * @return Set + */ + public <T extends JspNode> Set<T> getNodes(Class<T> clazz, String source) { + JspParser parser = new JspParser(new JavaCharStream(new StringReader(source))); + Node rootNode = parser.CompilationUnit(); + Set<T> nodes = new HashSet<>(); + addNodeAndSubnodes(rootNode, nodes, clazz); + return nodes; + } + + /** + * Return a subset of allNodes, containing the items in allNodes + * that are of the given type. + * + * @param clazz + * @param allNodes + * @return Set + */ + public <T extends JspNode> Set<T> getNodesOfType(Class<T> clazz, Set<JspNode> allNodes) { + Set<T> result = new HashSet<>(); + for (Node node: allNodes) { + if (clazz.equals(node.getClass())) { + result.add((T)node); + } + } + return result; + } + + /** + * Add the given node and its subnodes to the set of nodes. If clazz is not null, only + * nodes of the given class are put in the set of nodes. + */ + private <T extends JspNode> void addNodeAndSubnodes(Node node, Set<T> nodes, Class<T> clazz) { + if (null != node) { + if ((null == clazz) || (clazz.equals(node.getClass()))) { + nodes.add((T)node); + } + for (int i=0; i < node.jjtGetNumChildren(); i++) { + addNodeAndSubnodes(node.jjtGetChild(i), nodes, clazz); + } + } + } + +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspDocStyleTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspDocStyleTest.java new file mode 100644 index 0000000000..52ce5d61f3 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspDocStyleTest.java @@ -0,0 +1,1068 @@ +package net.sourceforge.pmd.lang.jsp.ast; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import net.sourceforge.pmd.lang.ast.Node; + +import org.junit.Ignore; +import org.junit.Test; +/** + * Test parsing of a JSP in document style, by checking the generated AST. + * + * @author pieter_van_raemdonck - Application Engineers NV/SA - www.ae.be + * + */ +public class JspDocStyleTest extends AbstractJspNodesTst { + + /** + * Smoke test for JSP parser. + * + * @throws Throwable + */ + @Test + public void testSimplestJsp() throws Throwable { + assertNumberOfNodes(ASTElement.class, TEST_SIMPLEST_HTML, 1); + } + + /** + * Test the information on a Element and Attribute. + * + * @throws Throwable + */ + @Test + public void testElementAttributeAndNamespace() throws Throwable { + Set<JspNode> nodes = getNodes(null, TEST_ELEMENT_AND_NAMESPACE); + + Set<ASTElement> elementNodes = getNodesOfType(ASTElement.class, nodes); + assertEquals("One element node expected!", 1, elementNodes.size()); + ASTElement element = elementNodes.iterator().next(); + assertEquals("Correct name expected!", "h:html", element.getName()); + assertEquals("Has namespace prefix!", true, element.isHasNamespacePrefix()); + assertEquals("Element is empty!", true, element.isEmpty()); + assertEquals("Correct namespace prefix of element expected!", "h", element + .getNamespacePrefix()); + assertEquals("Correct local name of element expected!", "html", element + .getLocalName()); + + Set<ASTAttribute> attributeNodes = getNodesOfType(ASTAttribute.class, nodes); + assertEquals("One attribute node expected!", 1, attributeNodes.size()); + ASTAttribute attribute = attributeNodes.iterator().next(); + assertEquals("Correct name expected!", "MyNsPrefix:MyAttr", attribute + .getName()); + assertEquals("Has namespace prefix!", true, attribute.isHasNamespacePrefix()); + assertEquals("Correct namespace prefix of element expected!", "MyNsPrefix", + attribute.getNamespacePrefix()); + assertEquals("Correct local name of element expected!", "MyAttr", attribute + .getLocalName()); + + } + + /** + * Test exposing a bug of parsing error when having a hash as last character + * in an attribute value. + * + */ + @Test + public void testAttributeValueContainingHash() + { + Set<JspNode> nodes = getNodes(null, TEST_ATTRIBUTE_VALUE_CONTAINING_HASH); + + Set<ASTAttribute> attributes = getNodesOfType(ASTAttribute.class, nodes); + assertEquals("Three attributes expected!", 3, attributes.size()); + + List<ASTAttribute> attrsList = new ArrayList<>(attributes); + Collections.sort(attrsList, new Comparator<ASTAttribute>() { + public int compare(ASTAttribute arg0, ASTAttribute arg1) { + return arg0.getName().compareTo(arg1.getName()); + } + }); + + ASTAttribute attr = attrsList.get(0); + assertEquals("Correct attribute name expected!", + "foo", attr.getName()); + assertEquals("Correct attribute value expected!", + "CREATE", attr.getFirstDescendantOfType(ASTAttributeValue.class).getImage()); + + attr = attrsList.get(1); + assertEquals("Correct attribute name expected!", + "href", attr.getName()); + assertEquals("Correct attribute value expected!", + "#", attr.getFirstDescendantOfType(ASTAttributeValue.class).getImage()); + + attr = attrsList.get(2); + assertEquals("Correct attribute name expected!", + "something", attr.getName()); + assertEquals("Correct attribute value expected!", + "#yes#", attr.getFirstDescendantOfType(ASTAttributeValue.class).getImage()); + } + + /** + * Test correct parsing of CDATA. + */ + @Test + public void testCData() { + Set<ASTCData> cdataNodes = getNodes(ASTCData.class, TEST_CDATA); + + assertEquals("One CDATA node expected!", 1, cdataNodes.size()); + ASTCData cdata = cdataNodes.iterator().next(); + assertEquals("Content incorrectly parsed!", " some <cdata> ]] ]> ", cdata + .getImage()); + } + + /** + * Test parsing of Doctype declaration. + */ + @Test + public void testDoctype() { + Set<JspNode> nodes = getNodes(null, TEST_DOCTYPE); + + Set<ASTDoctypeDeclaration> docTypeDeclarations = getNodesOfType(ASTDoctypeDeclaration.class, nodes); + assertEquals("One doctype declaration expected!", 1, docTypeDeclarations + .size()); + ASTDoctypeDeclaration docTypeDecl = docTypeDeclarations + .iterator().next(); + assertEquals("Correct doctype-name expected!", "html", docTypeDecl.getName()); + + Set<ASTDoctypeExternalId> externalIds = getNodesOfType(ASTDoctypeExternalId.class, nodes); + assertEquals("One doctype external id expected!", 1, externalIds + .size()); + ASTDoctypeExternalId externalId = externalIds.iterator().next(); + assertEquals("Correct external public id expected!", "-//W3C//DTD XHTML 1.1//EN", + externalId.getPublicId()); + assertEquals("Correct external uri expected!", "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd", + externalId.getUri()); + + } + + /** + * Test parsing of a XML comment. + * + */ + @Test + public void testComment() { + Set<ASTCommentTag> comments = getNodes(ASTCommentTag.class, TEST_COMMENT); + assertEquals("One comment expected!", 1, comments.size()); + ASTCommentTag comment = comments.iterator().next(); + assertEquals("Correct comment content expected!", "comment", comment.getImage()); + } + + /** + * Test parsing of HTML <script> element. + */ + @Test + public void testHtmlScript() { + Set<ASTHtmlScript> scripts = getNodes(ASTHtmlScript.class, TEST_HTML_SCRIPT); + assertEquals("One script expected!", 1, scripts.size()); + ASTHtmlScript script = scripts.iterator().next(); + assertEquals("Correct script content expected!", "Script!", script.getImage()); + } + + /** + * Test parsing of HTML <script src="x"/> element. It might not be valid html + * but it is likely to appear in .JSP files. + */ + @Test + public void testImportHtmlScript() { + Set<ASTHtmlScript> scripts = getNodes(ASTHtmlScript.class, TEST_IMPORT_JAVASCRIPT); + assertEquals("One script expected!", 1, scripts.size()); + ASTHtmlScript script = scripts.iterator().next(); + List<ASTAttributeValue> value = script.findDescendantsOfType(ASTAttributeValue.class); + assertEquals("filename.js",value.get(0).getImage()); + } + + /** + * Test parsing of HTML <script> element. + */ + @Test + public void testHtmlScriptWithAttribute() { + Set<ASTHtmlScript> scripts = getNodes(ASTHtmlScript.class, TEST_HTML_SCRIPT_WITH_ATTRIBUTE); + assertEquals("One script expected!", 1, scripts.size()); + ASTHtmlScript script = scripts.iterator().next(); + assertEquals("Correct script content expected!", "Script!", script.getImage()); + List<ASTAttributeValue> attrs = script.findDescendantsOfType(ASTAttributeValue.class); + assertTrue("text/javascript".equals(attrs.get(0).getImage())); + } + + /** + * A complex script containing HTML comments, escapes, quotes, etc. + */ + @Test + public void testComplexHtmlScript(){ + Set<ASTHtmlScript> script = getNodes(ASTHtmlScript.class, TEST_COMPLEX_SCRIPT); + assertEquals("One script expected!", 1, script.size()); + ASTHtmlScript next = script.iterator().next(); + assertTrue(next.getImage().contains("<!--")); + Set<ASTCommentTag> comments = getNodes(ASTCommentTag.class, TEST_COMPLEX_SCRIPT); + assertEquals("One comment expected!", 1, comments.size()); + } + + /** + * Test parsing of HTML <script> element. + */ + @Test + public void testInlineCss() { + Set<ASTElement> scripts = getNodes(ASTElement.class, TEST_INLINE_STYLE); + assertEquals("Three elements expected!", 3, scripts.size()); + } + + /** + * Test parsing of HTML text within element. + */ + @Test + public void testTextInTag() { + Set<ASTText> scripts = getNodes(ASTText.class, TEST_TEXT_IN_TAG); + assertEquals("One text chunk expected!", 1, scripts.size()); + ASTText script = scripts.iterator().next(); + assertEquals("Correct content expected!", " some text ", script.getImage()); + } + + /** + * Test parsing of HTML with no spaces between tags. Parser is likely + * in this scenario. + */ + @Test + public void noSpacesBetweenTags() { + Set<ASTElement> scripts = getNodes(ASTElement.class, TEST_TAGS_NO_SPACE); + assertEquals("Two tags expected!", 2, scripts.size()); + List<ASTElement> elmts = sortNodesByName(scripts); + Iterator<ASTElement> iterator = elmts.iterator(); + ASTElement script = iterator.next(); + assertEquals("Correct content expected!", "a", script.getName()); + script = iterator.next(); + assertEquals("Correct content expected!", "b", script.getName()); + } + + /** + * the $ sign might trick the parser into thinking an EL is next. + * He should be able to treat it as plain text + */ + @Test + public void unclosedTagsWithDollar(){ + Set<ASTText> scripts = getNodes(ASTText.class, TEST_TAGS_WITH_DOLLAR); + assertEquals("Two text chunks expected!", 2, scripts.size()); + ASTText script = scripts.iterator().next(); + assertEquals("Correct content expected!", " $ ", script.getImage()); + } + + /** + * Make sure EL expressions aren't treated as plain text when they + * are around unclosed tags. + */ + @Test + public void unclosedTagsWithELWithin(){ + Set<ASTElExpression> scripts = getNodes(ASTElExpression.class, TEST_TAGS_WITH_EL_WITHIN); + assertEquals("Two EL expressions expected!", 2, scripts.size()); + List<ASTElExpression> exprs = sortByImage(scripts); + Iterator<ASTElExpression> iterator = exprs.iterator(); + ASTElExpression script = iterator.next(); + assertEquals("Correct content expected!", "expr1", script.getImage()); + script = iterator.next(); + assertEquals("Correct content expected!", "expr2", script.getImage()); + } + + /** + * Make sure mixed expressions don't confuse the parser + */ + @Test + public void mixedExpressions(){ + Set<ASTJspExpression> exprs = getNodes(ASTJspExpression.class, TEST_TAGS_WITH_MIXED_EXPRESSIONS); + assertEquals("One JSP expression expected!", 1, exprs.size()); + assertEquals("Image of expression should be \"expr\"", + "expr",exprs.iterator().next().getImage()); + Set<ASTElExpression> els = getNodes(ASTElExpression.class, TEST_TAGS_WITH_MIXED_EXPRESSIONS); + assertEquals("Two EL expression expected!", 2, els.size()); + assertEquals("Image of el should be \"expr\"", + "expr",els.iterator().next().getImage()); + + Set<ASTUnparsedText> unparsedtexts = getNodes(ASTUnparsedText.class, TEST_TAGS_WITH_MIXED_EXPRESSIONS); + List<ASTUnparsedText> sortedUnparsedTxts = sortByImage(unparsedtexts); + assertEquals("Two unparsed texts expected!", 2, sortedUnparsedTxts.size()); + Iterator<ASTUnparsedText> iterator = sortedUnparsedTxts.iterator(); + assertEquals("Image of text should be \"\\${expr}\"", + " \\${expr} ",iterator.next().getImage()); + assertEquals("Image of text should be \" aaa \"", + " aaa ",iterator.next().getImage()); + + // ASTText should contain the text between two tags. + Set<ASTText> texts = getNodes(ASTText.class, TEST_TAGS_WITH_MIXED_EXPRESSIONS); + List<ASTText> sortedTxts = sortByImage(texts); + assertEquals("Two regular texts expected!", 2, sortedTxts.size()); + Iterator<ASTText> iterator2 = sortedTxts.iterator(); + assertEquals("Image of text should be \"\\${expr}\"", + " \\${expr} ",iterator2.next().getImage()); + assertEquals("Image of text should be all text between two nodes" + + " \" aaa ${expr}#{expr} \"", + " aaa ${expr}#{expr}",iterator2.next().getImage()); + } + + + /** + * Make sure JSP expressions are properly detected when they are next + * to unclosed tags. + */ + @Test + public void unclosedTagsWithJspExpressionWithin(){ + Set<ASTJspExpression> scripts = getNodes(ASTJspExpression.class, TEST_TAGS_WITH_EXPRESSION_WITHIN); + assertEquals("Two JSP expressions expected!", 2, scripts.size()); + ASTJspExpression script = scripts.iterator().next(); + assertEquals("Correct content expected!", "expr", script.getImage()); + } + + + /** + * A dangling unopened ( just </closed> ) tag should not influence the parsing. + */ + @Test + @Ignore // sadly the number of + // <opening> tags has to be >= then the number of </closing> tags + public void textBetweenUnopenedTag(){ + Set<ASTText> scripts = getNodes(ASTText.class, TEST_TEXT_WITH_UNOPENED_TAG); + assertEquals("Two text chunks expected!", 2, scripts.size()); + ASTText script = scripts.iterator().next(); + assertEquals("Correct content expected!", "$", script.getImage()); + } + + /** + * Parser should be able to handle documents which start or end with unparsed text + */ + @Test + @Ignore// sadly the number of + // <opening> tags has to be >= then the number of </closing> tags + public void textMultipleClosingTags(){ + Set<ASTText> scripts = getNodes(ASTText.class, TEST_MULTIPLE_CLOSING_TAGS); + assertEquals("Four text chunks expected!", 4, scripts.size()); + ASTText script = scripts.iterator().next(); + assertEquals("Correct content expected!", " some text ", script.getImage()); + } + + + /** + * Test parsing of HTML <script> element. + */ + @Test + public void textAfterOpenAndClosedTag() { + Set<ASTElement> nodes = getNodes(ASTElement.class, TEST_TEXT_AFTER_OPEN_AND_CLOSED_TAG); + assertEquals("Two elements expected!", 2, nodes.size()); + List<ASTElement> elmts = sortNodesByName(nodes); + assertEquals("First element should be a","a",elmts.get(0).getName()); + assertFalse("first element should be closed",elmts.get(0).isUnclosed()); + assertEquals("Second element should be b","b",elmts.get(1).getName()); + assertTrue("Second element should not be closed",elmts.get(1).isUnclosed()); + + Set<ASTText> text = getNodes(ASTText.class, TEST_TEXT_AFTER_OPEN_AND_CLOSED_TAG); + assertEquals("Two text chunks expected!", 2, text.size()); + } + + @Test + public void quoteEL(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_QUOTE_EL); + assertEquals("One attribute expected!", 1, attributes.size()); + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "${something}", attr.getImage()); + } + + @Test + public void quoteExpression(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_QUOTE_EXPRESSION); + assertEquals("One attribute expected!", 1, attributes.size()); + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "<%=something%>", attr.getImage()); + } + + + @Test + @Ignore // tags contain quotes and break attribute parsing + public void quoteTagInAttribute(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_QUOTE_TAG_IN_ATTR); + assertEquals("One attribute expected!", 1, attributes.size()); + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", + "<bean:write name=\"x\" property=\"z\">", attr.getImage()); + } + /** + * smoke test for a non-quoted attribute value + */ + @Test + public void noQuoteAttrValue() { + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_ATTR); + assertEquals("One attribute expected!", 1, attributes.size()); + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "yes|", attr.getImage()); + } + + + /** + * tests whether JSP el is properly detected as attribute value + */ + @Test + public void noQuoteAttrWithJspEL(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_ATTR_WITH_EL); + assertEquals("two attributes expected!", 2, attributes.size()); + Iterator<ASTAttributeValue> iterator = attributes.iterator(); + ASTAttributeValue attr2 = iterator.next(); + if ("url".equals(attr2.getImage())){ + // we have to employ this nasty work-around + // in order to ensure that we check the proper attribute + attr2 = iterator.next(); + } + assertEquals("Expected to detect proper value for EL in attribute!", "${something}", attr2.getImage()); + } + + /** + * tests whether parse correctly detects presence of JSP expression <%= %> + * within an non-quoted attribute value + */ + @Test + public void noQuoteAttrWithJspExpression(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_ATTR_WITH_EXPRESSION); + assertEquals("One attribute expected!", 1, attributes.size()); + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "<%=something%>", attr.getImage()); + } + + /** + * tests whether parse correctly interprets empty non quote attribute + */ + @Test + public void noQuoteAttrEmpty(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_EMPTY_ATTR); + assertEquals("two attributes expected!", 2, attributes.size()); + Iterator<ASTAttributeValue> iterator = attributes.iterator(); + ASTAttributeValue attr = iterator.next(); + if ("http://someHost:/some_URL".equals(attr.getImage())){ + // we have to employ this nasty work-around + // in order to ensure that we check the proper attribute + attr = iterator.next(); + } + assertEquals("Expected to detect proper value for attribute!", "", attr.getImage()); + } + + /** + * tests whether parse correctly interprets an cr lf instead of an attribute + */ + @Test + public void noQuoteAttrCrLf(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_CR_LF_ATTR); + assertEquals("One attribute expected!", 2, attributes.size()); + Iterator<ASTAttributeValue> iterator = attributes.iterator(); + ASTAttributeValue attr = iterator.next(); + if ("http://someHost:/some_URL".equals(attr.getImage())){ + // we have to employ this nasty work-around + // in order to ensure that we check the proper attribute + attr = iterator.next(); + } + assertEquals("Expected to detect proper value for attribute!", "\r\n", attr.getImage()); + + } + + /** + * tests whether parse correctly interprets an tab instead of an attribute + */ + @Test + public void noQuoteAttrTab(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_TAB_ATTR); + assertEquals("One attribute expected!", 1, attributes.size()); + Iterator<ASTAttributeValue> iterator = attributes.iterator(); + ASTAttributeValue attr = iterator.next(); + assertEquals("Expected to detect proper value for attribute!", "\t", attr.getImage()); + + } + + /** + * tests whether parse does not fail in the presence of unclosed JSP expression <%= + * within an non-quoted attribute value + */ + @Test + public void noQuoteAttrWithMalformedJspExpression(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_ATTR_WITH_MALFORMED_EXPR); + assertEquals("One attribute expected!",1, attributes.size()); + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "<%=something", attr.getImage()); + } + + /** + * test a no quote attribute value which contains a scriptlet <% %> + * within its value + */ + @Test + @Ignore// nice test for future development + public void noQuoteAttrWithScriptletInValue(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_ATTR_WITH_SCRIPTLET); + assertEquals("One attribute expected!", 1, attributes.size()); + + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "<% String a = \"1\";%>", attr.getImage()); + } + + /** + * test a no quote attribute value can contain a tag (e.g. attr=<bean:write property="value" />) + * + */ + @Test + @Ignore// nice test for future development + public void noQuoteAttrWithBeanWriteTagAsValue(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_TAG_IN_ATTR); + assertEquals("One attribute expected!", 1, attributes.size()); + + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "<% String a = \"1\";%>", attr.getImage()); + } + + /** + * test a quote attribute value can contain a tag (e.g. attr="<bean:write property="value" />" ) + * Not sure if it's legal JSP code but most JSP engine accept and properly treat + * this value at runtime + */ + @Test + @Ignore// nice test for future development + public void quoteAttrWithBeanWriteTagAsValue(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, TEST_NO_QUOTE_TAG_IN_ATTR); + assertEquals("One attribute expected!", 1, attributes.size()); + + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "<% String a = \"1\";%>", attr.getImage()); + } + + /** + * test a no quote attribute value which contains the EL dollar sign $ + * within its value + */ + @Test + @Ignore// nice test for future development + public void noQuoteAttrWithDollarSignInValue(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, + TEST_NO_QUOTE_ATTR_WITH_DOLLAR); + assertEquals("One attribute expected!", 2, attributes.size()); + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "${something", attr.getImage()); + } + + /** + * test a no quote attribute value which contains the EL sharp sign # + * within its value + */ + @Test + @Ignore// nice test for future development + public void noQuoteAttrWithSharpSymbolInValue(){ + Set<ASTAttributeValue> attributes = getNodes(ASTAttributeValue.class, + TEST_NO_QUOTE_ATTR_WITH_HASH); + assertEquals("One attribute expected!", 1, attributes.size()); + ASTAttributeValue attr = attributes.iterator().next(); + assertEquals("Expected to detect proper value for attribute!", "#{something", attr.getImage()); + } + + @Test + public void unclosedTag(){ + Set<ASTElement> elements = getNodes(ASTElement.class, TEST_UNCLOSED_SIMPLE); + List<ASTElement> sortedElmnts = sortNodesByName(elements); + assertEquals("2 tags expected",2,elements.size()); + assertEquals("First element should be sorted tag:if", + "tag:if",sortedElmnts.get(0).getName()); + assertEquals("Second element should be tag:someTag", + "tag:someTag",sortedElmnts.get(1).getName()); + + assertTrue(sortedElmnts.get(0).isEmpty()); + assertTrue(sortedElmnts.get(0).isUnclosed()); + assertFalse(sortedElmnts.get(1).isEmpty()); + assertFalse(sortedElmnts.get(1).isUnclosed()); + } + + @Test + public void unclosedTagAndNoQuotesForAttribute(){ + Set<ASTElement> elements = getNodes(ASTElement.class, TEST_UNCLOSED_NO_QUOTE_ATTR); + List<ASTElement> sortedElmnts = sortNodesByName(elements); + assertEquals("2 tags expected",2,elements.size()); + assertEquals("First element should be sorted tag:if", + "tag:if",sortedElmnts.get(0).getName()); + assertEquals("Second element should be tag:someTag", + "tag:someTag",sortedElmnts.get(1).getName()); + + assertTrue(sortedElmnts.get(0).isEmpty()); + assertTrue(sortedElmnts.get(0).isUnclosed()); + assertFalse(sortedElmnts.get(1).isEmpty()); + assertFalse(sortedElmnts.get(1).isUnclosed()); + } + + @Test + public void unclosedTagMultipleLevels(){ + Set<ASTElement> elements = getNodes(ASTElement.class, TEST_UNCLOSED_MULTIPLE_LEVELS); + List<ASTElement> sortedElmnts = sortNodesByName(elements); + assertEquals("3 tags expected",3,elements.size()); + assertEquals("First element should be sorted tag:someTag", + "tag:someTag",sortedElmnts.get(0).getName()); + assertEquals("Second element should be tag:someTag", + "tag:someTag",sortedElmnts.get(1).getName()); + assertEquals("Third element should be tag:x", + "tag:x",sortedElmnts.get(2).getName()); + + assertFalse(sortedElmnts.get(0).isEmpty()); + assertFalse(sortedElmnts.get(0).isUnclosed()); + + assertTrue(sortedElmnts.get(1).isEmpty()); + assertTrue(sortedElmnts.get(1).isUnclosed()); + + assertFalse(sortedElmnts.get(2).isEmpty()); + assertFalse(sortedElmnts.get(2).isUnclosed()); + } + + /** + * <html> <a1> <a2/> <b/> </a1> </html> + */ + @Test + public void nestedEmptyTags(){ + Set<ASTElement> elements = getNodes(ASTElement.class, TEST_MULTIPLE_EMPTY_TAGS); + List<ASTElement> sortedElmnts = sortNodesByName(elements); + assertEquals("4 tags expected",4,elements.size()); + assertEquals("First element should a1", + "a1",sortedElmnts.get(0).getName()); + assertEquals("Second element should be a2", + "a2",sortedElmnts.get(1).getName()); + assertEquals("Third element should be b", + "b",sortedElmnts.get(2).getName()); + assertEquals("Third element should be html", + "html",sortedElmnts.get(3).getName()); + + + // a1 + assertFalse(sortedElmnts.get(0).isEmpty()); + assertFalse(sortedElmnts.get(0).isUnclosed()); + + // a2 + assertTrue(sortedElmnts.get(1).isEmpty()); + assertFalse(sortedElmnts.get(1).isUnclosed()); + + // b + assertTrue(sortedElmnts.get(2).isEmpty()); + assertFalse(sortedElmnts.get(2).isUnclosed()); + + //html + assertFalse(sortedElmnts.get(3).isEmpty()); + assertFalse(sortedElmnts.get(3).isUnclosed()); + + } + + /** + * <html> <a1> <a2> <a3> </a2> </a1> <b/> <a4/> </html> + */ + @Test + public void nestedMultipleTags(){ + Set<ASTElement> elements = getNodes(ASTElement.class, TEST_MULTIPLE_NESTED_TAGS); + List<ASTElement> sortedElmnts = sortNodesByName(elements); + assertEquals("4 tags expected",6,elements.size()); + assertEquals("First element should a1", + "a1",sortedElmnts.get(0).getName()); + assertEquals("Second element should be a2", + "a2",sortedElmnts.get(1).getName()); + assertEquals("Third element should be a3", + "a3",sortedElmnts.get(2).getName()); + assertEquals("Forth element should be a4", + "a4",sortedElmnts.get(3).getName()); + assertEquals("Fifth element should be b", + "b",sortedElmnts.get(4).getName()); + assertEquals("Sixth element should be html", + "html",sortedElmnts.get(5).getName()); + + + // a1 not empty and closed + assertFalse(sortedElmnts.get(0).isEmpty()); + assertFalse(sortedElmnts.get(0).isUnclosed()); + + // a2 not empty and closed + assertFalse(sortedElmnts.get(1).isEmpty()); + assertFalse(sortedElmnts.get(1).isUnclosed()); + + // a3 empty and not closed + assertTrue(sortedElmnts.get(2).isEmpty()); + assertTrue(sortedElmnts.get(2).isUnclosed()); + + // a4 empty but closed + assertTrue(sortedElmnts.get(3).isEmpty()); + assertFalse(sortedElmnts.get(3).isUnclosed()); + + // b empty but closed + assertTrue(sortedElmnts.get(4).isEmpty()); + assertFalse(sortedElmnts.get(4).isUnclosed()); + + //html not empty and closed + assertFalse(sortedElmnts.get(5).isEmpty()); + assertFalse(sortedElmnts.get(5).isUnclosed()); + + } + + /** + * will test <x> <a> <b> <b> </x> </a> </x> . + * Here x is the first tag to be closed thus rendering the next close of a (</a>) + * to be disregarded. + */ + @Test + public void unclosedParentTagClosedBeforeChild(){ + Set<ASTElement> elements = getNodes(ASTElement.class, + TEST_UNCLOSED_END_AFTER_PARENT_CLOSE); + List<ASTElement> sortedElmnts = sortNodesByName(elements); + assertEquals("4 tags expected",4,elements.size()); + assertEquals("First element should be 'a'", + "a",sortedElmnts.get(0).getName()); + assertEquals("Second element should be b", + "b",sortedElmnts.get(1).getName()); + assertEquals("Third element should be b", + "b",sortedElmnts.get(2).getName()); + assertEquals("Forth element should be x", + "x",sortedElmnts.get(3).getName()); + + //a + assertTrue(sortedElmnts.get(0).isEmpty()); + assertTrue(sortedElmnts.get(0).isUnclosed()); + + //b + assertTrue(sortedElmnts.get(1).isEmpty()); + assertTrue(sortedElmnts.get(1).isUnclosed()); + + //b + assertTrue(sortedElmnts.get(2).isEmpty()); + assertTrue(sortedElmnts.get(2).isUnclosed()); + + //x + assertFalse(sortedElmnts.get(3).isEmpty()); + assertFalse(sortedElmnts.get(3).isUnclosed()); + } + + + /** + * <x> <a> <b> <b> </z> </a> </x> + * An unmatched closing of 'z' appears randomly in the document. This + * should be disregarded and structure of children and parents should not be influenced. + * in other words </a> should close the first <a> tag , </x> should close the first + * <x>, etc. + */ + @Test + public void unmatchedTagDoesNotInfluenceStructure(){ + Set<ASTElement> elements = getNodes(ASTElement.class, + TEST_UNCLOSED_UNMATCHED_CLOSING_TAG); + List<ASTElement> sortedElmnts = sortNodesByName(elements); + assertEquals("4 tags expected",4,elements.size()); + assertEquals("First element should be 'a'", + "a",sortedElmnts.get(0).getName()); + assertEquals("Second element should be b", + "b",sortedElmnts.get(1).getName()); + assertEquals("Third element should be b", + "b",sortedElmnts.get(2).getName()); + assertEquals("Forth element should be x", + "x",sortedElmnts.get(3).getName()); + + //a is not empty and closed + assertFalse(sortedElmnts.get(0).isEmpty()); + assertFalse(sortedElmnts.get(0).isUnclosed()); + + //b empty and unclosed + assertTrue(sortedElmnts.get(1).isEmpty()); + assertTrue(sortedElmnts.get(1).isUnclosed()); + + //b empty and unclosed + assertTrue(sortedElmnts.get(2).isEmpty()); + assertTrue(sortedElmnts.get(2).isUnclosed()); + + //x not empty and closed + assertFalse(sortedElmnts.get(3).isEmpty()); + assertFalse(sortedElmnts.get(3).isUnclosed()); + } + + /** + * <a> <x> <a> <b> <b> </z> </a> </x> + * An unmatched closing of 'z' appears randomly in the document. This + * should be disregarded and structure of children and parents should not be influenced. + * Also un unclosed <a> tag appears at the start of the document + */ + @Test + public void unclosedStartTagWithUnmatchedCloseOfDifferentTag(){ + Set<ASTElement> elements = getNodes(ASTElement.class, + TEST_UNCLOSED_START_TAG_WITH_UNMATCHED_CLOSE); + List<ASTElement> sortedElmnts = sortNodesByName(elements); + assertEquals("5 tags expected",5,elements.size()); + assertEquals("First element should be 'a'", + "a",sortedElmnts.get(0).getName()); + assertEquals("Second element should be a", + "a",sortedElmnts.get(1).getName()); + assertEquals("Third element should be b", + "b",sortedElmnts.get(2).getName()); + assertEquals("Forth element should be b", + "b",sortedElmnts.get(3).getName()); + assertEquals("Fifth element should be x", + "x",sortedElmnts.get(4).getName()); + + //first a is empty and unclosed + assertTrue(sortedElmnts.get(0).isEmpty()); + assertTrue(sortedElmnts.get(0).isUnclosed()); + + //second a not empty and closed + assertFalse(sortedElmnts.get(1).isEmpty()); + assertFalse(sortedElmnts.get(1).isUnclosed()); + + //b empty and unclosed + assertTrue(sortedElmnts.get(2).isEmpty()); + assertTrue(sortedElmnts.get(2).isUnclosed()); + + //b empty and unclosed + assertTrue(sortedElmnts.get(3).isEmpty()); + assertTrue(sortedElmnts.get(3).isUnclosed()); + + //x not empty and closed + assertFalse(sortedElmnts.get(4).isEmpty()); + assertFalse(sortedElmnts.get(4).isUnclosed()); + } + + /** + * {@link #TEST_UNCLOSED_END_OF_DOC} + * <tag:x> <tag:y> + * Tests whether parser breaks on no closed tags at all + */ + //This is yet to be improved. If a closing tag does not + // exist no tags will be marked as empty :( + @Ignore + @Test + public void unclosedEndOfDoc(){ + Set<ASTElement> elements = getNodes(ASTElement.class, + TEST_UNCLOSED_END_OF_DOC); + List<ASTElement> sortedElmnts = sortNodesByName(elements); + assertEquals("2 tags expected",2,elements.size()); + assertEquals("First element should be 'tag:x'", + "tag:x",sortedElmnts.get(0).getName()); + assertEquals("Second element should be tag:y", + "tag:y",sortedElmnts.get(1).getName()); + + //b + //assertTrue(sortedElmnts.get(0).isEmpty()); + assertTrue(sortedElmnts.get(0).isUnclosed()); + + //b + assertTrue(sortedElmnts.get(1).isEmpty()); + assertTrue(sortedElmnts.get(1).isUnclosed()); + } + + /** + * will sort the AST element in list in alphabetical order and if tag name + * is the same it will sort against o1.getBeginColumn() +""+ o1.getBeginLine(). + * so first criteria is the name, then the second is the column +""+line string. + * @param elements + * @return + */ + private List<ASTElement> sortNodesByName(Set<ASTElement> elements){ + List<ASTElement> list = new ArrayList<>(); + list.addAll(elements); + Collections.sort(list, new Comparator<ASTElement>() { + public int compare(ASTElement o1, ASTElement o2) { + if (o1.getName() == null) + return Integer.MIN_VALUE; + if (o2.getName() == null) + return Integer.MAX_VALUE; + if (o1.getName().equals(o2.getName())){ + String o1Value = o1.getBeginColumn() +""+ o1.getBeginLine(); + String o2Value = o2.getBeginColumn() +""+ o2.getBeginLine(); + return o1Value.compareTo(o2Value); + } + return o1.getName().compareTo(o2.getName()); + } + }); + return list; + } + + /** + * will sort the AST node by the image name. + * @param elements + * @return + */ + private <T extends Node> List<T> sortByImage(Set<T> elements){ + List<T> list = new ArrayList<>(); + list.addAll(elements); + Collections.sort(list, new Comparator<Node>() { + public int compare(Node o1, Node o2) { + if (o1.getImage() == null) + return Integer.MIN_VALUE; + if (o2.getImage() == null) + return Integer.MAX_VALUE; + if (o1.getImage().equals(o2.getImage())){ + String o1Value = o1.getBeginColumn() +""+ o1.getBeginLine(); + String o2Value = o2.getBeginColumn() +""+ o2.getBeginLine(); + return o1Value.compareTo(o2Value); + } + return o1.getImage().compareTo(o2.getImage()); + } + }); + return list; + } + + + private static final String TEST_SIMPLEST_HTML = "<html/>"; + + private static final String TEST_ELEMENT_AND_NAMESPACE = "<h:html MyNsPrefix:MyAttr='MyValue'/>"; + + private static final String TEST_CDATA = "<html><![CDATA[ some <cdata> ]] ]> ]]></html>"; + + private static final String TEST_DOCTYPE = "<?xml version=\"1.0\" standalone='yes'?>\n" + + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" " + + "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" + + "<greeting>Hello, world!</greeting>"; + + private static final String TEST_COMMENT = "<html><!-- comment --></html>"; + + private static final String TEST_ATTRIBUTE_VALUE_CONTAINING_HASH = + "<tag:if something=\"#yes#\" foo=\"CREATE\"> <a href=\"#\">foo</a> </tag:if>"; + + private static final String TEST_HTML_SCRIPT = + "<html><head><script>Script!</script></head></html>"; + + private static final String TEST_IMPORT_JAVASCRIPT = + "<html><head><script src=\"filename.js\" type=\"text/javascript\"/></head></html>"; + + private static final String TEST_HTML_SCRIPT_WITH_ATTRIBUTE = + "<html><head><script type=\"text/javascript\">Script!</script></head></html>"; + + private static final String TEST_COMPLEX_SCRIPT = + "<HTML><BODY><!--Java Script-->" + + "<SCRIPT language='JavaScript' type='text/javascript'>" + + "<!--function calcDays(){" + + " date1 = date1.split(\"-\"); date2 = date2.split(\"-\");" + + " var sDate = new Date(date1[0]+\"/\"+date1[1]+\"/\"+date1[2]);" + + " var eDate = new Date(date2[0]+\"/\"+date2[1]+\"/\"+date2[2]);" + + " onload=calcDays;//-->" + + "</SCRIPT></BODY></HTML>;"; + + private static final String TEST_INLINE_STYLE = + "<html><head><style> div { color:red; } </style></head></html>"; + + private static final String TEST_TEXT_IN_TAG= + "<a> some text </a>"; + + private static final String TEST_TAGS_NO_SPACE = + "<a><b></a>"; + + private static final String TEST_TAGS_WITH_DOLLAR = + "<a> $ <b> $ </a>"; + + private static final String TEST_TAGS_WITH_EL_WITHIN = + "<a>#{expr1}<b>${expr2}</a>"; + + private static final String TEST_TAGS_WITH_MIXED_EXPRESSIONS = + "<a> aaa ${expr} #{expr} <%=expr%> <b> \\${expr} </a>"; + + private static final String TEST_TAGS_WITH_EXPRESSION_WITHIN = + "<a> <%=expr%> <b> <%=expr%> </a>"; + + private static final String TEST_TEXT_AFTER_OPEN_AND_CLOSED_TAG = + "<a> some text <b> some text </a>"; + + private static final String TEST_TEXT_WITH_UNOPENED_TAG = + "<a> some text </b> some text </a>"; + + private static final String TEST_MULTIPLE_CLOSING_TAGS = + "<a> some text </b> </b> </b> some text </a>"; + + private static final String TEST_QUOTE_EL = + "<tag:if something=\"${something}\" > </tag:if>"; + + private static final String TEST_QUOTE_EXPRESSION = + "<tag:if something=\"<%=something%>\" > </tag:if>"; + + private static final String TEST_QUOTE_TAG_IN_ATTR = + "<tag:if something=\"<bean:write name=\"x\" property=\"z\">\" > " + + "<a href=http://someHost:/some_URL >foo</a> </tag:if>"; + + private static final String TEST_NO_QUOTE_ATTR = +"<tag:if something=yes| > </tag:if>"; + + private static final String TEST_NO_QUOTE_EMPTY_ATTR = + "<tag:if something= > <a href=http://someHost:/some_URL >foo</a> </tag:if>"; + + private static final String TEST_NO_QUOTE_TAG_IN_ATTR = + "<tag:if something=<bean:write name=\"x\" property=\"z\"> > <a href=http://someHost:/some_URL >foo</a> </tag:if>"; + + private static final String TEST_NO_QUOTE_CR_LF_ATTR = + "<tag:if something=\r\n > <a href=http://someHost:/some_URL >foo</a> </tag:if>"; + + private static final String TEST_NO_QUOTE_TAB_ATTR = + "<tag:if something=\t > </tag:if>"; + + private static final String TEST_NO_QUOTE_ATTR_WITH_EL = + "<tag:if something=${something} > <a href=url >foo</a> </tag:if>"; + + private static final String TEST_NO_QUOTE_ATTR_WITH_EXPRESSION = + "<tag:if something=<%=something%> > </tag:if>"; + + /** + * same as {@link #TEST_NO_QUOTE_ATTR_WITH_EXPRESSION} only expression is not + * properly closed + */ + private static final String TEST_NO_QUOTE_ATTR_WITH_MALFORMED_EXPR = + "<tag:if something=<%=something > </tag:if>"; + + private static final String TEST_NO_QUOTE_ATTR_WITH_SCRIPTLET = + "<tag:if something=<% String a = \"1\";%>x > </tag:if>"; + + + private static final String TEST_NO_QUOTE_ATTR_WITH_DOLLAR = + "<tag:if something=${something > <a href=${ >foo</a> </tag:if>"; + + private static final String TEST_NO_QUOTE_ATTR_WITH_HASH = + "<tag:if something=#{something > <a href=#{url} >foo</a> </tag:if>"; + + private static final String TEST_UNCLOSED_SIMPLE = + "<tag:someTag> <tag:if someting=\"x\" > </tag:someTag>"; + + /** + * someTag is closed just once + */ + private static final String TEST_UNCLOSED_MULTIPLE_LEVELS = + "<tag:x> <tag:someTag> <tag:someTag someting=\"x\" > </tag:someTag> </tag:x>"; + + /** + * nested empty tags + */ + private static final String TEST_MULTIPLE_EMPTY_TAGS = + "<html> <a1> <a2/> <b/> </a1> </html>"; + + + /** + * multiple nested tags with some tags unclosed + */ + private static final String TEST_MULTIPLE_NESTED_TAGS = + "<html> <a1> <a2> <a3> </a2> </a1> <b/> <a4/> </html>"; + + /** + * </x> will close before </a>, thus leaving <a> to remain unclosed + */ + private static final String TEST_UNCLOSED_END_AFTER_PARENT_CLOSE = + "<x> <a> <b> <b> </x> </a> aa </x> bb </x>"; + + /** + * </z> is just a dangling closing tag not matching any parent. The parser should + * disregard it + */ + private static final String TEST_UNCLOSED_UNMATCHED_CLOSING_TAG = + "<x> <a> <b> <b> </z> </a> </x>"; + + /** + * First <a> tag does not close. The first closing of </a> will match the + * second opening of a. Another rogue </z> is there for testing compliance + */ + private static final String TEST_UNCLOSED_START_TAG_WITH_UNMATCHED_CLOSE = + "<a> <x> <a> <b> <b> </z> </a> </x>"; + + private static final String TEST_UNCLOSED_END_OF_DOC = + "<tag:x> <tag:y>"; + + private static final String TEST_UNCLOSED_NO_QUOTE_ATTR = + "<tag:someTag> <tag:if someting=x > </tag:someTag>"; + + + public static junit.framework.Test suite() { + return new junit.framework.JUnit4TestAdapter(JspDocStyleTest.class); + } +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspPageStyleTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspPageStyleTest.java new file mode 100644 index 0000000000..c89a579b72 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspPageStyleTest.java @@ -0,0 +1,180 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.junit.Test; +public class JspPageStyleTest extends AbstractJspNodesTst { + + /** + * Test parsing of a JSP comment. + */ + @Test + public void testComment() { + Set<ASTJspComment> comments = getNodes(ASTJspComment.class, JSP_COMMENT); + assertEquals("One comment expected!", 1, comments.size()); + ASTJspComment comment = comments.iterator().next(); + assertEquals("Correct comment content expected!", "some comment", comment.getImage()); + } + + /** + * Test parsing a JSP directive. + */ + @Test + public void testDirective() { + Set<JspNode> nodes = getNodes(null, JSP_DIRECTIVE); + + Set<ASTJspDirective> directives = getNodesOfType(ASTJspDirective.class, nodes); + assertEquals("One directive expected!", 1, directives.size()); + ASTJspDirective directive = directives.iterator().next(); + assertEquals("Correct directive name expected!", + "page", directive.getName()); + + Set<ASTJspDirectiveAttribute> directiveAttrs = getNodesOfType(ASTJspDirectiveAttribute.class, nodes); + assertEquals("Two directive attributes expected!", 2, directiveAttrs.size()); + + List<ASTJspDirectiveAttribute> attrsList = new ArrayList<>(directiveAttrs); + Collections.sort(attrsList, new Comparator<ASTJspDirectiveAttribute>() { + public int compare(ASTJspDirectiveAttribute arg0, ASTJspDirectiveAttribute arg1) { + return arg0.getName().compareTo(arg1.getName()); + } + }); + + ASTJspDirectiveAttribute attr = attrsList.get(0); + assertEquals("Correct directive attribute name expected!", + "language", attr.getName()); + assertEquals("Correct directive attribute value expected!", + "java", attr.getValue()); + + attr = attrsList.get(1); + assertEquals("Correct directive attribute name expected!", + "session", attr.getName()); + assertEquals("Correct directive attribute value expected!", + "true", attr.getValue()); + + + } + + /** + * Test parsing of a JSP declaration. + */ + @Test + public void testDeclaration() { + Set<ASTJspDeclaration> declarations = getNodes(ASTJspDeclaration.class, JSP_DECLARATION); + assertEquals("One declaration expected!", 1, declarations.size()); + ASTJspDeclaration declaration = declarations.iterator().next(); + assertEquals("Correct declaration content expected!", + "String someString = \"s\";", declaration.getImage()); + } + + /** + * Test parsing of a JSP scriptlet. + */ + @Test + public void testScriptlet() { + Set<ASTJspScriptlet> scriptlets = getNodes(ASTJspScriptlet.class, JSP_SCRIPTLET); + assertEquals("One scriptlet expected!", 1, scriptlets.size()); + ASTJspScriptlet scriptlet = scriptlets.iterator().next(); + assertEquals("Correct scriptlet content expected!", + "someString = someString + \"suffix\";", scriptlet.getImage()); + } + + /** + * Test parsing of a JSP expression. + */ + @Test + public void testExpression() { + Set<ASTJspExpression> expressions = getNodes(ASTJspExpression.class, JSP_EXPRESSION); + assertEquals("One expression expected!", 1, expressions.size()); + ASTJspExpression expression = expressions.iterator().next(); + assertEquals("Correct expression content expected!", + "someString", expression.getImage()); + } + + /** + * Test parsing of a JSP expression in an attribute. + */ + @Test + public void testExpressionInAttribute() { + Set<ASTJspExpressionInAttribute> expressions = getNodes(ASTJspExpressionInAttribute.class, + JSP_EXPRESSION_IN_ATTRIBUTE); + assertEquals("One expression expected!", 1, expressions.size()); + ASTJspExpressionInAttribute expression = expressions.iterator().next(); + assertEquals("Correct expression content expected!", + "style.getClass()", expression.getImage()); + } + + /** + * Test parsing of a EL expression. + */ + @Test + public void testElExpression() { + Set<ASTElExpression> expressions = getNodes(ASTElExpression.class, JSP_EL_EXPRESSION); + assertEquals("One expression expected!", 1, expressions.size()); + ASTElExpression expression = expressions.iterator().next(); + assertEquals("Correct expression content expected!", + "myBean.get(\"${ World }\")", expression.getImage()); + } + + /** + * Test parsing of a EL expression in an attribute. + */ + @Test + public void testElExpressionInAttribute() { + Set<ASTElExpression> expressions = getNodes(ASTElExpression.class, JSP_EL_EXPRESSION_IN_ATTRIBUTE); + assertEquals("One expression expected!", 1, expressions.size()); + ASTElExpression expression = expressions.iterator().next(); + assertEquals("Correct expression content expected!", + "myValidator.find(\"'jsp'\")", expression.getImage()); + } + + /** + * Test parsing of a EL expression in an attribute. + */ + @Test + public void testJsfValueBinding() { + Set<ASTValueBinding> valueBindings = getNodes(ASTValueBinding.class, JSF_VALUE_BINDING); + assertEquals("One value binding expected!", 1, valueBindings.size()); + ASTValueBinding valueBinding = valueBindings.iterator().next(); + assertEquals("Correct expression content expected!", + "myValidator.find(\"'jsf'\")", valueBinding.getImage()); + } + + private static final String JSP_COMMENT + = "<html> <%-- some comment --%> </html>"; + + private static final String JSP_DIRECTIVE + = "<html> <%@ page language=\"java\" session='true'%> </html>"; + + private static final String JSP_DECLARATION + = "<html><%! String someString = \"s\"; %></html>"; + + private static final String JSP_SCRIPTLET + = "<html> <% someString = someString + \"suffix\"; %> </html>"; + + private static final String JSP_EXPRESSION + = "<html><head><title> <%= someString %> "; + + private static final String JSP_EXPRESSION_IN_ATTRIBUTE + = "

Hello

"; + + private static final String JSP_EL_EXPRESSION + = "Hello ${myBean.get(\"${ World }\") } .jsp"; + + private static final String JSP_EL_EXPRESSION_IN_ATTRIBUTE + = " "; + + private static final String JSF_VALUE_BINDING + = "

Hello

"; + + public static junit.framework.Test suite() { + return new junit.framework.JUnit4TestAdapter(JspPageStyleTest.class); + } +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegisterTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegisterTest.java new file mode 100644 index 0000000000..61e1cd262f --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegisterTest.java @@ -0,0 +1,142 @@ +package net.sourceforge.pmd.lang.jsp.ast; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +public class OpenTagRegisterTest { + + private OpenTagRegister tagList; + + private int elmId = 0; + + @Before + public void newRegister() { + tagList = new OpenTagRegister(); + } + + /** + *
+ */ + @Test + public void testSimpleNesting() { + ASTElement elm = element("a"); + ASTElement elm2 = element("b"); + + tagList.openTag(elm); + tagList.openTag(elm2); + tagList.closeTag(elm); + + assertFalse(elm.isUnclosed()); + assertTrue(elm2.isUnclosed()); + } + + /** + * + */ + @Test + public void doubleNesting() { + ASTElement elm = element("a"); + ASTElement elm2 = element("b"); + ASTElement elm3 = element("b"); + + tagList.openTag(elm); + tagList.openTag(elm2); + tagList.openTag(elm3); + tagList.closeTag(elm); + + assertFalse(elm.isUnclosed()); + assertTrue(elm2.isUnclosed()); + assertTrue(elm3.isUnclosed()); + } + + /** + *
+ */ + @Test + public void unopenedTags() { + ASTElement elm = element("x"); + ASTElement elm2 = element("a"); + ASTElement elm3 = element("b"); + ASTElement elm4 = element("b"); + + tagList.openTag(elm); + tagList.openTag(elm2); + tagList.openTag(elm3); + tagList.openTag(elm4); + tagList.closeTag(elm); + tagList.closeTag(elm2); + tagList.closeTag(elm3); + tagList.closeTag(elm); + + assertFalse(elm.isUnclosed()); + assertTrue(elm2.isUnclosed()); + assertTrue(elm3.isUnclosed()); + assertTrue(elm4.isUnclosed()); + } + + /** + * + * + */ + @Test + public void interleavedTags() { + ASTElement elm = element("x"); + ASTElement elm2 = element("a"); + ASTElement elm3 = element("b"); + ASTElement elm4 = element("b"); + ASTElement elm5 = element("z"); + + tagList.openTag(elm); + tagList.openTag(elm2); + tagList.openTag(elm3); + tagList.openTag(elm4);// open b + tagList.closeTag(elm5);// close z + tagList.closeTag(elm2);// close a + tagList.closeTag(elm);// close x + + assertFalse(elm.isUnclosed()); // x is closed + assertFalse(elm2.isUnclosed()); // a is closed + assertTrue(elm3.isUnclosed()); + assertTrue(elm4.isUnclosed()); + // elm5 ??? + } + + /** + * + */ + @Test + public void openedIsolatedTag() { + ASTElement a = element("a"); + ASTElement x = element("x"); + ASTElement a2 = element("a"); + ASTElement b = element("b"); + ASTElement b2 = element("b"); + ASTElement z = element("z"); + + tagList.openTag(a); + tagList.openTag(x); + tagList.openTag(a2); + tagList.openTag(b); + tagList.openTag(b2); + tagList.closeTag(z);// close z + tagList.closeTag(a2);// close second a + tagList.closeTag(x);// close x + + assertTrue(a.isUnclosed()); // first a is unclosed + assertFalse(x.isUnclosed()); // x is closed + assertFalse(a2.isUnclosed()); // a is closed + + assertTrue(b.isUnclosed()); + assertTrue(b2.isUnclosed()); + } + + private ASTElement element(String name) { + ASTElement elm = new ASTElement(elmId++); + elm.setName(name); + return elm; + } + +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/XPathJspRuleTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/XPathJspRuleTest.java new file mode 100644 index 0000000000..09954701b5 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/XPathJspRuleTest.java @@ -0,0 +1,65 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.ast; + +import static org.junit.Assert.assertEquals; + +import java.io.StringReader; + +import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.Report; +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.RuleSet; +import net.sourceforge.pmd.RuleSets; +import net.sourceforge.pmd.RuleViolation; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.jsp.JspLanguageModule; +import net.sourceforge.pmd.lang.rule.XPathRule; +import net.sourceforge.pmd.testframework.RuleTst; + +import org.junit.Test; + + +public class XPathJspRuleTest extends RuleTst { + + /** + * Test matching a XPath expression against a JSP source. + * + * @throws Throwable + */ + @Test + public void testExpressionMatching() throws Throwable { + Rule rule = new XPathRule(XPATH_EXPRESSION); + rule.setMessage("Test"); + rule.setLanguage(LanguageRegistry.getLanguage(JspLanguageModule.NAME)); + RuleSet rules = new RuleSet(); + rules.addRule(rule); + + RuleContext ctx = new RuleContext(); + Report report = new Report(); + ctx.setReport(report); + ctx.setSourceCodeFilename("n/a"); + ctx.setLanguageVersion(LanguageRegistry.getLanguage(JspLanguageModule.NAME).getDefaultVersion()); + + PMD p = new PMD(); + + p.getSourceCodeProcessor().processSourceCode(new StringReader(MATCH), new RuleSets(rules), ctx); + + assertEquals("One violation expected!", 1, report.size()); + + RuleViolation rv = report.iterator().next(); + assertEquals(1, rv.getBeginLine()); + } + + private static final String MATCH + = "
"; + + private static final String XPATH_EXPRESSION + = "//Element [@Name='hr']"; + + public static junit.framework.Test suite() { + return new junit.framework.JUnit4TestAdapter(XPathJspRuleTest.class); + } +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basic/BasicRulesTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basic/BasicRulesTest.java new file mode 100644 index 0000000000..a8485d7f1f --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basic/BasicRulesTest.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.rule.basic; + +import net.sourceforge.pmd.testframework.SimpleAggregatorTst; + +public class BasicRulesTest extends SimpleAggregatorTst { + + private static final String RULESET = "jsp-basic"; + + @Override + public void setUp() { + addRule(RULESET, "DuplicateJspImports"); + addRule(RULESET, "IframeMissingSrcAttribute"); + addRule(RULESET, "JspEncoding"); + addRule(RULESET, "NoClassAttribute"); + addRule(RULESET, "NoHtmlComments"); + addRule(RULESET, "NoInlineScript"); + addRule(RULESET, "NoInlineStyleInformation"); + addRule(RULESET, "NoJspForward"); + addRule(RULESET, "NoLongScripts"); + addRule(RULESET, "NoScriptlets"); + addRule(RULESET, "NoUnsanitizedJSPExpression"); + } +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basicjsf/BasicJsfRulesTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basicjsf/BasicJsfRulesTest.java new file mode 100644 index 0000000000..dda4b35881 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basicjsf/BasicJsfRulesTest.java @@ -0,0 +1,16 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.jsp.rule.basicjsf; + +import net.sourceforge.pmd.testframework.SimpleAggregatorTst; + +public class BasicJsfRulesTest extends SimpleAggregatorTst { + + private static final String RULESET = "jsp-basic-jsf"; + + @Override + public void setUp() { + addRule(RULESET, "DontNestJsfInJstlIteration"); + } +} diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/DuplicateJspImports.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/DuplicateJspImports.xml new file mode 100644 index 0000000000..b615c0e7f4 --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/DuplicateJspImports.xml @@ -0,0 +1,63 @@ + + + + + 0 + xxtext + ]]> + jsp + + + + 1 + xxtext + ]]> + jsp + + + + 1 + <%@ page import="com.foo.MyClass"%>xxtext + ]]> + jsp + + + + 1 + <%@ page import="com.foo.MyClass"%>xxtext + ]]> + jsp + + + + 2 + <%@ page import="com.foo.MyClass"%>xxtext + ]]> + jsp + + + + 0 + <%@ page import="com.foo.AClass"%><%@ page import="com.foo.MyClass"%>xxtext + ]]> + jsp + + diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/IframeMissingSrcAttribute.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/IframeMissingSrcAttribute.xml new file mode 100644 index 0000000000..d5d9d90a97 --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/IframeMissingSrcAttribute.xml @@ -0,0 +1,33 @@ + + + + + 0 + + ]]> + jsp + + + + 1 + + ]]> + jsp + + + + 1 + + ]]> + jsp + + diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/JspEncoding.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/JspEncoding.xml new file mode 100644 index 0000000000..a3140ac2d5 --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/JspEncoding.xml @@ -0,0 +1,67 @@ + + + + + 1 + + ]]> + jsp + + + + 0 + + ]]> + jsp + + + + 0 + +<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +<%@ page import="should.ignore.this.one" %> + ]]> + jsp + + + + 1 + + ]]> + jsp + + + + 0 + + ]]> + jsp + + + + 0 + + + + ]]> + jsp + + diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoClassAttribute.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoClassAttribute.xml new file mode 100644 index 0000000000..86437e25e4 --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoClassAttribute.xml @@ -0,0 +1,25 @@ + + + + + 1 + +

Some text

+ + ]]>
+ jsp +
+ + + 0 +

text

+ ]]>
+ jsp +
+
diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoHtmlComments.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoHtmlComments.xml new file mode 100644 index 0000000000..295b4b0e67 --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoHtmlComments.xml @@ -0,0 +1,33 @@ + + + + + 0 + + ]]> + jsp + + + + 1 + + ]]> + jsp + + + + 0 + <%-- JSP Comment --%> + ]]> + jsp + + diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoInlineScript.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoInlineScript.xml new file mode 100644 index 0000000000..fe1c5ad5d0 --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoInlineScript.xml @@ -0,0 +1,53 @@ + + + + + 1 + + ]]> + jsp + + + + 1 + + ]]> + jsp + + + + 0 + + ]]> + jsp + + + + 0 + + ]]> + jsp + + + + 0 + + +; + ]]> + jsp + + + + 0 + + + + + +; + ]]> + jsp + + diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoScriptlets.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoScriptlets.xml new file mode 100644 index 0000000000..546135b7e0 --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoScriptlets.xml @@ -0,0 +1,30 @@ + + + + + 2 + + +<% response.setHeader("Pragma", "No-cache"); %> + + + String title = "Hello world!"; + + + ]]> + jsp + + + + 0 +

text

+ ]]>
+ jsp +
+
diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoUnsanitizedJSPExpression.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoUnsanitizedJSPExpression.xml new file mode 100644 index 0000000000..fa0d49c512 --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoUnsanitizedJSPExpression.xml @@ -0,0 +1,133 @@ + + + + + 1 + +${shouldBeDetectedAsXSSSecurityBreach} + ]]> + jsp + + + + 1 + + + + + ]]> + jsp + + + + 1 + + + + + ]]> + jsp + + + + 3 + +${shouldBeDetectedAsXSSSecurityBreach} + +${shouldBeDetectedAsXSSSecurityBreach} + + + + ]]> + jsp + + + c:out fixes it + 0 + + + + + ]]> + jsp + + + + fn:escape fixes it + 0 + +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +${fn:escapeXml(expression)} + ]]> + jsp + + + fn:escape as attribute value + 0 + +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + + + ]]> + jsp + + + fn:escape as attribute value, multiline + 0 + +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + + + ]]> + jsp + + + fn:escape and c:out mix + 0 + +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +${fn:escapeXml( expression )} + +${fn:escapeXml(expression)} + + + + ]]> + jsp + + diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basicjsf/xml/DontNestJsfInJstlIteration.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basicjsf/xml/DontNestJsfInJstlIteration.xml new file mode 100644 index 0000000000..1bf04b0525 --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basicjsf/xml/DontNestJsfInJstlIteration.xml @@ -0,0 +1,15 @@ + + + + + 1 +
    +
  • +
+ ]]>
+ jsp +
+
diff --git a/pmd-matlab/etc/grammar/matlab.jj b/pmd-matlab/etc/grammar/matlab.jj new file mode 100644 index 0000000000..aabbd748db --- /dev/null +++ b/pmd-matlab/etc/grammar/matlab.jj @@ -0,0 +1,168 @@ +/** + * This Matlab grammar is derived from MParser ANTLR grammar. (http://www.mit.edu/~wingated/code/mparser_0.1.tar.gz) + * + * The ' character is ambiguous, because it is the tranpose operator but can also denote the start of a string. + * (see https://www.mathworks.com/matlabcentral/newsreader/view_thread/25108) + * + * Rule of the thumb: + * + * A single quotation character is a tranpose operator if it directly follows a right bracket ("]"), right parenthesis (")"), + * right brace ("}"), letter, digit, underline ("_"), punctuation mark ("."), or another single quote character ("'"). * + * + * To implement this an extra lexical state 'TRANSPOSE' was introduced. In this state the single quote character "'" will always be parsed as the TRANSPOSE operator. + */ + +options { + BUILD_PARSER=false; + CACHE_TOKENS=true; + UNICODE_INPUT = true; +} + +PARSER_BEGIN(MatlabParser) +package net.sourceforge.pmd.lang.matlab.ast; + +import net.sourceforge.pmd.lang.ast.CharStream; +import net.sourceforge.pmd.lang.ast.TokenMgrError; + +public class MatlabParser { + +} + +PARSER_END(MatlabParser) + + SKIP : +{ + " " : DEFAULT +| + "\t" : DEFAULT +| + "\r\n" : DEFAULT +| + "\n" : DEFAULT +| + "%{" : IN_COMMENT +| + "%" : IN_LINE_COMMENT + +} + + SKIP: +{ + "%}" : DEFAULT +} + + SKIP: +{ + "\n" : DEFAULT +} + + MORE: +{ + < ~[] > +} + + TOKEN : /* SEPARATORS AND OTHER USEFULL LANGUAGE CONSTRUCTS*/ +{ + < SEMI: ";" > : DEFAULT +| < LPAREN: "(" > : DEFAULT +| < RPAREN: ")" > : TRANSPOSE +| < LBRACE: "{" > : DEFAULT +| < RBRACE: "}" > : TRANSPOSE +| < LSBRACE: "[" > : DEFAULT +| < RSBRACE: "]" > : TRANSPOSE +| < AT: "@" > : DEFAULT +| < DOT: "." > : TRANSPOSE +| < COMMA: "," > : DEFAULT +} + + TOKEN : /* OPERATORS AND ASSIGNMENTS */ +{ + < DOUBLE_EQ: "==" > : DEFAULT +| < LOG_OR: "||" > : DEFAULT +| < LOG_AND: "&&" > : DEFAULT +| < LSTE: "<=" > : DEFAULT +| < GRTE: ">=" > : DEFAULT +| < NEQ: "~=" > : DEFAULT +| < EL_TIMES: ".*" > : DEFAULT +| < EL_LEFTDIV: "./" > : DEFAULT +| < EL_RIGHTDIV: ".\\" > : DEFAULT +| < EL_EXP: ".^" > : DEFAULT +| < EL_CCT: ".'" > : DEFAULT +| < EQ: "=" > : DEFAULT +| < BIN_OR: "|" > : DEFAULT +| < BIN_AND: "&" > : DEFAULT +| < LST: "<" > : DEFAULT +| < GRT: ">" > : DEFAULT +| < COLON: ":" > : DEFAULT +| < PLUS: "+" > : DEFAULT +| < MINUS: "-" > : DEFAULT +| < NEG: "~" > : DEFAULT +| < TIMES: "*" > : DEFAULT +| < LEFTDIV: "/" > : DEFAULT +| < RIGHTDIV: "\\" > : DEFAULT +| < EXP: "^" > : DEFAULT +} + + TOKEN : /* KEYWORDS */ +{ + < BREAK: "break" > : DEFAULT +| < CASE: "case" > : DEFAULT +| < CATCH: "catch" > : DEFAULT +| < CONTINUE: "continue" > : DEFAULT +| < ELSE: "else" > : DEFAULT +| < ELSEIF: "elseif" > : DEFAULT +| < END: "end" > : DEFAULT +| < FOR: "for" > : DEFAULT +| < FUNCTION: "function" > : DEFAULT +| < GLOBAL: "global" > : DEFAULT +| < IF: "if" > : DEFAULT +| < OTHERWISE: "otherwise" > : DEFAULT +| < PERSISTENT: "persistent" > : DEFAULT +| < RETURN: "return" > : DEFAULT +| < SWITCH: "switch" > : DEFAULT +| < TRY: "try" > : DEFAULT +| < VARARGIN: "varargin" > : DEFAULT +| < WHILE: "while" > : DEFAULT +| < CLEAR: "clear" > : DEFAULT +} + + TOKEN : /* Matlab identifiers */ +{ + < ID: ( | | "_" )* > : TRANSPOSE +| < #LETTER: ["a"-"z", "A"-"Z"] > +} + + TOKEN : +{ + < INT: ( )+ > : DEFAULT +| < FLOAT: + "." ( )* ( )? + | "." ( )+ ( )? + | ( )+ + > : DEFAULT +| < #EXPONENT: ( "e" | "E" ) ( "+" | "-" )? > +| < #DIGIT: ["0"-"9"] > +} + + TOKEN : +{ + < STRING: "'" ( | "'" "'" | ~["\\","'","\n"] )* "'" > +| < #ESC_SEQ: + "\\" ( "b" | "t" | "n" | "f" | "r" | "\"" | "'" | "\\" ) + | + | + > +| < #UNICODE_ESC: "\\" "u" > +| < #OCTAL_ESC: + "\\" ["0" - "3"] + | "\\" + | "\\" + > +| < #HEX_DIGIT: ["0"-"9", "a"-"f", "A"-"F"] > +| < #OCTAL_DIGIT: ["0"-"7"] > +} + + TOKEN : +{ + < TR : "'" > : TRANSPOSE +} \ No newline at end of file diff --git a/pmd-matlab/pom.xml b/pmd-matlab/pom.xml new file mode 100644 index 0000000000..a9a84279fc --- /dev/null +++ b/pmd-matlab/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + pmd-matlab + PMD Matlab + + + net.sourceforge.pmd + pmd + 5.5.0-SNAPSHOT + + + + ${basedir}/../pmd-core + + + + + + maven-resources-plugin + + false + + ${*} + + + + + + org.apache.maven.plugins + maven-antrun-plugin + true + + + generate-sources + generate-sources + + + + + + + + + + run + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-javacc-generated-sources + + add-source + + + + ${project.build.directory}/generated-sources/javacc + + + + + + + + + + net.sourceforge.pmd + pmd-core + + + + junit + junit + test + + + net.sourceforge.pmd + pmd-test + test + + + diff --git a/pmd-matlab/src/main/ant/alljavacc.xml b/pmd-matlab/src/main/ant/alljavacc.xml new file mode 100644 index 0000000000..414a16998c --- /dev/null +++ b/pmd-matlab/src/main/ant/alljavacc.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabLanguage.java b/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabLanguage.java new file mode 100644 index 0000000000..b1bbcb017a --- /dev/null +++ b/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabLanguage.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + + +/** + * Defines the Language module for Matlab + */ +public class MatlabLanguage extends AbstractLanguage { + + /** + * Creates a new instance of {@link MatlabLanguage} with the default extensions for matlab files. + */ + public MatlabLanguage() { + super("Matlab", "matlab", new MatlabTokenizer(), ".m"); + } +} diff --git a/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabTokenizer.java b/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabTokenizer.java new file mode 100644 index 0000000000..e96726d2e1 --- /dev/null +++ b/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabTokenizer.java @@ -0,0 +1,50 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.io.Reader; +import java.io.StringReader; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.TokenManager; +import net.sourceforge.pmd.lang.ast.TokenMgrError; +import net.sourceforge.pmd.lang.matlab.MatlabLanguageModule; +import net.sourceforge.pmd.lang.matlab.ast.Token; +import net.sourceforge.pmd.util.IOUtil; + +import org.apache.commons.io.IOUtils; + +/** + * The Matlab Tokenizer. + */ +public class MatlabTokenizer implements Tokenizer { + + @Override + public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { + StringBuilder buffer = sourceCode.getCodeBuffer(); + Reader reader = null; + try { + LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(MatlabLanguageModule.NAME) + .getDefaultVersion().getLanguageVersionHandler(); + reader = new StringReader(buffer.toString()); + reader = IOUtil.skipBOM(reader); + TokenManager tokenManager = languageVersionHandler.getParser( + languageVersionHandler.getDefaultParserOptions()).getTokenManager(sourceCode.getFileName(), reader); + Token currentToken = (Token) tokenManager.getNextToken(); + while (currentToken.image.length() > 0) { + tokenEntries.add(new TokenEntry(currentToken.image, sourceCode.getFileName(), currentToken.beginLine)); + currentToken = (Token) tokenManager.getNextToken(); + } + tokenEntries.add(TokenEntry.getEOF()); + System.err.println("Added " + sourceCode.getFileName()); + } catch (TokenMgrError err) { + err.printStackTrace(); + System.err.println("Skipping " + sourceCode.getFileName() + " due to parse error"); + tokenEntries.add(TokenEntry.getEOF()); + } finally { + IOUtils.closeQuietly(reader); + } + } +} diff --git a/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabHandler.java b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabHandler.java new file mode 100644 index 0000000000..cb34b3ddae --- /dev/null +++ b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabHandler.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.matlab; + +import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler; +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.ParserOptions; +import net.sourceforge.pmd.lang.rule.RuleViolationFactory; + +/** + * Implementation of LanguageVersionHandler for the Matlab Language. + */ +public class MatlabHandler extends AbstractLanguageVersionHandler { + + @Override + public RuleViolationFactory getRuleViolationFactory() { + throw new UnsupportedOperationException("getRuleViolationFactory() is not supported for Matlab"); + } + + @Override + public Parser getParser(ParserOptions parserOptions) { + return new MatlabParser(parserOptions); + } +} diff --git a/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabLanguageModule.java b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabLanguageModule.java new file mode 100644 index 0000000000..f78322a0a4 --- /dev/null +++ b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabLanguageModule.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.matlab; + +import net.sourceforge.pmd.lang.BaseLanguageModule; + +/** + * Implementation of the Matlab Language Module. + */ +public class MatlabLanguageModule extends BaseLanguageModule { + + /** The name, that can be used to display the language in UI. */ + public static final String NAME = "Matlab"; + /** The internal name. */ + public static final String TERSE_NAME = "matlab"; + + /** + * Creates a new instance of {@link MatlabLanguageModule} with the default file extensions for Matlab. + */ + public MatlabLanguageModule() { + super(NAME, null, TERSE_NAME, null, "m"); + addVersion("", new MatlabHandler(), true); + } +} diff --git a/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabParser.java b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabParser.java new file mode 100644 index 0000000000..e7140bd4e1 --- /dev/null +++ b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabParser.java @@ -0,0 +1,49 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.matlab; + +import java.io.Reader; +import java.util.Map; + +import net.sourceforge.pmd.lang.AbstractParser; +import net.sourceforge.pmd.lang.ParserOptions; +import net.sourceforge.pmd.lang.TokenManager; +import net.sourceforge.pmd.lang.ast.AbstractTokenManager; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.ParseException; + +/** + * Adapter for the Matlab Parser. + */ +public class MatlabParser extends AbstractParser { + + /** + * Creates a new Matlab Parser. + * @param parserOptions the options + */ + public MatlabParser(ParserOptions parserOptions) { + super(parserOptions); + } + + @Override + public TokenManager createTokenManager(Reader source) { + return new MatlabTokenManager(source); + } + + @Override + public boolean canParse() { + return false; + } + + @Override + public Node parse(String fileName, Reader source) throws ParseException { + AbstractTokenManager.setFileName(fileName); + throw new UnsupportedOperationException("parse(Reader) is not supported for Matlab"); + } + + @Override + public Map getSuppressMap() { + throw new UnsupportedOperationException("getSuppressMap() is not supported for Matlab"); + } +} diff --git a/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabTokenManager.java b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabTokenManager.java new file mode 100644 index 0000000000..5b3a3e3dda --- /dev/null +++ b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabTokenManager.java @@ -0,0 +1,34 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.matlab; + +import java.io.Reader; + +import net.sourceforge.pmd.lang.TokenManager; +import net.sourceforge.pmd.lang.ast.SimpleCharStream; +import net.sourceforge.pmd.lang.matlab.ast.MatlabParserTokenManager; + +/** + * Matlab Token Manager implementation. + */ +public class MatlabTokenManager implements TokenManager { + private final MatlabParserTokenManager tokenManager; + + /** + * Creates a new Matlab Token Manager from the given source code. + * @param source the source code + */ + public MatlabTokenManager(Reader source) { + tokenManager = new MatlabParserTokenManager(new SimpleCharStream(source)); + } + + public Object getNextToken() { + return tokenManager.getNextToken(); + } + + @Override + public void setFileName(String fileName) { + MatlabParserTokenManager.setFileName(fileName); + } +} diff --git a/pmd-matlab/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-matlab/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..0db55583f2 --- /dev/null +++ b/pmd-matlab/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.cpd.MatlabLanguage diff --git a/pmd-matlab/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language b/pmd-matlab/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language new file mode 100644 index 0000000000..9e379335bd --- /dev/null +++ b/pmd-matlab/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.lang.matlab.MatlabLanguageModule diff --git a/pmd-matlab/src/site/markdown/index.md b/pmd-matlab/src/site/markdown/index.md new file mode 100644 index 0000000000..3564c505d8 --- /dev/null +++ b/pmd-matlab/src/site/markdown/index.md @@ -0,0 +1,3 @@ +# PMD Matlab + +Only CPD is supported. There are no PMD rules for Matlab. diff --git a/pmd-matlab/src/site/site.xml b/pmd-matlab/src/site/site.xml new file mode 100644 index 0000000000..74378432fd --- /dev/null +++ b/pmd-matlab/src/site/site.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pmd-matlab/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java b/pmd-matlab/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java new file mode 100644 index 0000000000..e10ebb1d70 --- /dev/null +++ b/pmd-matlab/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java @@ -0,0 +1,27 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd; + +import java.util.Arrays; +import java.util.Collection; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.matlab.MatlabLanguageModule; + +import org.junit.runners.Parameterized.Parameters; + +public class LanguageVersionTest extends AbstractLanguageVersionTest { + + public LanguageVersionTest(String name, String terseName, String version, LanguageVersion expected) { + super(name, terseName, version, expected); + } + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + { MatlabLanguageModule.NAME, MatlabLanguageModule.TERSE_NAME, "", LanguageRegistry.getLanguage(MatlabLanguageModule.NAME).getDefaultVersion() } + }); + } +} diff --git a/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java b/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java new file mode 100644 index 0000000000..8d97ecfa49 --- /dev/null +++ b/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java @@ -0,0 +1,36 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.io.IOException; + +import net.sourceforge.pmd.testframework.AbstractTokenizerTest; + +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; + + +public class MatlabTokenizerTest extends AbstractTokenizerTest { + + private static final String FILENAME = "sample-matlab.m"; + + @Before + @Override + public void buildTokenizer() throws IOException { + this.tokenizer = new MatlabTokenizer(); + this.sourceCode = new SourceCode(new SourceCode.StringCodeLoader(this.getSampleCode(), FILENAME)); + } + + @Override + public String getSampleCode() throws IOException { + return IOUtils.toString(MatlabTokenizer.class.getResourceAsStream(FILENAME)); + } + + @Test + public void tokenizeTest() throws IOException { + this.expectedTokenCount = 3925; + super.tokenizeTest(); + } +} diff --git a/pmd-matlab/src/test/resources/net/sourceforge/pmd/cpd/sample-matlab.m b/pmd-matlab/src/test/resources/net/sourceforge/pmd/cpd/sample-matlab.m new file mode 100644 index 0000000000..54abfa4118 --- /dev/null +++ b/pmd-matlab/src/test/resources/net/sourceforge/pmd/cpd/sample-matlab.m @@ -0,0 +1,1065 @@ +% Example source code copied from the Chebfun project on GitHub: +% https://github.com/chebfun/chebfun/blob/development/@chebfun/chebfun.m + +classdef chebfun +%CHEBFUN CHEBFUN class for representing functions on [a,b]. +% +% Class for approximating functions defined on finite, semi-infinite, or +% doubly-infinite intervals [a,b]. Functions may be smooth, piecewise smooth, +% weakly singular, or blow up on the interval. +% +% CHEBFUN(F) constructs a CHEBFUN object representing the function F on the +% interval [-1,1]. F may be a string, e.g., 'sin(x)', a function handle, e.g., +% @(x) x.^2 + 2*x + 1, or a vector of numbers. In the first two instances, F +% should be "vectorized" in the sense that it may be evaluated at a column +% vector of points x(:) in [-1,1] and return an output of size NxM where N = +% length(x(:)). If this is not possible then the flag CHEBFUN(F, 'vectorize') +% should be passed. CHEBFUN(F, 'vectorcheck', 'off') disables the automatic +% checking for vector input. Additionally, F may be a CHEBFUN, in which case +% CHEBFUN(F) is equivalent to CHEBFUN(@(X) FEVAL(F, X)). CHEBFUN() returns an +% empty CHEBFUN object. +% +% CHEBFUN(F, [A, B]) specifies an interval [A,B] on which the CHEBFUN is +% defined, where A and/or B may be infinite. CHEBFUN(F, ENDS), where ENDS is a +% 1x(K+1) vector of unique ascending values, specifies a piecewise smooth +% CHEBFUN defined on the interval [ENDS(1), ENDS(K+1)] with additional interior +% breaks at ENDS(2), ..., ENDS(K). Specifying these breaks can be particularly +% useful if F is known to have discontinuities. For example, +% CHEBFUN(@(x) abs(x), [-1, 0, 1]). +% If a domain is passed to the constructor, it should always be the 2nd input. +% +% CHEBFUN(A) or CHEBFUN(A, 'chebkind', 2), where A is an Nx1 matrix, constructs +% a CHEBFUN object which interpolates the data in A on an N-point Chebyshev grid +% of the second kind (see >> help chebpts). CHEBFUN(A, 'chebkind', 1) and +% CHEBFUN(A, 'equi') are similar, but here the data is assumed to come from a +% 1st-kind Chebyshev or equispaced grid linspace(-1, 1, N), respectively. (In +% the latter case, a smooth interpolant is constructed using an adaptive +% Floater-Hormann scheme [Numer. Math. 107, 315-331 (2007)].). CHEBFUN(F, N) or +% CHEBFUN(F, N, 'chebkind', 2) is equivalent to CHEBFUN(feval(F, chebpts(N)). +% +% CHEBFUN(C, 'coeffs'), where C is an Nx1 matrix, constructs a CHEBFUN object +% representing the polynomial C(1) T_0(x) + ... + C(N) T_(N-1)(x), +% where T_K(x) denotes the K-th Chebyshev polynomial. This is equivalent to +% CHEBFUN({{[], C}}). C may also be an NxM matrix, as described below. +% +% CHEBFUN(F, ...), where F is an NxM matrix or an array-valued function handle, +% returns an "array-valued" CHEBFUN. For example, +% CHEBFUN(rand(14, 2)) +% or +% CHEBFUN(@(x) [sin(x), cos(x)]) +% Note that each column in an array-valued CHEBFUN object is discretized in the +% same way (i.e., the same breakpoint locations and the same underlying +% representation). For more details see ">> help quasimatrix". Note the +% difference between +% CHEBFUN(@(x) [sin(x), cos(x)], [-1, 0, 1]) +% and +% CHEBFUN({@(x) sin(x), @(x) cos(x)}, [-1, 0, 1]). +% The former constructs an array-valued CHEBFUN with both columns defined on the +% domain [-1, 0, 1]. The latter defines a single column CHEBFUN which represents +% sin(x) in the interval [-1, 0) and cos(x) on the interval (0, 1]. +% +% CHEBFUN({F1,...,Fk}, ENDS) constructs a piecewise smooth CHEBFUN which +% represents Fj on the interval [ENDS(j), END(j+1)]. Each entry Fj may be a +% string, function handle, or vector of doubles. For example +% CHEBFUN({@(x) sin(x), @(x) cos(x)}, [-1, 0, 1]) +% +% CHEBFUN(F, PREF) or CHEBFUN(F, [A, B], PREF) constructs a CHEBFUN object from +% F with the options determined by the CHEBFUNPREF object PREF. Construction +% time options may also be passed directly to the constructor in the form +% CHEBFUN(F, [A, B], PROP1, VAL1, PROP2, VAL2, ...). (See CHEBFUNPREF for +% details of the various preference options and their defaults.). In +% particular, CHEBFUN(F, 'splitting', 'on') allows the constructor to +% adaptively determine breakpoints to better represent piecewise smooth +% functions F. For example, +% CHEBFUN(@(x) sign(x - .3), [-1, 1], 'splitting', 'on') +% CHEBFUN(F, 'extrapolate', 'on') prevents the constructor from evaluating the +% function F at the endpoints of the domain. +% +% If PROP/VAL and PREF inputs are mixed in a single constructor call, the +% preferences determined by the PROP/VAL inputs take priority over those +% determined by PREF. At most one PREF input may be supplied to the +% constructor at any time. +% +% CHEBFUN(F, 'trunc', N) returns a smooth N-point CHEBFUN constructed by +% computing the first N Chebyshev coefficients from their integral form, rather +% than by interpolation at Chebyshev points. +% +% CHEBFUN(F, 'trig') constructs a CHEBFUN object representing a smooth and +% periodic function F on the interval [-1,1]. The resulting CHEBFUN is +% represented using a Fourier series. All operations done on F should preserve +% smoothness and periodicity, otherwise results are casted into chebfuns +% represented by Chebyshev rather than Fourier series. Similar options +% as discussed above may be combined with the 'trig' flag, with exception to +% the 'chebkind' and 'splitting' flags. +% +% CHEBFUN(F, 'periodic') is the same as CHEBFUN(F, 'trig'). +% +% CHEBFUN --UPDATE can be used to update to the latest stable release of CHEBFUN +% (obviously an internet connection is required!). CHEBFUN --UPDATE-DEVEL will +% update to the latest development release, but we recommend instead that you +% checkout from the Github repo https://github.com/chebfun/chebfun/. See +% CHEBFUN.UPDATE() for further details. +% +% See also CHEBFUNPREF, CHEBPTS. + +% Copyright 2014 by The University of Oxford and The Chebfun Developers. +% See http://www.chebfun.org/ for Chebfun information. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% CHEBFUN Class Description: +% +% The CHEBFUN class is for representations of piecewise functions on the +% interval [a,b]. +% +% The CHEBFUN class is the main user interface. We do not expect users to +% directly invoke any objects below this level. +% +% A CHEBFUN object consists of a collection of FUN objects. There are two main +% tasks for the CHEBFUN constructor: (1) parse the user input, and (2) correctly +% piece together FUN objects to form a global approximation. If the input +% function is globally smooth then the resulting CHEBFUN contains a single FUN +% object. If the input is not smooth, or breakpoints are passed to the +% constructor, CHEBFUN must determine appropriate breakpoints and return a +% piecewise smooth CHEBFUN with multiple FUN objects. +% +% This is a user-level class, and all input arguments should be thoroughly +% sanity checked. +% +% Class diagram: [ADchebfun] <>-- [CHEBFUN] <>-- [<>] +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% CLASS PROPERTIES: + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + properties (Access = public) + % DOMAIN of definition of a CHEBFUN object. If K = length(F.DOMAIN) is + % greater than 1 then the CHEBFUN is referred to as a "piecewise". + % CHEBFUN. The first and last values of this vector define the left and + % right endpoints of the domain, respectively. The other values give the + % locations of the interior breakpoints that define the domains of the + % individual FUN objects comprising the CHEBFUN. The entries in this + % vector should be strictly increasing. + domain % (1x(K+1) double) + + % FUNS is a cell array containing the FUN objects that comprise a + % piecewise CHEBFUN. The kth entry in this cell is the FUN defining + % the representation used by the CHEBFUN object on the open interval + % (F.DOMAIN(k), F.DOMAIN(k+1)). If M = size(f.funs, 2) is greater than + % 1, then the CHEBFUN object is referred to as "array valued". + funs % (Kx1 cell array of FUN objects) + + % POINTVALUES Values of the function at the break points. + pointValues = []; % (1 x (K+1) double) + + % ISTRANSPOSED determines whether a (possibly array-valued) CHEBFUN F + % should be interpreted as a collection of "column" CHEBFUN objects (if + % F.ISTRANSPOSED == 0, the default), which are considered (infxM) + % arrays, or "row" CHEBFUN objects (if F.ISTRANSPOSED == 1), which are + % (Mxinf) arrays. This difference is only behavioral; the other + % properties described above are _NOT_ stored differently if this flag + % is set.) + isTransposed = 0; % (logical) + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% CLASS CONSTRUCTOR: + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + methods ( Access = public, Static = false ) + + function f = chebfun(varargin) + % The main CHEBFUN constructor! + + % Return an empty CHEBFUN: + if ( (nargin == 0) || isempty(varargin{1}) ) + return + end + + if ( iscell(varargin{1}) && ... + all(cellfun(@(x) isa(x, 'fun'), varargin{1})) ) + % Construct a CHEBFUN from a cell array of FUN objects. + % Note, this is not affected by the input parser (see error + % message below) and must be _fast_ as it is done often. + if ( nargin > 1 ) + error('CHEBFUN:CHEBFUN:chebfun:nargin', ... + 'Only one input is allowed when passing an array of FUNs.') + end + % Assign the cell to the .FUNS property: + f.funs = varargin{1}; + % Collect the domains together: + dom = cellfun(@(fun) get(fun, 'domain'), f.funs, ... + 'uniformOutput', false); + f.domain = unique([dom{:}]); + % Update values at breakpoints (first row of f.pointValues): + f.pointValues = chebfun.getValuesAtBreakpoints(f.funs, f.domain); + return + end + + % Parse inputs: + [op, dom, data, pref] = parseInputs(varargin{:}); + + if ( strcmp(op, 'done') ) + % An update was performed. Exit gracefully: + throwAsCaller(MException('', '')) + end + + % Deal with 'trunc' option: + doTrunc = false; + truncLength = NaN; + for k = 1:length(varargin) + if ( strcmpi(varargin{k}, 'trunc') ) + doTrunc = true; + truncLength = varargin{k+1}; + break + end + end + + if ( isa(op, 'chebfun') && doTrunc ) + % Deal with the particular case when we're asked to truncate a + % CHEBFUN: + f = op; + + else + % Construct from function_handle, numeric, or string input: + + % Call the main constructor: + [f.funs, f.domain] = chebfun.constructor(op, dom, data, pref); + + % Update values at breakpoints (first row of f.pointValues): + f.pointValues = chebfun.getValuesAtBreakpoints(f.funs, ... + f.domain, op); + + % Remove unnecessary breaks (but not those that were given): + [ignored, index] = setdiff(f.domain, dom); + f = merge(f, index(:).', pref); + + end + + if ( doTrunc ) + % Truncate the CHEBFUN to the required length: + if ( isa( pref.tech(),'chebtech' ) ) + c = chebcoeffs(f, truncLength); + else + c = trigcoeffs(f, truncLength); + end + f = chebfun(c, f.domain([1,end]), 'coeffs', pref); + end + + end + + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% CLASS METHODS: + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + methods ( Access = public, Static = false ) + + % Absolute value of a CHEBFUN. + f = abs(f, pref) + + % True if any element of a CHEBFUN is a nonzero number, ignoring NaN. + a = any(f, dim) + + % Compute the length of the arc defined by a CHEBFUN. + out = arcLength(f, a, b) + + % Solve boundary value problems for ODEs by collocation. + [y, t] = bvp4c(fun1, fun2, y0, varargin); + + % Solve boundary value problems for ODEs by collocation. + [y, t] = bvp5c(fun1, fun2, y0, varargin); + + % Round a CHEBFUN towards plus infinity. + g = ceil(f) + + % Plot information regarding the representation of a CHEBFUN object: + h = plotcoeffs(f, varargin); + + % Construct complex CHEBFUN from real and imaginary parts. + C = complex(A, B) + + % Compose CHEBFUN objects with another function. + h = compose(f, op, g, pref) + + % Complex conjugate of a CHEBFUN. + f = conj(f) + + % Complex transpose of a CHEBFUN. + f = ctranspose(f) + + % Display a CHEBFUN object. + display(f); + + % Accuracy estimate of a CHEBFUN object. + out = epslevel(f, flag); + + % Evaluate a CHEBFUN. + y = feval(f, x, varargin) + + % Round a CHEBFUN towards zero. + g = fix(f); + + % Round a CHEBFUN towards minus infinity. + g = floor(f); + + % Get properties of a CHEBFUN object. + out = get(f, prop, simpLevel); + + % Horizontal scale of a CHEBFUN object. + out = hscale(f); + + % Imaginary part of a CHEBFUN. + f = imag(f) + + % True for an empty CHEBFUN. + out = isempty(f) + + % Test if CHEBFUN objects are equal. + out = isequal(f, g) + + % Test if a CHEBFUN is bounded. + out = isfinite(f) + + % Test if a CHEBFUN is unbounded. + out = isinf(f) + + % Test if a CHEBFUN has any NaN values. + out = isnan(f) + + % True for real CHEBFUN. + out = isreal(f); + + % Test if a CHEBFUN object is built upon DELTAFUN. + out = isdelta(f); + + % Test if a CHEBFUN object is built upon SINGFUN. + out = issing(f) + + % Test if a CHEBFUN object is built upon a basis of periodic + % functions, i.e., a periodic TECH. + out = isPeriodicTech(f) + + % True for zero CHEBFUN objects. + out = iszero(f) + + % Kronecker product of two CHEBFUN object. + out = kron(f, g) + + % Length of a CHEBFUN. + [out, out2] = length(f); + + % Return Legendre coefficients of a CHEBFUN. + c_leg = legpoly(f, n) + + % Plot a CHEBFUN object on a loglog scale: + h = loglog(f, varargin); + + % Subtraction of two CHEBFUN objects. + f = minus(f, g) + + % Multiplication of CHEBFUN objects. + f = mtimes(f, c) + + % Remove unnecessary breakpoints in from a CHEBFUN. + [f, mergedPts] = merge(f, index, pref) + + % Overlap the domain of two CHEBFUN objects. + [f, g] = overlap(f, g) + + % Plot a CHEBFUN object: + varargout = plot(f, varargin); + + % 3-D plot for CHEBFUN objects. + varargout = plot3(f, g, h, varargin) + + % Power of a CHEBFUN + f = power(f, b, pref); + + % Real part of a CHEBFUN. + f = real(f) + + % Restrict a CHEBFUN object to a subdomain. + f = restrict(f, newDomain); + + % The roots of the CHEBFUN F. + r = roots(f, varargin); + + % Round a CHEBFUN towards nearest integer. + g = round(f) + + % Plot a CHEBFUN object on a log-linear scale: + h = semilogx(f, varargin); + + % Plot a CHEBFUN object on a linear-log scale: + h = semilogy(f, varargin); + + % Signum of a CHEBFUN. + f = sign(f, pref) + + % Simplify the representation of a CHEBFUN object. + f = simplify(f, tol); + + % Size of a CHEBFUN object. + [s1, s2] = size(f, dim); + + % Square root of a CHEBFUN. + f = sqrt(f, pref) + + % Retrieve and modify preferences for this class. + varargout = subsref(f, index); + + % Retrieve and modify preferences for this class. + varargout = subsasgn(f, varargin); + + % CHEBFUN multiplication. + f = times(f, g, varargin) + + % Transpose a CHEBFUN. + f = transpose(f) + + % Unary minus of a CHEBFUN. + f = uminus(f) + + % Unary plus of a CHEBFUN. + f = uplus(f) + + % Vertical scale of a CHEBFUN object. + out = vscale(f, s); + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% HIDDEN METHODS: + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + methods ( Hidden = true, Static = false ) + + % Add breakpoints to the domain of a CHEBFUN. + f = addBreaks(f, breaks, tol) + + % Add breaks at appropriate roots of a CHEBFUN. + f = addBreaksAtRoots(f, tol) + + % Assign columns (or rows) of an array-valued CHEBFUN. + f = assignColumns(f, colIdx, g) + + % Convert a CHEBFUN to another TECH. + f = changeTech(f, newtech); + + % Deprecated function. + f = define(f,s,v); + + % Supply a new definition for a CHEBFUN on a subinterval. + f = defineInterval(f, subInt, g) + + % Supply new definition for a CHEBFUN at a point or set of points. + f = definePoint(f, s, v) + + % Multiplication operator. + M = diag(f) + + % Useful information for DISPLAY. + [name, data] = dispData(f) + + % Compare domains of two CHEBFUN objects. + pass = domainCheck(f, g); + + % Extract columns of an array-valued CHEBFUN object. + f = extractColumns(f, columnIndex); + + % Deprecated function. + varargin = fzero(varargout); + + % Get Delta functions within a CHEBFUN. + [deltaMag, deltLoc] = getDeltaFunctions(f); + + % Get roots of a CHEBFUN and polish for use as breakpoints. + [rBreaks, rAll] = getRootsForBreaks(f, tol) + + % Returns true if numel(f) > 1 + out = isQuasi(f) + + % Number of columns (or rows) of a CHEBFUN quasimatrix. + out = numColumns(f) + + % Obtain data used for plotting a CHEBFUN object: + data = plotData(f, g, h) + + % Deprecated function. + varargin = quad(varargout); + + % Set pointValues property: + f = setPointValues(f, j, k, vals) + + % Remove all-zero layers of higher-order impulses. + f = tidyImpulses(f) + + % Adjust nearby common break points in domains of CHEBFUN objects. + [f, g, newBreaksLocF, newBreaksLocG] = tweakDomain(f, g, tol, pos) + + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% PRIVATE METHODS: + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + methods ( Access = private, Static = false ) + % Set small breakpoint values to zero. + f = thresholdBreakpointValues(f); + end + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% STATIC METHODS: + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + methods ( Access = public, Static = true ) + + % Discrete cosine transform: + y = dct(u, kind); + + % Inverse discrete cosine transform: + u = idct(y, kind); + + % Discrete sine transform: + y = dst(u, kind); + + % Inverse discrete sine transform: + u = idst(y, kind); + + % Interpolate data: + f = interp1(x, y, method, dom); + + % Compute Lagrange basis functions for a given set of points. + f = lagrange(x, varargin); + + % ODE113 with CHEBFUN output. + [t, y] = ode113(varargin); + + % ODE15S with CHEBFUN output. + [t, y] = ode15s(varargin); + + % ODE45 with CHEBFUN output. + [t, y] = ode45(varargin); + + % Cubic Hermite interpolation: + f = pchip(x, y, method); + + % Cubic spline interpolant: + f = spline(x, y, d); + + % Update Chebfun source files: + update(varargin) + + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% HIDDEN STATIC METHODS: + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + methods ( Hidden = true, Static = true ) + + % Convert a cell array of CHEBFUN objects to a quasimatrix. + G = cell2quasi(F) + + % Determine values of CHEBFUN at breakpoints. + vals = getValuesAtBreakpoints(funs, ends, op); + + % Which interval is a point in? + out = whichInterval(dom, x, direction); + + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% PRIVATE STATIC METHODS: + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + methods ( Access = private, Static = true ) + + % Main constructor. + [funs, ends] = constructor(op, domain, data, pref); + + % Convert ODE solutions into CHEBFUN objects: + [y, t] = odesol(sol, dom, opt); + + % Parse inputs to PLOT. Extract 'lineWidth', etc. + [lineStyle, pointStyle, jumpStyle, deltaStyle, out] = ... + parsePlotStyle(varargin) + + end + +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Class-related functions: private utilities for this m-file. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function op = str2op(op) + % Convert string inputs to either numeric format or function_handles. + sop = str2num(op); %#ok % STR2DOUBLE doesn't support str2double('pi') + if ( ~isempty(sop) ) + op = sop; + else + depVar = symvar(op); + if ( numel(depVar) ~= 1 ) + error('CHEBFUN:CHEBFUN:str2op:indepvars', ... + 'Incorrect number of independent variables in string input.'); + end + op = eval(['@(' depVar{:} ')', op]); + end +end + +function [op, dom, data, pref] = parseInputs(op, varargin) + + % TODO: Should we 'data' structure to be passed to the constructor? + % Currently, like in CHEBFUN/COMPOSE(), we don't have a use for this, but it + % might be useful in the future. + + % Deal with string input options. + if ( strncmp(op, '--', 2) ) + % An option has been passed to the constructor. + if ( strcmpi(op, '--update') ) + chebfun.update(); + elseif ( strcmpi(op, '--update-devel') ) + chebfun.update('development'); + elseif ( strcmpi(op, '--version') ) + installDir = chebfunroot(); + fid = fopen(fullfile(installDir, 'Contents.m'), 'r'); + fgetl(fid); + str = fgetl(fid); + disp(['Chebfun ', str(3:end)]); + fclose(fid); + else + error('CHEBFUN:parseInputs:unknown', ... + 'Unknow command %s.', op); + end + op = 'done'; + dom = []; + data = struct(); + pref = []; + return + end + + % Initialize data output. + data.hscale = []; + data.vscale = []; + data.exponents = []; + data.singType = []; + + args = varargin; + + % An op-only constructor call. + if ( nargin == 1 ) + pref = chebfunpref(); + end + + % Try to parse out the domain which, if passed, is the second argument. + domainWasPassed = false; + if ( ~isempty(args) ) + if ( isnumeric(args{1}) && ... + ((length(args{1}) >= 2) || isempty(args{1})) ) + dom = args{1}; + args(1) = []; + domainWasPassed = true; + elseif ( isa(args{1}, 'domain') ) + dom = double(args{1}); + args(1) = []; + domainWasPassed = true; + end + end + + % A struct to hold any preferences supplied by keyword (name-value pair). + keywordPrefs = struct(); + + % Parse the remaining arguments. + prefWasPassed = false; + isPeriodic = false; + vectorize = false; + doVectorCheck = true; + while ( ~isempty(args) ) + if ( isstruct(args{1}) || isa(args{1}, 'chebfunpref') ) + % Preference object input. (Struct inputs not tied to a keyword + % are interpreted as preference objects.) + if ( ~prefWasPassed ) + pref = chebfunpref(args{1}); + prefWasPassed = true; + args(1) = []; + else + error('CHEBFUN:CHEBFUN:parseInputs:twoPrefs', ... + 'Multiple preference inputs are not allowed.'); + end + elseif ( strcmpi(args{1}, 'equi') ) + % Enable FUNQUI when dealing with equispaced data. + keywordPrefs.tech = 'funqui'; + args(1) = []; + elseif ( strcmpi(args{1}, 'vectorize') || ... + strcmpi(args{1}, 'vectorise') ) + % Vectorize flag for function_handles. + vectorize = true; + args(1) = []; + elseif ( strcmpi(args{1}, 'novectorcheck') ) + % Vector check for function_handles. + doVectorCheck = false; + args(1) = []; + elseif ( strcmpi(args{1}, 'coeffs') && isnumeric(op) ) + % Hack to support construction from coefficients. + op = {{[], op}}; + args(1) = []; + elseif ( any(strcmpi(args{1}, {'periodic', 'trig'})) ) + isPeriodic = true; + args(1) = []; + elseif ( strcmpi(args{1}, 'coeffs') && iscell(op) ) + error('CHEBFUN:CHEBFUN:parseInputs:coeffcell', ... + 'Cannot construct CHEBFUN from a cell array of coefficients.'); + elseif ( strcmpi(args{1}, 'trunc') ) + % Pull out this preference, which is checked for later. + keywordPrefs.splitting = true; + args(1:2) = []; + elseif ( isnumeric(args{1}) && isscalar(args{1}) ) + % g = chebfun(@(x) f(x), N) + keywordPrefs.techPrefs.fixedLength = args{1}; + args(1) = []; + elseif ( strcmpi(args{1}, 'splitting') ) + keywordPrefs.splitting = strcmpi(args{2}, 'on'); + args(1:2) = []; + elseif ( strcmpi(args{1}, 'minsamples') ) + % Translate "minsamples" --> "techPrefs.minSamples". + keywordPrefs.techPrefs.minSamples = args{2}; + args(1:2) = []; + elseif ( strcmpi(args{1}, 'blowup') ) + if ( strcmpi(args{2}, 'off') ) + % If 'blowup' is 'off'. + keywordPrefs.blowup = 0; + else + % If 'blowup' is not 'off', set the singTypes. (NB: These + % cells really need to store a left and right singType for each + % domain subinterval, but we may not know the domain yet, so we + % store just one cell for now and replicate it later, after + % we've figured out the domain.) + if ( (isnumeric(args{2}) && args{2} == 1 ) || ... + strcmpi(args{2}, 'on') ) + % Translate "blowup" and flag "1" --> + % "blowup" and "poles only". + keywordPrefs.blowup = 1; + data.singType = {'pole'}; + elseif ( args{2} == 2 ) + % Translate "blowup" and flag "2" --> + % "blowup" and "fractional singularity". + keywordPrefs.blowup = 1; + data.singType = {'sing'}; + else + error('CHEBFUN:CHEBFUN:parseInputs:badBlowupOption', ... + 'Invalid value for ''blowup'' option.'); + end + end + args(1:2) = []; + elseif ( strcmpi(args{1}, 'vscale') ) + % Store vscale types. + data.vscale = args{2}; + args(1:2) = []; + elseif ( strcmpi(args{1}, 'hscale') ) + % Store vscale types. + data.vscale = args{2}; + args(1:2) = []; + elseif ( strcmpi(args{1}, 'singType') ) + % Store singularity types. + data.singType = args{2}; + args(1:2) = []; + elseif ( strcmpi(args{1}, 'exps') ) + % Store exponents. + data.exponents = args{2}; + args(1:2) = []; + elseif ( any(strcmpi(args{1}, 'chebkind')) ) + % Translate "chebkind" and "kind" --> "tech.@chebtech". + if ( (isnumeric(args{2}) && (args{2} == 1)) || ... + (ischar(args{2}) && strncmpi(args{2}, '1st', 1)) ) + keywordPrefs.tech = @chebtech1; + elseif ( (isnumeric(args{2}) && (args{2} == 2)) || ... + (ischar(args{2}) && strncmpi(args{2}, '2nd', 1)) ) + keywordPrefs.tech = @chebtech2; + else + error('CHEBFUN:CHEBFUN:parseInputs:badChebkind', ... + 'Invalid value for ''chebkind'' option.'); + end + args(1:2) = []; + elseif ( strcmpi(args{1}, 'resampling') ) + % Translate "resampling" --> "techPrefs.refinementFunction". + if ( strcmpi(args{2}, 'on') ) + keywordPrefs.techPrefs.refinementFunction = 'resampling'; + elseif ( strcmpi(args{2}, 'off') ) + keywordPrefs.techPrefs.refinementFunction = 'nested'; + end + args(1:2) = []; + elseif ( strcmpi(args{1}, 'maxdegree') ) + % Translate "maxdegree" --> "techPrefs.maxLength". + keywordPrefs.techPrefs.maxLength = args{2}; + args(1:2) = []; + elseif ( any(strcmpi(args{1}, {'splitLength', 'splitdegree'})) ) + % Translate "splitdegree" --> "splitPrefs.splitLength". + keywordPrefs.splitPrefs.splitLength = args{2}; + args(1:2) = []; + elseif ( strcmpi(args{1}, 'splitMaxLength') ) + % Translate "splitMaxLength" --> "splitPrefs.splitMaxLength". + keywordPrefs.splitPrefs.splitMaxLength = args{2}; + args(1:2) = []; + elseif ( ischar(args{1}) ) + % Update these preferences: + if ( length(args) < 2 ) + error('CHEBFUN:CHEBFUN:parseInputs:noPrefValue', ... + ['Value for ''' args{1} ''' preference was not supplied.']); + end + keywordPrefs.(args{1}) = args{2}; + args(1:2) = []; + else + if ( isnumeric(args{1}) ) + error('CHEBFUN:CHEBFUN:parseInputs:badInputNumeric', ... + ['Could not parse input argument sequence.\n' ... + '(Perhaps the construction domain is not the second ' ... + 'argument?)']); + else + error('CHEBFUN:CHEBFUN:parseInputs:badInput', ... + 'Could not parse input argument sequence.'); + end + end + end + + % Override preferences supplied via a preference object with those supplied + % via keyword. + if ( prefWasPassed ) + pref = chebfunpref(pref, keywordPrefs); + else + pref = chebfunpref(keywordPrefs); + end + + % Use the domain of the chebfun that was passed if none was supplied. + if ( ~domainWasPassed || isempty(dom) ) + if ( isa(op, 'chebfun') ) + dom = [ op.domain(1) op.domain(end) ]; + else + dom = pref.domain; + end + end + numIntervals = numel(dom) - 1; + + % Deal with the 'periodic' or 'trig' flag: + if ( isPeriodic ) + % Translate 'periodic' or 'trig'. + pref.tech = @trigtech; + pref.splitting = false; + if ( numel(dom) > 2 ) + error('CHEBFUN:parseInputs:periodic', ... + '''periodic'' or ''trig'' option is only supported for smooth domains.'); + end + end + + % Parse the OP (handle the vectorize flag, etc.). + if ( iscell(op) ) + for k = 1:numel(op) + op{k} = parseOp(op{k}); + end + else + op = parseOp(op); + end + + function op = parseOp(op) + % Convert string input to function_handle: + if ( ischar(op) ) + op = str2op(op); + end + if ( doVectorCheck && isa(op, 'function_handle') ) + op = vectorCheck(op, dom, vectorize); + end + if ( isa(op, 'chebfun') ) + op = @(x) feval(op, x); + end + if ( isa(op, 'function_handle') && strcmp(pref.tech, 'funqui') ) + if ( isfield(pref.techPrefs, 'fixedLength') && ... + ~isnan(pref.techPrefs.fixedLength) ) + x = linspace(dom(1), dom(end), pref.techPrefs.fixedLength).'; + op = feval(op, x); + pref.techPrefs.fixedLength = NaN; + end + end + end + + % Enable singularity detection if we have exponents or singTypes: + if ( any(data.exponents) || ~isempty(data.singType) ) + pref.blowup = true; + end + % Sort out the singularity types: + if ( numel(data.singType) == 1 ) + singType = data.singType{1}; + data.singType = cell(1, 2*numIntervals); + for j = 1:2*numIntervals + data.singType{j} = singType; + end + elseif ( ~isempty(data.singType) && ... + (numel(data.singType) ~= 2*numIntervals) ) + % If the number of exponents supplied by user isn't equal to twice the + % the number of the FUNs, throw an error message: + error('CHEBFUN:CHEBFUN:parseInputs:badExponents', ... + 'The number of the exponents is inappropriate.'); + end + % Sort out the exponents: + if ( ~isempty(data.exponents) ) + exps = data.exponents; + nExps = numel(exps); + if ( nExps == 1 ) + % If only one exponent is supplied, assume the exponent at other + % breakpoints are exactly same. + exps = exps*ones(1, 2*numIntervals); + elseif ( nExps == 2 ) + % If the exponents are only supplied at endpoints of the entire + % domain, then pad zeros at the interior breakpoints. + exps = [exps(1) zeros(1, 2*(numIntervals-1)) exps(2)]; + elseif ( nExps == numIntervals + 1 ) + % If only one exponent is supplied for each interior breakpoint, + % then we assume that the singularity take the same order on each + % side. + exps = exps(ceil(1:0.5:nExps - 0.5)); + elseif( nExps ~= 2*numIntervals ) + % The number of exponents supplied by user makes no sense. + error('CHEBFUN:CHEBFUN:chebfun:parseInputs', ... + 'Invalid length for vector of exponents.'); + end + data.exponents = exps; + end + + % Ensure DOM is a double (i.e., not a domain object). + dom = double(dom); + +end + +function op = vectorCheck(op, dom, vectorize) +%VECTORCHECK Try to determine whether op is vectorized. +% It's impossible to cover all eventualities without being too expensive. +% We do the best we can. "Do. Or do no. There is not try." + +% Make a slightly narrower domain to evaluate on. (Endpoints can be tricky). +y = dom([1 end]); + +if ( y(1) > 0 ) + y(1) = 1.01*y(1); +else + y(1) = .99*y(1); +end + +if ( y(end) > 0 ) + y(end) = .99*y(end); +else + y(end) = 1.01*y(end); +end + +y = y(:); + +if ( vectorize ) + op = vec(op, y(1)); +end + +try + % Evaluate a vector of (near the) endpoints + v = op(y); + + % Get the size of the output: + sv = size(v); + sy = size(y); + + % Check the sizes: + if ( sv(1) == sy(1) ) + % Here things seem OK! + + % However, we may possibly be fooled if we have an array-valued function + % whose number of columns equals the number of test points(i.e., 2). We + % choose one additional point as a final check: + if ( sv(2) == sy(1) ) + v = op(y(1)); + if ( size(v, 1) > 1 ) + op = @(x) op(x).'; + warning('CHEBFUN:CHEBFUN:vectorCheck:transpose',... + ['Chebfun input should return a COLUMN array.\n', ... + 'Attempting to transpose.']) + end + end + + elseif ( all( sv == 1 ) ) + % The operator always returns a scalar: + op = @(x) repmat(op(x), length(x), 1); + + elseif ( any(sv == sy(1)) ) + + if ( any(sv) == 1 ) + % We check to see if we have something like @(x) [1 1]. + v = op(y(1)); % Should evaluate to a scalar, unless array-valued. + if ( all(size(v) == sv) ) + % Scalar expand: + op = @(x) repmat(op(x), length(x), 1); + return + end + end + + % Try and transpose: + op = @(x) op(x).'; + warning('CHEBFUN:CHEBFUN:vectorCheck:transpose',... + ['Chebfun input should return a COLUMN array.\n', ... + 'Attempting to transpose.']) + + elseif ( any(sv == 1) ) + % The operator always returns a scalar: + op = @(x) repmat(op(x), length(x), 1); + + end + +catch ME + % The above didn't work. :( + + if ( vectorize ) + % We've already tried vectorizing, so we've failed. + rethrow(ME) + + else + % Try vectorizing. + op = vectorCheck(op, dom, 1); + warning('CHEBFUN:CHEBFUN:vectorcheck:vectorize',... + ['Function failed to evaluate on array inputs.\n',... + 'Vectorizing the function may speed up its evaluation\n',... + 'and avoid the need to loop over array elements.\n',... + 'Use ''vectorize'' flag in the CHEBFUN constructor call\n', ... + 'to avoid this warning message.']) + + end + +end + +end + +function g = vec(op, y) +%VEC Vectorize a function or string expression. +% VEC(OP, Y), if OP is a function handle or anonymous function, returns a +% function that returns vector outputs for vector inputs by wrapping F inside +% a loop. Y, serving as a testing point, is a point in the domain where OP is +% defined and is used to determine if OP is array-valued or not. + + % Check to see if OP is array-valued: + opy = op(y); + if ( any(size(opy) > 1) ) + % Use the array-valued wrapper: + g = @loopWrapperArray; + else + % It's not array-valued. Use the scalar wrapper: + g = @loopWrapperScalar; + end + + % Nested functions: + + % Scalar case: + function v = loopWrapperScalar(x) + v = zeros(size(x)); + for j = 1:numel(v) + v(j) = op(x(j)); + end + end + + % Array-valued case: + function v = loopWrapperArray(x) + numCol = size(op(x(1)), 2); + numRow = size(x, 1); + v = zeros(numRow, numCol); + for j = 1:numRow + v(j,:) = op(x(j)); + end + end + +end \ No newline at end of file diff --git a/pmd-php/pom.xml b/pmd-php/pom.xml new file mode 100644 index 0000000000..ee03a0a9c2 --- /dev/null +++ b/pmd-php/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + pmd-php + PMD PHP + + + net.sourceforge.pmd + pmd + 5.5.0-SNAPSHOT + + + + ${basedir}/../pmd-core + + + + + + maven-resources-plugin + + false + + ${*} + + + + + + + + net.sourceforge.pmd + pmd-core + + + + net.sourceforge.pmd + pmd-test + test + + + diff --git a/pmd-php/src/main/java/net/sourceforge/pmd/cpd/PHPLanguage.java b/pmd-php/src/main/java/net/sourceforge/pmd/cpd/PHPLanguage.java new file mode 100644 index 0000000000..bb8c199da2 --- /dev/null +++ b/pmd-php/src/main/java/net/sourceforge/pmd/cpd/PHPLanguage.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +/** + * Language implementation for PHP + */ +public class PHPLanguage extends AbstractLanguage { + + /** + * Creates a new PHP Language instance. + */ + public PHPLanguage() { + super("PHP", "php", new PHPTokenizer(), ".php", ".class"); + } +} diff --git a/pmd-php/src/main/java/net/sourceforge/pmd/cpd/PHPTokenizer.java b/pmd-php/src/main/java/net/sourceforge/pmd/cpd/PHPTokenizer.java new file mode 100644 index 0000000000..15c1fd0413 --- /dev/null +++ b/pmd-php/src/main/java/net/sourceforge/pmd/cpd/PHPTokenizer.java @@ -0,0 +1,27 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.util.List; + +/** + * Simple tokenizer for PHP. + */ +public class PHPTokenizer implements Tokenizer { + + @Override + public void tokenize(SourceCode tokens, Tokens tokenEntries) { + List code = tokens.getCode(); + for (int i = 0; i < code.size(); i++) { + String currentLine = code.get(i); + for (int j = 0; j < currentLine.length(); j++) { + char tok = currentLine.charAt(j); + if (!Character.isWhitespace(tok) && tok != '{' && tok != '}' && tok != ';') { + tokenEntries.add(new TokenEntry(String.valueOf(tok), tokens.getFileName(), i + 1)); + } + } + } + tokenEntries.add(TokenEntry.getEOF()); + } +} diff --git a/pmd-php/src/main/java/net/sourceforge/pmd/lang/php/PhpLanguageModule.java b/pmd-php/src/main/java/net/sourceforge/pmd/lang/php/PhpLanguageModule.java new file mode 100644 index 0000000000..f2fa8e586f --- /dev/null +++ b/pmd-php/src/main/java/net/sourceforge/pmd/lang/php/PhpLanguageModule.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.php; + +import net.sourceforge.pmd.lang.BaseLanguageModule; + +/** + * Language Module for PHP. + */ +public class PhpLanguageModule extends BaseLanguageModule { + + /** The name. */ + public static final String NAME = "PHP: Hypertext Preprocessor"; + /** The terse name. */ + public static final String TERSE_NAME = "php"; + + /** + * Create a new instance of the PHP Language Module. + */ + public PhpLanguageModule() { + super(NAME, "PHP", TERSE_NAME, null, "php", "class"); + addVersion("", null, true); + } + +} diff --git a/pmd-php/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-php/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..665241293c --- /dev/null +++ b/pmd-php/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.cpd.PHPLanguage diff --git a/pmd-php/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language b/pmd-php/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language new file mode 100644 index 0000000000..2d0b4aa64f --- /dev/null +++ b/pmd-php/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.lang.php.PhpLanguageModule diff --git a/pmd-php/src/site/markdown/index.md b/pmd-php/src/site/markdown/index.md new file mode 100644 index 0000000000..43f979c0bb --- /dev/null +++ b/pmd-php/src/site/markdown/index.md @@ -0,0 +1,3 @@ +# PMD PHP + +Only CPD is supported. There are no PMD rules for PHP. diff --git a/pmd-php/src/site/site.xml b/pmd-php/src/site/site.xml new file mode 100644 index 0000000000..2813d6fe41 --- /dev/null +++ b/pmd-php/src/site/site.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pmd-php/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java b/pmd-php/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java new file mode 100644 index 0000000000..946b37701d --- /dev/null +++ b/pmd-php/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java @@ -0,0 +1,27 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd; + +import java.util.Arrays; +import java.util.Collection; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.php.PhpLanguageModule; + +import org.junit.runners.Parameterized.Parameters; + +public class LanguageVersionTest extends AbstractLanguageVersionTest { + + public LanguageVersionTest(String name, String terseName, String version, LanguageVersion expected) { + super(name, terseName, version, expected); + } + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + { PhpLanguageModule.NAME, PhpLanguageModule.TERSE_NAME, "", LanguageRegistry.getLanguage(PhpLanguageModule.NAME).getDefaultVersion() } + }); + } +} diff --git a/pmd-plsql/etc/grammar/PldocAST.jjt b/pmd-plsql/etc/grammar/PldocAST.jjt new file mode 100644 index 0000000000..723e831d25 --- /dev/null +++ b/pmd-plsql/etc/grammar/PldocAST.jjt @@ -0,0 +1,5425 @@ +/* Copyright (C) 2002-2012 Albert Tumanov + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the 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 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. +*/ + +// +options { + DEBUG_PARSER = false ; + DEBUG_TOKEN_MANAGER = false; + DEBUG_LOOKAHEAD = false; + IGNORE_CASE = true; + STATIC = false; + LOOKAHEAD = 1; + // set the ambiguity checks to higher values, if you need more extensive checks of the grammar + // this however make the parser generation very slow, so should only be done once after + // modifying the grammar to make sure there are no grammar errors. + // Default value is 2. + CHOICE_AMBIGUITY_CHECK = 2; + OTHER_AMBIGUITY_CHECK = 1; + ERROR_REPORTING = true; + JAVA_UNICODE_ESCAPE = false; //true + UNICODE_INPUT = true; + USER_TOKEN_MANAGER = false; + USER_CHAR_STREAM = false; + BUILD_PARSER = true; + BUILD_TOKEN_MANAGER = true; + SANITY_CHECK = true; + FORCE_LA_CHECK = false; + + CACHE_TOKENS = true; + + + MULTI = true; + VISITOR = true; + NODE_USES_PARSER = true; + NODE_PACKAGE="net.sourceforge.pmd.lang.plsql.ast"; + NODE_CLASS = "net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode"; +} + +PARSER_BEGIN(PLSQLParser) + +/* Copyright (C) 2002 Albert Tumanov + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.io.*; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.SimpleCharStream; +import net.sourceforge.pmd.lang.ast.TokenMgrError; + +public class PLSQLParser { + + /** + * main method, for testing only. + * @param args + */ + public static void main(String[] args) + throws ParseException { + + PLSQLParser parser = new PLSQLParser(System.in); + PLSQLNode node = parser.Input(); + + String s; + s = "qwerty"; +// System.err.println(s + " -> " + parser.purifyIdentifier(s)); + s = "\"qwerty\""; +// System.err.println(s + " -> " + parser.purifyIdentifier(s)); + s = "\"qwerty\".uiop"; +// System.err.println(s + " -> " + parser.purifyIdentifier(s)); + s = "\"qwerty\".\"uiop\""; +// System.err.println(s + " -> " + parser.purifyIdentifier(s)); + } + + /** + Return canonical version of the Oracle + */ + public static String canonicalName(String name) + { + StringBuilder s = null ; + + + if (null == name) { + return name; + } + else if (-1 == name.indexOf('"') ) + { + name = name.toUpperCase(); + s = new StringBuilder(name.trim()); + } + else + { + StringBuilder oldString = new StringBuilder( name.trim().length()); + s = new StringBuilder(name.trim()); + boolean quotedCharacter = false ; + for (int i=0; i|||||||||) SkipPastNextTokenOccurrence(SQLPLUS_TERMINATOR) //Ignore SQL statements in scripts + ) + ("/")* + )* + + { return jjtThis ; } +} + +ASTDDLCommand DDLCommand() : +{ + PLSQLNode simpleNode = null ; +} +{ + ( + simpleNode = DDLEvent() + SkipPastNextTokenOccurrence(SQLPLUS_TERMINATOR) + ) + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTSqlPlusCommand SqlPlusCommand() : +{ + StringBuilder sb = new StringBuilder() ; +} +{ + ( + // e.g. SHOW ERRORS, GRANT EXECUTE ON ... TO ... + // SQLPLUS commands + ( "@" + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + // DDL that might be encountered + | + | + | + | + | + // Attach Library + | "." + ) + { sb.append(token.image) ; sb.append(" ...") ; } + Skip2NextTokenOccurrence(EOL) //Tracker Issue 1433480 skip until next EOL Special Token + //[";" | "-"] + ) + { jjtThis.setImage(sb.toString()) ; return jjtThis ; } +} + +/* +SRT 2011-04-17 This syntax breaks the parser when fields of record.attach* are referenced in PL/SQL +void attachLibrary() : +{} +{ + <".attach"> +} +*/ + +/** + * All global definitions of triggers, functions and procedures are evaluated here. + * Every occurence goes under a new PACKAGE-Node in the XML document. + * This happens, cause a global "block" does not have a definied start and end token + * like a package specification or a package body. + * Thats why every construct is handled like a new part of the global package. + * To overcome this problem, I could use an infinity lookahead - which should solve the problem + * and slow down the whole parsing. + * Another idea would be to lookahead the next tokens and decide wether they belong to a package definition or not. + * Then I could decide to stay in this global parsing state. By now lookahead gives the parser a hint to + * choose the correct way on a given base. So we can't negate it easily. + * On the other hand I could also hold the global state in a global variable. + * But this does not seems the correct way to solve the problem, I think. + * + * 2006-05-17 - Matthias Hendler - added + */ +ASTGlobal Global() : +{ +} +{ + + /* + Remove triggers from global processing because their schema may be defined in the trigger code itself + Still wrap the trigger in a fake package but make the package name dependent on the actual schema + defaulting to the globalPackageName if it cannot be found + */ + (LOOKAHEAD ( ( Label() )* [ DeclarativeSection()] ) ( Label() )* Block() ";" | LOOKAHEAD (4) ProgramUnit() + ) + + + { return jjtThis ; } +} + + + +ASTBlock Block() : +{ +} +{ + // Included as part of statement() + [ + + DeclarativeSection() + ] + + + (Statement())* + (ExceptionHandler())? + [ ] // Optional END Identifier has to match the label + { return jjtThis ; } +} + + + +ASTPackageSpecification PackageSpecification() : +{ + PLSQLNode simpleNode = null ; +} +{ + ( + [ [ ] [ | ] ] + simpleNode = ObjectNameDeclaration() + + ( + ( ( | )) + | AccessibleByClause() + )* + + ( + ( + WrappedObject() + ) + | + ( + ( | ) + + DeclarativeSection() + + [ID()] ";" + ) + ) + ) + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTPackageBody PackageBody() : +{ + PLSQLNode simpleNode = null ; +} +{ + ( + [ [ ] [ | ] ] + ( | ) simpleNode = ObjectNameDeclaration() + + ( + ( + WrappedObject() + ) + | + ( + ( | ) + + DeclarativeSection() //SRT 20110524 Allow PLDOc in Type Bodies + + + [ (Statement())* (ExceptionHandler())? ] [ID()] ";" + + ) + ) + ) + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTDeclarativeUnit DeclarativeUnit() : +{} +{ + ( + Pragma() | + LOOKAHEAD(2) + ExceptionDeclaration() | + LOOKAHEAD((|) QualifiedID() ( | ) ) //SRT 20110616 - make sue soen't break object type + SubTypeDefinition() | + LOOKAHEAD((|) QualifiedID() ) //SRT 20111117 - Special case of parameterless methods:choose method in preference to variable + ProgramUnit() | + LOOKAHEAD(4) + VariableOrConstantDeclaration() | + LOOKAHEAD(2) + CursorSpecification() | + CursorBody() | + CollectionDeclaration() | + //ProgramUnit() + //|TypeMethod() + MethodDeclaration() + |CompilationDeclarationFragment() + ) + { return jjtThis ; } +} + +ASTDeclarativeSection DeclarativeSection() : +{} +{ + ( + DeclarativeUnit() + )* + { return jjtThis ; } +} + +ASTCompilationDeclarationFragment CompilationDeclarationFragment() : +{ +} +{ + ( //SRT 20110601 + + ConditionalOrExpression() + + (DeclarativeUnit() + | Expression() + )* + + ( + + ConditionalOrExpression() + + (DeclarativeUnit() + | Expression() + )* + )* + + ( + + (DeclarativeUnit() + | Expression() + )* + )* + + ) + { return jjtThis ; } +} + + + +/** + * 2006-05-22 - Matthias Hendler - Printing of custom tag "@deprecated" removed. + * Printing of any custom tag added. Now user can define his own + * custom tags which he can evaluate in the XSLT. + * This methode also documents global functions/procedures. + */ +ASTProgramUnit ProgramUnit() : +{ +} +{ + ( + + [ [ ] [ | ] ] + + MethodDeclarator() + + ( + WrappedObject() + | + /* + //SRT 20110516 Cope with AUTHID for schema level functions and procedures + (tokenIsAs= ( | ))? + */ + + ( ( ( | ) ) | + | AccessibleByClause() + | [ ParallelClause() ] [ ID() ["." ID()] ] + | [ ( ID() ["." ID()] ) + | //20110531 + (( | ) [ID()] "(" ID() ( "," ID() )* ")" ) + ] // drvparx.IndexMapDocuments + | [ "(" ID() ["." ID()] ( "," ID() ["." ID()])* ")" ] + ) * + + [ ID() ] + + // body + [ + + ( | ) + //SRT ( | ) + ( + LOOKAHEAD(2) + CallSpecTail() //{ System.err.println("Found CallSpecTail") ; } + | + ( + DeclarativeSection() + (Statement())* (ExceptionHandler())? [ID()] + ) + ) + ] + ";" //SRT 20110416 { System.err.println("Found terminating semi-colon") ; } + + ) //UnwrappedCode + ) + { return jjtThis ; } +} + +ASTObjectNameDeclaration ObjectNameDeclaration() : +{PLSQLNode schemaName = null, objectName = null ; } +{ + [ LOOKAHEAD(2) schemaName = ID() "." ] objectName = ID() + { jjtThis.setImage( (null == schemaName) ? objectName.getImage() : (schemaName.getImage() + "." + objectName.getImage() ) ) ; } + { return jjtThis ; } +} + + +ASTFormalParameter FormalParameter() : +{ + PLSQLNode simpleNode = null ; +} +{ + ( + simpleNode = ID() + // the order of outer "|" is important ! + ( LOOKAHEAD(2) ( LOOKAHEAD(2) ( (|( )) (LOOKAHEAD(2) )? ) | ) )? + ("..." | Datatype()) + ( (":" "="|<_DEFAULT>) Expression() )? + ) + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + + + +ASTMethodDeclaration MethodDeclaration() : +{} +{ + ( + ProgramUnit() + | + TypeMethod() + ) + { return jjtThis ; } +} + + +ASTMethodDeclarator MethodDeclarator() : +{ + PLSQLNode simpleNode = null ; +} +{ + ( + simpleNode = ObjectNameDeclaration() + ( + [ FormalParameters() ] + + { + Token nextToken; + nextToken = getToken(1); //ReadAhead + if (!nextToken.image.equalsIgnoreCase("WRAPPED") + && + !nextToken.image.equalsIgnoreCase("RETURN") + ) + { + throw new ParseException("FUNCTION must RETURN a value or must be WRAPPED : found \"" + + nextToken.image + + "\" at line "+nextToken.beginLine + + ", column "+nextToken.beginColumn + ); + } + } + // There is no RETURN for a WRAPPED object + [ Datatype() ] + + ) + | + simpleNode = ObjectNameDeclaration() + ( + [ FormalParameters() ] + ) + ) + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + + +ASTFormalParameters FormalParameters() : +{ + PLSQLNode simpleNode = null ; + StringBuilder sb = new StringBuilder(); +} +{ + ( + "(" {sb.append("(");} + [ simpleNode = FormalParameter() { sb.append(simpleNode.getImage());} + ( "," simpleNode = FormalParameter() { sb.append(","+simpleNode.getImage());} )* + ] + ")"{sb.append(")");} + ) + { jjtThis.setImage(sb.toString()) ; return jjtThis ; } +} + + + + +ASTVariableOrConstantDeclarator VariableOrConstantDeclarator() : +{ + PLSQLNode simpleNode = null ; + StringBuilder sb = new StringBuilder(); +} +{ + ( + simpleNode = VariableOrConstantDeclaratorId() { sb.append(simpleNode.getImage());} + [LOOKAHEAD(2) {sb.append(" " + token.image);} ] simpleNode = Datatype() { sb.append(" " + simpleNode.getImage());} + [[ {sb.append(" " + token.image);} ] {sb.append(" " + token.image);} ] + [ ( ":" "=" {sb.append(" :=");}| <_DEFAULT> {sb.append(" " + token.image);}) + simpleNode = VariableOrConstantInitializer() { sb.append(" " + simpleNode.getImage());} + ] + ) + { jjtThis.setImage(sb.toString()) ; return jjtThis ; } +} + +ASTVariableOrConstantDeclaratorId VariableOrConstantDeclaratorId() : +{ + PLSQLNode simpleNode = null ; +} +{ + simpleNode = ID() + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTVariableOrConstantInitializer VariableOrConstantInitializer() : +{ + PLSQLNode simpleNode = null ; +} +{ + simpleNode = Expression() + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + + + + +ASTDatatype Datatype() : +{ + PLSQLNode simpleNode = null ; + StringBuilder sb = new StringBuilder(); +} +{ + ( + // this should be first + simpleNode = CompilationDataType() {sb.append(simpleNode.getImage());} | + LOOKAHEAD(2) simpleNode = ScalarDataTypeName() {sb.append(simpleNode.getImage());} + | + ( + ( [LOOKAHEAD(2) {sb.append(token.image);} ] simpleNode = QualifiedName() {sb.append(simpleNode.getImage());} + //Bug 35352414 - datatype may include dblink + ["@" simpleNode = QualifiedName() {sb.append("@"+simpleNode.getImage());} ] + ["%" (|){sb.append("%"+token.image);} ] + ) + ) + ) + { jjtThis.setImage(sb.toString()) ; return jjtThis; } +} + + +ASTCompilationDataType CompilationDataType() : +{ + PLSQLNode simpleNode = null; + StringBuilder sb = new StringBuilder() ; +} +{ + ( + {sb.append(" "); sb.append(token.image) ; } + simpleNode= ConditionalOrExpression() {sb.append(" "); sb.append(simpleNode.getImage()); } + {sb.append(" "); sb.append(token.image); } + simpleNode = Datatype() {sb.append(" "); sb.append(simpleNode.getImage()); } + + ( + {sb.append(" "); sb.append(token.image); } + simpleNode = ConditionalOrExpression() {sb.append(" "); sb.append(simpleNode.getImage()); } + {sb.append(" "); sb.append(token.image); } + simpleNode = Datatype() {sb.append(" "); sb.append(simpleNode.getImage()); } + )* + + ( + {sb.append(" "); sb.append(token.image); } + simpleNode = Datatype() {sb.append(" "); sb.append(simpleNode.getImage()); } + )* + + {sb.append(" "); sb.append(token.image); } + ) + { + jjtThis.setImage(sb.toString()) ; return jjtThis; + } +} + +ASTCollectionTypeName CollectionTypeName() : +{ PLSQLNode size=null, precision=null; + StringBuilder sb = new StringBuilder(); +} +{ + ( + // Collection types +
| | ( {sb.append( "VARYING ARRAY") ;}) + ) + + { + if (sb.length() == 0) { + sb.append(token.toString()); + } + } + (LOOKAHEAD(2) "(" size=NumericLiteral() {sb.append( "(" + size);} + ["," precision=NumericLiteral() {sb.append( "," + precision);}] + [ {sb.append( " CHAR") ;}] + [ {sb.append( " BYTE") ;}] + ")" {sb.append( ")");})? + + { jjtThis.setImage(sb.toString()) ; return jjtThis; } +} + +ASTScalarDataTypeName ScalarDataTypeName() : +{ PLSQLNode size=null, precision=null ; + StringBuilder name = new StringBuilder(); + PLSQLNode characterSet = null; +} +{ + ( + //Base types used in SYS.STANDARD + | + | + | + | + | + | + + // scalar types - numeric: + | | | ( {name.append("DOUBLE PRECISION");}) | + | | | | + | | | | | | | | + | + + // scalar types - character: + ( + ( + | + //SRT | LOOKAHEAD(2) ( {name = "LONG RAW";}) | LOOKAHEAD(2) | + | LOOKAHEAD(2) ( {name.append("LONG RAW");}) | | + | | | | + | | | | | + ) + ) + | + // scalar types - boolean: + + + | + + // composite types + //SRT 20090531
| | ( {name = "VARYING ARRAY";}) | + // - defined elsewhere + + // LOB types + | | + + // reference types + | //SRT Added to support pre-defined weak REF CURSOR + ( {name.append("REF CURSOR");}) | + // object_type - defined elsewhere + + // scalar types - date/time: + | + LOOKAHEAD(2) ( {name.append("INTERVAL YEAR");}) | + LOOKAHEAD(2) ( {name.append("INTERVAL DAY");}) | +
||) + Skip2NextTerminator(initiator,terminator) + { + return jjtThis ; + } +} + + +/** + * 2011-05-15 - SRT - Added to cope with wrapped objects + A wrapped function looks like this (always terminated by one or more equals signs "="):- + " CREATE OR REPLACE FUNCTION "TESTUSER"."GET_DATE_STRING" +/ ** Return SYSDATE formatted using the provided template. + * + * + * @param p_date_format normal TO_CHARE/TO_DATE date template + * @return formatted datestring + * @see http://www.oracle-base.com/articles/10g/WrapAndDBMS_DDL_10gR2.php#dbms_ddl + * / +wrapped +a000000 +369 +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +abcd +8 +89 b6 +/SBrhM8+1iUO4QAih+qD2SK8kSowg8eZgcfLCNL+XlquYvSuoVah8JbRPpdHDLHn479SdFLw +v04omzJ0zOfHdMAzuHQlw+fAsr2ym9YI8I521pRTbnFVAHOOUw4JqPkIyj7wj4VwyL17nhYb +3qPVuL6SvhZTmEBnRtaErHpzaDuIpqZ0G4s= + " + */ + + + +void WrappedObject() : +{} +{ + + { + Token nextToken; + + nextToken = getToken(1); //ReadAhead + while ( + null != nextToken && nextToken.kind!=EOF + ) + { + nextToken = getNextToken(); + + //Execute manual readahead + nextToken = getToken(1); //ReadAhead 1 Token + } + return; + } +} + + +// ============================================================================ +// S T A T E M E N T S +// ============================================================================ + +/** + * 2006-05-24 - Matthias Hendler - added MERGE, EXECUTE choice and LOOKAHEAD at + */ +ASTUnlabelledStatement UnlabelledStatement() : +{} +{ + ( + LOOKAHEAD(["("]
||) SqlStatement(null,";") [";"] + | LOOKAHEAD(3) ContinueStatement() ";" // CONTINUE keyword was added in 11G, so Oracle compilation supports CONTINUE as a variable name + | CaseStatement() ";" + | IfStatement() ";" + | ForStatement() ";" + | ForAllStatement() ";" + | LoopStatement() ";" + | WhileStatement() ";" + | GotoStatement() ";" + | ReturnStatement() ";" + | ExitStatement() ";" + | RaiseStatement() ";" + | CloseStatement() ";" + | OpenStatement() ";" + | FetchStatement() ";" + | Block() ";" + | EmbeddedSqlStatement() ";" + | PipelineStatement() ";" + | ConditionalCompilationStatement() // Conditional Compilation works outside the normal parsing rules + | InlinePragma() ";" + | Expression() ";" + ) + { return jjtThis ; } +} + + +ASTStatement Statement() : +{} +{ + ( + //SQL Developer compiler allows 0, 1 or many labels immediately before a statement + //SQL Developer compiler requires a statement after a sequence of labels + LabelledStatement() + | + UnlabelledStatement() + ) + { return jjtThis ; } +} + +/* +LabelledStatement created solely to conform with PMD Java AST (for PMD DataFlow Analysis - DFA) +N.B. equivalent Java AST* class is ASTLabeledStatement (single "l" rather than double "ll") +*/ +ASTLabelledStatement LabelledStatement() : +{ +PLSQLNode simpleNode = null; +} +{ + ( + //SQL Developer compiler allows 0, 1 or many labels immediately before a statement + //SQL Developer compiler requires a statement after a sequence of labels + //Use the last label + (simpleNode = Label() )+ + UnlabelledStatement() + ) + { + jjtThis.setImage( simpleNode.getImage() ) ; + return jjtThis ; + } +} + +ASTCaseStatement CaseStatement() : +{} +{ + ( + ( Expression() )? + ( CaseWhenClause() )* + [ ElseClause() ] + [] + ) + { return jjtThis ; } +} + +ASTCaseWhenClause CaseWhenClause() : +{} +{ + Expression() (Statement())+ + { return jjtThis ; } +} + +ASTElseClause ElseClause() : +{} +{ + (Statement())+ + { return jjtThis ; } +} + +ASTElsifClause ElsifClause() : +{} +{ + Expression() (Statement())+ + { return jjtThis ; } +} + +ASTLoopStatement LoopStatement() : +{} +{ + (Statement())+ [] + { return jjtThis ; } +} + +/** Scope rule: the loop index only exists within the Loop */ +ASTForStatement ForStatement() : +{} +{ + ForIndex() [] Expression()[".."Expression()] (Statement())+ [] + { return jjtThis ; } +} + +ASTWhileStatement WhileStatement() : +{} +{ + Expression() (Statement())+ [] + { return jjtThis ; } +} + +ASTIfStatement IfStatement() : +{} +{ + Expression() (Statement())+ + ( ElsifClause() {jjtThis.setHasElse();} )* + [ ElseClause() {jjtThis.setHasElse();} ] + + { return jjtThis ; } +} + +/** Scope rule: the loop index only exists within the statement */ +/** +ForIndex is declared implicitly, unlike most variables or constants. +*/ +ASTForIndex ForIndex() : +{ + PLSQLNode simpleNode = null ; +} +{ + simpleNode = ID() + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +/** +ForAllIndex is declared implicitly, unlike most variables or constants. +*/ +ASTForAllIndex ForAllIndex() : +{ + PLSQLNode simpleNode = null; +} +{ + simpleNode = ID() + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTForAllStatement ForAllStatement() : +{} +{ + ForAllIndex() + ( + ( | ) Expression() // Add support for sparse collections + | Expression() [".." Expression()] + ) + [ ] SqlStatement(null,";") + { return jjtThis ; } +} + +ASTGotoStatement GotoStatement() : +{ + PLSQLNode label = null; +} +{ + label = QualifiedName() + { jjtThis.setImage(label.getImage()) ; return jjtThis ; } +} + +ASTReturnStatement ReturnStatement() : +{} +{ + [Expression()] + { return jjtThis ; } +} + +ASTContinueStatement ContinueStatement() : +{ + PLSQLNode label = null; +} +{ + [ label = UnqualifiedID() ] [ Expression() ] + { if (null != label) { jjtThis.setImage(label.getImage()) ; } return jjtThis ; } +} + +ASTExitStatement ExitStatement() : +{ + PLSQLNode label = null; +} +{ + [ label = UnqualifiedID() ] [ Expression() ] + { if (null != label) { jjtThis.setImage(label.getImage()) ; } return jjtThis ; } +} + +ASTRaiseStatement RaiseStatement() : +{PLSQLNode exception = null ; } +{ + [exception = QualifiedName()] + { if (null != exception) { jjtThis.setImage(exception.getImage()) ; } return jjtThis ; } +} + +ASTCloseStatement CloseStatement() : +{PLSQLNode cursor = null ; +} +{ + cursor = QualifiedName() + { jjtThis.setImage(cursor.getImage()) ; return jjtThis ; } +} + +ASTOpenStatement OpenStatement() : +{} +{ + [Expression()] + //[LOOKAHEAD(functionCall()) functionCall() | QualifiedName()] + [ Expression() [ Expression() ("," Expression())*]] + { return jjtThis ; } +} + +ASTFetchStatement FetchStatement() : +{} +{ + QualifiedName() [ ] + //MMUE 04/08/2005 (LOOKAHEAD(functionCall()) functionCall() | QualifiedName()) ("," (LOOKAHEAD(functionCall()) functionCall() | QualifiedName()))* ";" + Expression() ("," Expression())* [ Expression()] + // + { return jjtThis ; } +} + +ASTEmbeddedSqlStatement EmbeddedSqlStatement() : +{} +{ + Expression() // StringLiteral() /* */ + //SRT 20110429 | StringExpression() [ Expression() ("," Expression())*] ";" + //SRT 20121126 | StringExpression() + [ Name() ("," Name())* ] + [ [ [ ] | ] Expression() ("," [ [ ] | ] Expression())* ] + [ ( | ) Expression() ("," Expression())*] ";" + // PIPELINED FUNCTION OUTPUT + { return jjtThis ; } +} + +ASTPipelineStatement PipelineStatement() : +{} +{ + Expression() + { return jjtThis ; } +} + +ASTConditionalCompilationStatement ConditionalCompilationStatement() : +{} +{ + ( ConditionalOrExpression() (Statement())* + ( ConditionalOrExpression() (Statement())+ )* + ( (Statement())+ )* + + | Expression() + ) + { return jjtThis ; } +} + +ASTSubTypeDefinition SubTypeDefinition() : +{ + Token start, subtype_name=null, constraint=null, base_type=null; + Token collection = null, collection2 = null; + PLSQLNode name = null; + PLSQLNode startElement = null, endElement = null; + PLSQLNode baseType = null, returnType = null, indexBy = null ; + int lastField = 0; +} +{ + + ( + ( + name = QualifiedID() // SRT 20110605 to Parse SYS.standard need to allow normally reserved words which are low-level types + Datatype() + [ + ( "(" ")") // SRT 20110604 [ ] + | + ( UnaryExpression(true) ".." UnaryExpression(true) ) // In "RANGE -1 .. 31" -1 is a unary Expression + ] + [ ] //SRT 20110604 + ) + | + ( name = QualifiedID() // SRT 20110605 to Parse SYS.standars ned to allow nprmally reserved words which are low-level types + (|) + ( + LOOKAHEAD(2) ( + + | + | + | + | + | + ) + + | + //SRT 20110606 SYS.STANDRD + ( "(" FieldDeclaration() ("," FieldDeclaration())* ")" ) + | + ( "(" FieldDeclaration() ("," FieldDeclaration())* ")" ) + | + ((
| | )["(" NumericLiteral() ")"] + Datatype() ( )? ( Datatype())?) + | + [ Datatype()] + //Enumeration + | ( "(" + Expression() + ( "," Expression() )* + ")" + ) + //Alias for existing type + | Datatype() + ) + ) + ) + ";" + { jjtThis.setImage(name.getImage()) ; return jjtThis ; } +} + +ASTFieldDeclaration FieldDeclaration() : +{ + PLSQLNode name; + PLSQLNode dataType; + PLSQLNode defaultValue = null; +} +{ + name = ID() Datatype() [[] ] [ (":" "=" | <_DEFAULT>) Expression() ] + { jjtThis.setImage(name.getImage()) ; return jjtThis ; } +} + +ASTCollectionTypeDefinition CollectionTypeDefinition() : {Token t = null ; } { t = { jjtThis.setImage(t.image) ; return jjtThis ; } } +ASTCollectionDeclaration CollectionDeclaration() : {Token t = null ; } { t = { jjtThis.setImage(t.image) ; return jjtThis ; } } +ASTObjectDeclaration ObjectDeclaration() : {Token t = null ; } { t = { jjtThis.setImage(t.image) ; return jjtThis ; } } + +/** Java stored procedure, external function*/ +ASTCallSpecTail CallSpecTail() : { +} +{ + // /* (/*"C"*/ | ) + + ( + ( + | (/*"C"*/ | ) + + )//SRT 20110516 { System.err.println("Found EXTERNAL or LANG ") ; } + //These can appear in any order .... + ( + ( ( | | StringLiteral() ) + [ "." ( | | StringLiteral() ) ] + ) + | + ( ( | /* C */| StringLiteral() /* JAVA */ ) ) + //SRT 20110517 Need to cope with CallSpecTails in ObjectTypes // Skip2NextTerminator(null,";") + //SkipPastNextOccurrence(")") // Read until we have eaten the closing bracket ")" + | + ( + + ) + | + ( + + SkipPastNextOccurrence(")") // Read until we have eaten the closing bracket ")" + ) + )* + ) + + /* ### or: + library_name + [NAME c_string_literal_name] + [WITH CONTEXT] + [PARAMETERS (external_parameter[, external_parameter]...)]; + + Where library_name is the name of your alias library, c_string_literal_name is the name of your external C procedure, and external_parameter stands for: + + { + CONTEXT + | SELF [{TDO | property}] + | {parameter_name | RETURN} [property] [BY REFERENCE] [external_datatype] + } + + where property stands for: + + {INDICATOR [{STRUCT | TDO}] | LENGTH | MAXLEN | CHARSETID | CHARSETFORM} + + ( [ | ] | | | | ) + + */ + + /* + " + ( + ( | ( | ) ) + + + [ ] + [ + "(" + + ( "," + ( + [ | [ ( [ | ] ) | | | | ] ] + | ( + ( | ) + [ | [ ( [ | ] ) | | | | ] ] + [ ] + [ ] + ) + ) + )* + ")" + ] + ) + { + return; + } + */ + { return jjtThis ; } +} + + + + + + +/** Cursor (common part of specification and body) */ +ASTCursorUnit CursorUnit() : +{ + PLSQLNode simpleNode = null ; +} +{ + ( + simpleNode = ID() + [ FormalParameters() ] + [ Datatype() + ] + ) + [ ("(")* SqlStatement(null,";")] ";" + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTCursorSpecification CursorSpecification() : {} +{ + CursorUnit() + { return jjtThis ; } +} + +ASTCursorBody CursorBody() : {} +{ + CursorUnit() +// /**/ + { return jjtThis ; } +} + + + +// ============================================================================ +// E X P R E S S I O N S +// ============================================================================ + +/* +String expression() : +{} +{ + "test" + { return ""; } +} +*/ + + + + +ASTExpression Expression() : +{ + PLSQLNode simpleNode = null; + StringBuilder sb = new StringBuilder() ; +} +{ + // Need syntactic lookahead to discriminate between Assignment and a procedure call + ( + LOOKAHEAD( PrimaryExpression() ":" "=" ) (simpleNode = Assignment()) {sb.append(simpleNode.getImage()); } + | (simpleNode = ConditionalOrExpression() ) {sb.append(simpleNode.getImage()); } + | (simpleNode = CompilationExpression() ) {sb.append(simpleNode.getImage()); } + ) + { + jjtThis.setImage(sb.toString()); return jjtThis; + } +} + +ASTCompilationExpression CompilationExpression() : +{ + PLSQLNode simpleNode = null; + StringBuilder sb = new StringBuilder() ; +} +{ + ( + {sb.append(" "); sb.append(token.image) ; } + simpleNode = ConditionalOrExpression() {sb.append(" "); sb.append(simpleNode.getImage()); } + {sb.append(" "); sb.append(token.image); } + simpleNode = Expression() {sb.append(" "); sb.append(simpleNode.getImage()); } + + ( + {sb.append(" "); sb.append(token.image); } + simpleNode = ConditionalOrExpression() {sb.append(" "); sb.append(simpleNode.getImage()); } + {sb.append(" "); sb.append(token.image); } + simpleNode = Expression() {sb.append(" "); sb.append(simpleNode.getImage()); } + )* + + ( + {sb.append(" "); sb.append(token.image); } + simpleNode = Expression() {sb.append(" "); sb.append(simpleNode.getImage()); } + )* + + {sb.append(" "); sb.append(token.image); } + ) + { + jjtThis.setImage(sb.toString()); return jjtThis; + } +} + + +ASTAssignment Assignment() : +{ PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } +{ + ( + (simpleNode = PrimaryExpression() ) {sb.append(simpleNode.getImage());} + (":" "=" ) {sb.append(" := ");} + (simpleNode = Expression()) {sb.append(simpleNode.getImage());} + ) + { + jjtThis.setImage(sb.toString()); return jjtThis; + } +} + +ASTCaseExpression CaseExpression() : +{ Token thisToken; PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } +{ + ( + thisToken = { sb.append(thisToken.image);} + ( simpleNode = Expression() { sb.append(" "); sb.append(simpleNode.getImage()); } )? + ( thisToken = { sb.append(" "); sb.append(thisToken.image); } + simpleNode = Expression() { sb.append(" "); sb.append(simpleNode.getImage()); } + thisToken = { sb.append(" "); sb.append(thisToken.image); } + Expression() { sb.append(" "); sb.append(simpleNode.getImage()); } + )+ + [ thisToken = { sb.append(" "); sb.append(thisToken.image);} + Expression() { sb.append(" "); sb.append(simpleNode.getImage()); } + ] + thisToken = { sb.append(" "); sb.append(thisToken.image);} + ) + { + jjtThis.setImage(sb.toString()); return jjtThis; + } +} + +/* +LIKE ( Expression ) [ +*/ +ASTLikeExpression LikeExpression() : +{ Token thisToken; PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } +{ + ( + simpleNode = PrimaryExpression() { sb.append(" "); sb.append(simpleNode.getImage()); } + (thisToken = ) { sb.append(thisToken.image);} + //["(" { sb.append("(");} ] + ( simpleNode = StringExpression() { sb.append(" "); sb.append(simpleNode.getImage()); } ) + //[ ")" { sb.append(")");} ] + [ + { sb.append(" ESCAPE ");} + ( { sb.append(" "); sb.append(token.toString()); } + | simpleNode = StringLiteral() { sb.append(" "); sb.append(simpleNode.getImage()); } + ) + ] + ) + ( "." simpleNode = Expression() { sb.append("."); sb.append(simpleNode.getImage()); } )* + { + jjtThis.setImage(sb.toString()); return jjtThis; + } +} + +ASTTrimExpression TrimExpression() : +{ Token thisToken; PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } +{ + ( + (thisToken = ) { sb.append(thisToken.image);} + "(" { sb.append("(");} + [ ( | | ){ sb.append(" "); sb.append(token.toString()); } ] + [ simpleNode = StringExpression() { sb.append(" "); sb.append(simpleNode.getImage()); } ] + ( ) { sb.append(thisToken.image);} + simpleNode = StringExpression() { sb.append(" "); sb.append(simpleNode.getImage()); } + ")" { sb.append(")");} + ) + { + jjtThis.setImage(sb.toString()); return jjtThis; + } +} + + +/* +TREAT ( Expression AS datatype) +CAST ( Expression AS datatype) +*/ +ASTObjectExpression ObjectExpression() : +{ Token thisToken; PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } +{ + ( + (thisToken = | thisToken = ) { sb.append(thisToken.image);} + "(" { sb.append("(");} + ( simpleNode = Expression() { sb.append(" "); sb.append(simpleNode.getImage()); } ) + [ +
ObjectNameDeclaration() + "(" TableColumn() ("," TableColumn())* ")" + [LOOKAHEAD(2) ( | ) ] + //### [physicalProperties()] + //### [tableProperties()] + [";"] + + { return jjtThis ; } +} + +ASTTableColumn TableColumn() : +{ +} +{ + ID() Datatype() [<_DEFAULT> Expression()] [[ ] ] + { return jjtThis ; } +} + + + +ASTView View() : +{ + PLSQLNode simpleNode = null; +} +{ + [ ] + [[] ] + simpleNode = ObjectNameDeclaration() + ["(" ViewColumn() ("," ViewColumn())* ")"] + //### OF ... WITH OBJECT IDENTIFIER... + + Statement() //SRT select() + //### WITH ... + (";" | "/") + + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTSynonym Synonym() : +{ + PLSQLNode simpleNode = null; +} +{ + [ ] + [] + simpleNode = ObjectNameDeclaration() + + ObjectNameDeclaration() + (";" | "/") + + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTDirectory Directory() : +{ + PLSQLNode simpleNode = null; +} +{ + [ ] + + simpleNode = ObjectNameDeclaration() + + StringLiteral() + (";" | "/") + + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTDatabaseLink DatabaseLink() : +{ + PLSQLNode simpleNode = null; +} +{ + + [ ] [ ] + + simpleNode = ObjectNameDeclaration() + ( + + ( + ( ) + | + ( + UnqualifiedID() UnqualifiedID() + + UnqualifiedID() UnqualifiedID() + ) + + ) + | + UnqualifiedID() UnqualifiedID() + ) + [ StringLiteral() ] + (";" | "/") + + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + +ASTViewColumn ViewColumn() : +{ } +{ + ID() + { return jjtThis ; } +} + +ASTComment Comment() : +{ +} +{ + ( + ((
| | ) [LOOKAHEAD(2) ID()"."] ID()) | + ( [LOOKAHEAD(ID()"."ID()"."ID()) ID()"."] ID() "." ID()) + ) + + [";"] + { return jjtThis ; } +} +// SRT * / + + + +ASTTypeMethod TypeMethod() : +{ +} +{ + + //inheritance CLAUSE + { + getToken(1); + //System.err.println("typeMethod: Result of getToken(1) is \"" + startToken.toString() + "\"" ); + } //SRT added to check Token List walking + ( [ ] ( | | ) )* + + //[ MAP | ORDER ] + //[ [] ] [ [] ] + [ | ] + + + ( | | ) + + + MethodDeclarator() + + + [] + [] + [] + [] + + // Java or C function wrapped with PL/SQL wrapper declaration + [ + + ( | ) + ( + CallSpecTail() + [ ";" ] // This only exists in the type body + | // SRT 20110524 Not really a Declaration any more ... + ( + DeclarativeSection() + (Statement())* (ExceptionHandler())? [ID()] + ";" // This only exists in the type body + ) + ) + ] + + + { return jjtThis ; } +} + + +ASTTypeSpecification TypeSpecification() : +{ + PLSQLNode simpleNode = null; +} +{ + [ [ ] [ | ] ] + simpleNode = ObjectNameDeclaration() + + [ + + ] + + // incomplete_type_spec (empty) + // object_type_spec + // varray_type_spec + // nested_table_type + + [ + LOOKAHEAD(2) + ] + + ( + LOOKAHEAD(2) // ( | ) + ( + ( ( | )) + | AccessibleByClause() + ) + )* + + //(tokenIsAsUnder= | tokenIsAs= ) + //{ + // // possibly, package comment is here + // processPackageComment(tokenIsAsUnder); + //} + + [ + ( + ObjectNameDeclaration() //SUBTYPE + | LOOKAHEAD(2) ( | ) // OBJECT TYPE + | LOOKAHEAD(2) ( | ) "(*)" // OPAQUE TYPE + ( | | StringLiteral() ) + [ "." ( | | StringLiteral() ) ] + | LOOKAHEAD(2) ( | ) CollectionTypeName() Datatype() + + ) + ] + + // + [ + LOOKAHEAD(8) + //tokenIsAsUnder= + // "NAME" //JavaInterfaceClass() //( | | ) + //JavaInterfaceClass() //( | | ) + ] + + [ + WrappedObject() + ] + + // //incomplete OBJECT TYPE and COLLECTION TYPEs will not have this + [ + ("(" ) + //Try matching against keywords first to allow FINCTION and procedure as a fallback + (LOOKAHEAD(2) TypeMethod() | AttributeDeclaration() + | PragmaClause() + )* //SRT 20111125 This part may be completely empty if is a subtype which is effectively an alias for the supertype + ( "," ( TypeMethod() | LOOKAHEAD(2) AttributeDeclaration() + | PragmaClause() + ) + )* + + ")" + ] + + ( [ ] + ( + /*OBJECTS TYPES ONLY */ + | //OBJECT TYPES ONLY + | //COLLECTION TYPES ONLY + ) + )* + + //Original Type Specification may be followed by a series of modifications + ( AlterTypeSpec() ) * + + + [ + (";" | "/" ) + ( AlterTypeSpec() ( "/" | ";" ) )* //SRT 20110529 There may be more after the first terminator + ] + + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + + +ASTAlterTypeSpec AlterTypeSpec() : +{ + PLSQLNode simpleNode = null; +} +{ + + simpleNode = QualifiedName() + + // REPLACE invoker_rights_clause AS OBJECT + //alter_method_spec + //(tokenAlterType = |tokenAlterType = |tokenAlterType = |tokenAlterType = ) + + //SRT 20110529 Each ALTER TYPE statement may contaon 0 or more ADD or DROP clause + //When more than one clause exists, each clause is separated by comma "," + ( + [","] + ( + ( | ) + ( + // Move typeMethidMatching above attribure matvhing becausse FUNCTION is a valid attribute name + ( + (TypeMethod() ) + //( "," ( typeMethod(,typeVersion, tokenAlterTypeString) ) )* + ) + | + ( + + ( "(" )* + (AttributeDeclaration() ) + ( "," ( AttributeDeclaration() ) )* + ( ")" )* + ) + | + ( NumericLiteral() ) + | + ( Datatype() ) + )+ + ) + | + ( + () + ( + ( + + ( "(" )* + (Attribute() ) + ( "," ( Attribute() ) )* + ( ")" )* + ) + | + ( + (TypeMethod() ) + //( "," ( typeMethod(,typeVersion, tokenAlterTypeString) ) )* + ) + )+ + ) + )* +/* + + ) + { + System.err.println("Alter Type is " + tokenAlterType.toString()); + } + | + ( ( + (TypeMethod() ) + ( "," ( TypeMethod() ) )* + ) + | + ( + + [ "(" ] + (QualifiedName() ) + ( "," ( QualifiedName() ) )* + [ ")" ] + ) + + ) + | + //alter_collection_clause + ( + + ( tokenCollectionSize = NumericLiteral() ) + | + ( baseType = Datatype() ) + ) + | +*/ + [ + + ( + LOOKAHEAD(2) // ( | ) + ( + ( ( | )) + | AccessibleByClause() + ) + )* + + ( + ( | ) // OBJECT TYPE + ) + + ("(" ) + + (LOOKAHEAD(2) TypeMethod() | AttributeDeclaration() ) + ( "," ( LOOKAHEAD(2) TypeMethod() | AttributeDeclaration() ) )* + + ")" + ] + +/* */ + + ( [ ] + ( + /*OBJECTS TYPES ONLY */ + | //OBJECT TYPES ONLY + | //COLLECTION TYPES ONLY + ) + )* + + //DEPENDENT HANDLING CLAUSE + [ + ( + + ) + | + ( + + ( + ( + [ ]
+ ) + | + ( + + ) + )* + + [ + [ ] + QualifiedName() + ] + ) + ] + + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + + + +/* +ASTTypeBody TypeBody() : +{ Token name=null; } +{ + [ [ ]] + + (LOOKAHEAD(2) ID()".")? name=ID() + + ( + ( + WrappedObject() + ) + | + ( + ( | ) + + DeclarativeSection() //SRT 20110524 Allow PLDOc in Type Bodies + + [ (Statement())* (ExceptionHandler())? ] [ID()] ";" + ) + ) +} +*/ + + +/** + * Method +**/ +ASTAttributeDeclaration AttributeDeclaration() : +{ + PLSQLNode simpleNode = null; +} +{ + simpleNode = ID() Datatype() + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + + + +ASTAttribute Attribute() : +{ + PLSQLNode simpleNode = null; +} +{ + simpleNode = ID() + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + + +/* + This was quick cut from PRAGMA +*/ +ASTPragmaClause PragmaClause() : +{ +} +{ + + ( + + | + + | + "(" (ID() /*| <_DEFAULT>*/) + ("," (ID() | StringLiteral() /* 20110526 */) )+ ")" + | + "(" "," ["+"|"-"] NumericLiteral() ")" + | + "(" "," "," NumericLiteral() ")" + ) + { return jjtThis ; } +} + +/** + * Trigger + * Triggers are always outside of a package. + * + * 2006-05-17 - Matthias Hendler - added + */ + +/* + 11g Trigger Syntax + + create_trigger : + CREATE [OR REPLACE] TRIGGER [schema.]trigger +( simple_dml_trigger | compound_dml_trigger | non_dml_trigger ) +[ FOLLOWS ( [schema.]trigger) ( , [schema.]trigger)* ] +[ ENABLE | DISABLE ] +( WHEN ( trigger_condition ) +trigger_body + + +simple_dml_trigger : +(BEFORE |AFTER | INSTEAD OF) +dml_event_clause +[ referencing_clause ] +[ FOR EACH ROW ] + + + +compound_dml_trigger : +FOR dml_event_clause +[ referencing_clause ] + + +non_dml_trigger : +(BEFORE> | ) +(DDLEvent | database_event) ( OR (DDLEvent | database_event))* +ON +(DATABASE | [schema.]SCHEMA + + + +trigger_body : +(plsql_block | compound_trigger_block | CALL routine_clause) + + + +dml_event_clause: +( DELETE | INSERT | UPDATE [ OF column (, column ) ] ) +ON ( (schema.table | NESTED TABLE nested_table_column OF [schema.]view ) + +referencing_clause: +REFERENCING +(OLD AS old_alias | NEW AS new_alias | PARENT AS parent_alias )* + + +compound_trigger_block : +COMPOUND TRIGGER +declare_section +(timing_point_section)+ +END [trigger_name] ; + +timing_point_section: +( +BEFORE STATEMENT IS tps_body END BEFORE STATEMENT +|BEFORE EACH ROW IS tps_body END BEFORE EACH ROW +|AFTER STATEMENT IS tps_body END AFTER STATEMENT +|AFTER EACH ROW IS tps_body END AFTER EACH ROW +) + + +tps_body: +(statement)+ +(EXCEPTION exception_handler )* + +*/ +ASTTriggerUnit TriggerUnit() : +{ + PLSQLNode simpleNode = null ; +} +{ + [ [ ] [ | ] ] + + () simpleNode = ObjectNameDeclaration() + + // simple_dml_trigger | compound_dml_trigger | non_dml_trigger + // simple_dml_trigger : + ( | | | // Incorporate 11G Compound DML Trigger + ) + + //dml_event_clause + ( (( | | ) [LOOKAHEAD(6) ID() ("," ID() )*] ) | NonDMLEvent() ) + + ( ( (( | | ) [LOOKAHEAD(6) ID() ("," ID() )* ] ) | NonDMLEvent() ) )* + + + ( + //11G non_dml_trigger + |LOOKAHEAD(2)
ID() [LOOKAHEAD(2) ID()"."] ID() + |[LOOKAHEAD(2) ID()"."] ID() //includes 11g schema. table === + ) + + + + // referencing_clause - there may be ZERO subclauses + [ (( | | ) ID())*] + + [] + // end of simple_dml_trigger (incorporating compound_dml_trigger ) + + + [ [|] ] // 11G Syntax to specify Cross Edition Trigger + [ (|) ( [LOOKAHEAD(2) ID() "."] ID() ) ( "," ( [ LOOKAHEAD(2) ID() "."] ID() ) )* ] // 11G Syntax to specify trigger firing order + [ | ] // 11G Syntax can create the trigger enabled or disabled explcitly + [ "(" ConditionalOrExpression() ")" ] + + //Trigger Body follows : + //plsql_block | compound_trigger_block | routine + ( + PrimaryExpression() ";" + //compound_trigger_block + | CompoundTriggerBlock() + + |//plsql_block + Block() ";" + //| // routine + ) + { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } +} + + +ASTTriggerTimingPointSection TriggerTimingPointSection() : +{ + StringBuilder sb = new StringBuilder(); +} +{ + ( + ( | | ) { sb.append(token.image) ; } + ( | ) {sb.append(" "); sb.append(token.image) ; } + + + (Statement())+ + ( | | ) ( | ) ";" + ) + { + //Add a TRIGGER ENTRY for each timing point section + } + { jjtThis.setImage(sb.toString()) ; return jjtThis ; } +} + + + +ASTCompoundTriggerBlock CompoundTriggerBlock() : +{ +} +{ + + + ( + //Problems making the declaration optional + //DeclarativeSection() + //(TriggerTimingPointSection())+ + ( + TriggerTimingPointSection()| + Pragma() | + LOOKAHEAD(2) + ExceptionDeclaration() | + LOOKAHEAD(2) + SubTypeDefinition() | + LOOKAHEAD(4) + VariableOrConstantDeclaration() | + LOOKAHEAD(2) + CursorSpecification() | + CursorBody() | + CollectionDeclaration() | + ProgramUnit() + )* + + + ) + + [ID()] ";" + { return jjtThis ; } +} + + +/* +non_dml_trigger : +(BEFORE> | ) +(DDLEvent | database_event) ( OR (DDLEvent | database_event))* +ON +(DATABASE | [schema.]SCHEMA +*/ +ASTNonDMLTrigger NonDMLTrigger() : +{ +} +{ + ( | ) + ( DDLEvent() | DatabaseEvent() ) + ( ( DDLEvent() | DatabaseEvent() ) )* + + ( | [LOOKAHEAD(2) ID()"."] ) + { return jjtThis ; } +} + + +ASTDDLEvent DDLEvent(): {} +{ + ( + | + | + | + | + | + | + | + | + | + | + | + | + | + ) + { jjtThis.setImage(token.toString()) ; jjtThis.value = token ; return jjtThis ; } +} + + +ASTDatabaseEvent DatabaseEvent(): {} +{ + ( + | + | + | + | + | + ) + { jjtThis.setImage(token.toString()) ; jjtThis.value = token ; return jjtThis ; } +} + +ASTNonDMLEvent NonDMLEvent(): {} +{ + (DDLEvent() | DatabaseEvent()) + { return jjtThis; } +} + +/* +When DBMS_METADATA.GET_DDL returns a trigger, it can come in 2 DDL statements. +The first is the CREATE OR REPLACE TRIGER statement; the second is an ALTER TRIGGER statement, +enabling or disabling the trigger. + +Unlike the ALTER TYPE, it does not seem to alter the structure of the object. +*/ +void AlterTrigger() : +{} +{ + + Skip2NextTerminator(null,";") + ";" + { + return; + } +} + +// Copyright (C) 2002 Albert Tumanov + +/* WHITE SPACE */ + +SKIP : +{ + //Tracker Issue 1433480 - Skip2 and SkipPast need to be able to check for EOL - so the characters + //cannot be SKIPped + //" " | "\t" | "\n" | "\r" | "\f" + " " | "\t" | "\f" +} + +/* COMMENTS */ + +MORE : +{ + <"/**" ~["/"]> : IN_FORMAL_COMMENT +| + "/*" : IN_MULTI_LINE_COMMENT +} + +SPECIAL_TOKEN : +{ + //Remove terminating EOL from Single Comment Token definition: it causes failurs if no other newline exists before next production + +} + +//Tracker Issue 1433480 - Skip2 and SkipPast need to be able to check for EOL - so it cannot be SKIPped +SPECIAL_TOKEN : +{ + +} + + +SPECIAL_TOKEN : +{ + : DEFAULT +} + + +SPECIAL_TOKEN : +{ + : DEFAULT +} + +/* + +SKIP : +{ + : DEFAULT +} +*/ + + +MORE : +{ + < ~[] > +} + +/* PSEUDO-RESERVED WORDS */ + +TOKEN [IGNORE_CASE]: +{ + | + | + | + | + | + | + | + +| // PRAGMA INLINE +} + +/* PL/SQL RESERVED WORDS */ +/** + * 2006-05-20 - Matthias Hendler - Removed: + * Added: , , , + * , , , + * , + */ + +TOKEN [IGNORE_CASE]: +{ + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | +<_DEFAULT: "DEFAULT"> | + | + | + | + | // Needed for 11G Trigger Syntax + | + | + | + | + | + | + | + | // Needed for 11G Trigger Syntax + | + | + | + | + | + | + | + | + | + | +// | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | //SRT 2011-04-17 + | + | + | + | + | //SRT 2011-04-17 + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | +// | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | //SRT 2011-04-17 + | + | + | + | + | + | + | + | + | +// | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | +// | // Mark the start of a Q-quoted string, e.g. Q'[ This string contains a single-quote(') ]' + | + | + | + | + | + | + | + | + | //SRT 2011-04-17 + | //SRT 2011-04-17 + | + | + | + | + | + | //SRT 2011-04-17 + | + | + | + | + | + | + | + | + | + | + | + | + | + | //SRT 2011-04-17 + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | //SRT 2011-04-17 + | + | //SRT 2011-04-17 + | + | //SRT 2011-04-17 + | + | //SRT 2011-04-17 + + + | + | + +// are they reserved or not ? +// most are not reserved, but cannot use just "WHERE" etc instead - resolves as identifier ! +// | +// | +// | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //11G Trigger Syntax +| //XE testing +| //XE testing +| //XE testing non-PLSQL functions +| //XE testing non-PLSQL functions +| //XE testing non-PLSQL functions +| //XE testing non-PLSQL functions +| //XE testing non-PLSQL functions +| //XE testing non-PLSQL functions +| //XE testing non-PLSQL functions +| //XE testing non-PLSQL functions +| //XE testing non-PLSQL functions +//SQLPlus Commands +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +} + +/** + * 2006-05-20 - Matthias Hendler - Added #GERMAN_SPECIAL_CHARACTERS and #SPECIAL_CHARACTERS. + * Added ":" to + */ +TOKEN : +{ +< #GERMAN_SPECIAL_CHARACTERS: "Ä" | "Ö" | "Ü" | "ä" | "ü" | "ö" | "ß" > +| +< #LETTER: ["A"-"Z"] | ["a"-"z"] | > +| +< #DIGIT: ["0"-"9"]> +| +< #_CHARACTER: <_CHARACTER_WO_ASTERISK> | "'" > +| +< #_CHARACTER_WO_ASTERISK: | | "(" | ")" | "+" | "-" | "*" | "/" | "<" | ">" + | "=" | "!" | "~" | "^" | ";" | ":" | "." | "@" | "%" | "," | "\"" | "#" + | "$" | "&" | "_" | "|" | "{" | "}" | "?" | "[" | "]" + | " " | "\t" > +| +< #SPECIAL_CHARACTERS: "á" | "Ž" | "™" | "š" | "„" | "”" | "ý" | "²" | "€" | "³" | "µ"> +| +< #DELIMITER: "+" | "%" | "'" | "\"" | "." | "/" | "(" | ")" | ":" | "," | "*" | "=" | "<" | ">" | "@" | ";" | "-"> +| +< IDENTIFIER: + ( ("$" | ":" | ) ( | | "$" | "_" | "#" )* ) // 2006-05-17 - Matthias Hendler - Bind variablen werden nun als Identifier akzeptiert. + //SRT Does NOT seem to like identifiers 2 or fewer characters( ( ) ) + //( ( ) ) + //( ( "$" ) ) + //( ( "_" ) ) + //( ( "#" ) ) + | + ( ( | "$" ) ( | | "$" | "_" | "#" )* ) + | +//SRT ( "\"" (<_CHARACTER_WO_ASTERISK>)* "\"" ) + ( "\"" ( | | "$" | "_" | "#" )* "\"" ) +> +| +< UNSIGNED_NUMERIC_LITERAL: ( ["e","E"] (["-","+"])? )? (["f","F","d","D"])? > +| +< #FLOAT_LITERAL: ( "." )? | "." > +| +< #INTEGER_LITERAL: ( )+ > +| + +< #_WHATEVER_CHARACTER_WO_ASTERISK: (~["'"]) > +| +< CHARACTER_LITERAL: "'" (<_WHATEVER_CHARACTER_WO_ASTERISK> | )? "'" > +//|< STRING_LITERAL: +// (["q","Q"])* "'" (<_WHATEVER_CHARACTER_WO_ASTERISK> | | "''")* "'" +//> //Cope with Q-quoted stringswithout single quotes in them +|< STRING_LITERAL: +// Hard-code the most likely q-quote strings + "'" (<_WHATEVER_CHARACTER_WO_ASTERISK> | | "''")* "'" +|(["n","N"]) "'" (<_WHATEVER_CHARACTER_WO_ASTERISK> | | "''")* "'" //National Character Set String +|(["q","Q"]) "'" (<_WHATEVER_CHARACTER_WO_ASTERISK> | | "''")* "'" // Bug 160632 +|(["q","Q"]) "'[" (~["[","]"])* "]'" +|(["q","Q"]) "'{" (~["{","}"])* "}'" +|(["q","Q"]) "'<" (~["<",">"])* ">'" +|(["q","Q"]) "'(" (~["(",")"])* ")'" +|(["q","Q"]) "'/" (~["/"])* "/'" +|(["q","Q"]) "'!" (~["!"])* "!'" +|(["q","Q"]) "'#" (~["#"])* "#'" +> //Cope with Q-quoted stringswithout single quotes in them +| +< #_WHATEVER_CHARACTER_WO_QUOTE: (~["\""]) > +| +< QUOTED_LITERAL: "\"" (<_WHATEVER_CHARACTER_WO_QUOTE> | | "\\\"")* "\"" > +| +< SQLDATA_CLASS: "SQLData" > +| +< CUSTOMDATUM_CLASS: "CustomDatum" > +| +< ORADATA_CLASS: "OraData" > +| +< JAVA_INTERFACE_CLASS: ( "SQLData" | "CustomDatum" | "OraData" ) > +//| +//< #BOOLEAN_LITERAL: "TRUE" | "FALSE" > +| + +} + +//SRT 2011-04-17 - START +ASTKEYWORD_RESERVED KEYWORD_RESERVED (): {} +{ +// PL/SQL RESERVED WORDS - V$RESERVED.RESERVED='Y' +( +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| <_DEFAULT> +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +|
+| +| +| +| +| +| +| +| +| +| +| +| +) + { jjtThis.setImage(token.toString()) ; jjtThis.value = token ; return jjtThis ; } +} + +ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} +{ +// PL/SQL UNRESERVED KEYWORDS - V$RESERVED.RESERVED='N' +( + | + | +//| +| +//| +//| test_unreserved_keyword.pks +//| +//| +| +//| test_unreserved_keyword.pks +//| +//| +//| +//| +| +| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| test_unreserved_keyword.pks +| +| +| +| //PLDoc Bug 3512149 test_unreserved_keyword.pks +//| +| +| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| test_unreserved_keyword.pks +//| +| //-test_unreserved_keyword.pks +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| test_unreserved_keyword.pks +//| +//| +| +//| +//| +//| // RESERVED WORD !!! +| //test_unreserved_keyword.pks +//| +//| +//| +//| +| +//| +//| +| +//| +| +//| +//| +//| +//| +//| +| +| +| +| +//| +//| +//| +//| +//| +//| +//| +| +//| +| +//| +//| +| +//| +| +| +//| +//| +//| +| +| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +| +| +//| +| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +| +| +//| +//| +//| +//| +| +| +//| +//| +//| +//| +| +//| +//| +//| +//| +| +| +//| +//| +| +//| +//| +//| +//| +//| +| // SQL*Plus command +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +| +//| +| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +| +| +//| +//| +//| +| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +| +| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//|
+| +//| +//| +//| +//| +| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +| +| // FORALL i I INDICES OF collection - SPARSE COLLECTIONS +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +| +| +| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +| +| +| +//| +| +//| +| +//| +//| +//| +//| +| +| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +//| +| +| +//| +//|
+//| +//| +//| +//| +| // Bug 3512150 +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +| +//| +| +//| +//| +//| +//| +//| +| +//| +//| +//| +//| +| +//| +//| +| +//| +//| +//| +//| +//| +//| +//| +//| +| +//| +| +//| +//| +//| +| +//| +| +//| +//| +//| +//| +//| +| +//|