diff --git a/BUILDING.md b/BUILDING.md index d02537a33d..f5839cf894 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,8 +1,8 @@ # How to build PMD -PMD uses [Maven](https://maven.apache.org/) and requires at least Java 10 for building. -You can get Java 10 from [Oracle](http://www.oracle.com/technetwork/java/javase/downloads/index.html) -or from the [OpenJDK Project](http://jdk.java.net). +PMD uses [Maven](https://maven.apache.org/) and requires at least Java 11 for building. +You can get Java 11 from [Oracle](http://www.oracle.com/technetwork/java/javase/downloads/index.html) +or from [AdoptOpenJdk](https://adoptopenjdk.net/). PMD uses the [maven wrapper](https://github.com/takari/maven-wrapper), so you can simply build PMD as following: @@ -16,7 +16,7 @@ This will create the zip files in the directory `pmd-dist/target`: That's all ! -**Note:** While Java 10 is required for building, running PMD only requires Java 7 (or Java 8 for Apex and the Designer). +**Note:** While Java 11 is required for building, running PMD only requires Java 7 (or Java 8 for Apex and the Designer). ## How to build the documentation? diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68b0cdc2ce..372eab4e2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ By participating in this project you agree to abide by its terms. You can find the code of conduct in the file [code_of_conduct.md](code_of_conduct.md). -| NB: the rule designer is developed over at [pmd/pmd-designer](https://github.com/pmd/pmd-designer). Please refer to the specific [contributor documentation](https://github.com/pmd/pmd-designer#contributing) if your issue, feature request or PR touches the designer. | +| NB: the rule designer is developed over at [pmd/pmd-designer](https://github.com/pmd/pmd-designer). Please refer to the specific [contributor documentation](https://github.com/pmd/pmd-designer/blob/master/CONTRIBUTING.md) if your issue, feature request or PR touches the designer. | | --- | ## Pull requests diff --git a/README.md b/README.md index e54a445913..fa1852e804 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Modelica, PLSQL, Apache Velocity, XML, XSL, Scala. Additionally it includes **CPD**, the copy-paste-detector. CPD finds duplicated code in C/C++, C#, Dart, Fortran, Go, Groovy, Java, JavaScript, JSP, Kotlin, Lua, Matlab, Modelica, -Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex, Scala, Swift and Visualforce. +Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex, Scala, Swift, Visualforce and XML. ## Support diff --git a/docs/images/androidsdkmanagericon.png b/docs/images/androidsdkmanagericon.png deleted file mode 100644 index 65014a2604..0000000000 Binary files a/docs/images/androidsdkmanagericon.png and /dev/null differ diff --git a/docs/images/authorizegithubscreen2.png b/docs/images/authorizegithubscreen2.png deleted file mode 100644 index 2f6b6ca0fa..0000000000 Binary files a/docs/images/authorizegithubscreen2.png and /dev/null differ diff --git a/docs/images/authorizeongithub.png b/docs/images/authorizeongithub.png deleted file mode 100644 index 9380f80c30..0000000000 Binary files a/docs/images/authorizeongithub.png and /dev/null differ diff --git a/docs/images/helpapi-01.png b/docs/images/helpapi-01.png deleted file mode 100644 index 51308a163d..0000000000 Binary files a/docs/images/helpapi-01.png and /dev/null differ diff --git a/docs/images/helpapi.svg b/docs/images/helpapi.svg deleted file mode 100644 index 0ddc9227e5..0000000000 --- a/docs/images/helpapi.svg +++ /dev/null @@ -1,1661 +0,0 @@ - - - - - - - - - - - - - - - - - sample help text sample help text sample help text sample help text sample help text sample help text sample help - - - - - - - - - - - - - - - - - - - - - - - Getting Started text sample help text sample help text sample help text sample help text sample help text sample help text sample - - - - - - - - - - - - - Learning Coursesample help text sample help text sample help text sample help text sample help text sample help text sample help - - - - - - - - - - - - - - - - - - - sample help text sample help text sample help text sample help text sample help text sample - - - - - - - - - - - - - - - Help API - - - -pulling from API - - - -pulling from API - - - -pulling from API - - - -pulling from API - - - - - - - website #1 - - website #2 - - website #4 - - website #3 - - diff --git a/docs/images/illustratoroptions.png b/docs/images/illustratoroptions.png deleted file mode 100644 index d043971ddb..0000000000 Binary files a/docs/images/illustratoroptions.png and /dev/null differ diff --git a/docs/images/itermexample.png b/docs/images/itermexample.png deleted file mode 100644 index e412af02a2..0000000000 Binary files a/docs/images/itermexample.png and /dev/null differ diff --git a/docs/images/jekyll.png b/docs/images/jekyll.png deleted file mode 100644 index ebaf27d0e4..0000000000 Binary files a/docs/images/jekyll.png and /dev/null differ diff --git a/docs/images/killalljekyll.png b/docs/images/killalljekyll.png deleted file mode 100644 index aa8adaa2a6..0000000000 Binary files a/docs/images/killalljekyll.png and /dev/null differ diff --git a/docs/images/liningup.png b/docs/images/liningup.png deleted file mode 100644 index ede4dc07c5..0000000000 Binary files a/docs/images/liningup.png and /dev/null differ diff --git a/docs/images/workflowarrow.png b/docs/images/workflowarrow.png deleted file mode 100644 index 91a3e81618..0000000000 Binary files a/docs/images/workflowarrow.png and /dev/null differ diff --git a/docs/pages/pmd/devdocs/building.md b/docs/pages/pmd/devdocs/building.md index c76ed1db04..0fcd7ce293 100644 --- a/docs/pages/pmd/devdocs/building.md +++ b/docs/pages/pmd/devdocs/building.md @@ -10,9 +10,9 @@ author: Tom Copeland, Xavier Le Vourch # Compiling PMD -* JDK 10 or higher +* JDK 11 or higher -{% include note.html content="While Java 10 is required for building, running PMD only requires Java 7 (or Java 8 for Apex and the Designer)." %} +{% include note.html content="While Java 11 is required for building, running PMD only requires Java 7 (or Java 8 for Apex and the Designer)." %} You’ll need to either check out the source code or download the latest source release. Assuming you’ve got the latest source release, unzip it to a directory: diff --git a/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md b/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md index 7a0513fe89..b6d94683e1 100644 --- a/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md +++ b/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md @@ -50,12 +50,18 @@ All you need to do is follow this few steps: **You are almost there!** -4. Please don't forget to add some test, you can again.. look at Go implementation ;) +4. Update the list of supported languages + + - Write the fully-qualified name of your Language class to the file `src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language` + + - Update the test that asserts the list of supported languages by updating the `SUPPORTED_LANGUAGES` constant in [BinaryDistributionIT](https://github.com/pmd/pmd/blob/master/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java) + +5. Please don't forget to add some test, you can again.. look at Go implementation ;) If you read this far, I'm keen to think you would also love to support some extra CPD configuration (ignore imports or crazy things like that) If that's your case , you came to the right place! -5. You can add your custom properties using a Token filter +6. You can add your custom properties using a Token filter - For Antlr grammars all you need to do is implement your own [AntlrTokenFilter](https://github.com/pmd/pmd/blob/master/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrTokenFilter.java) diff --git a/docs/pages/pmd/devdocs/major_contributions/adding_new_language.md b/docs/pages/pmd/devdocs/major_contributions/adding_new_language.md index f8ae647569..ae36818f10 100644 --- a/docs/pages/pmd/devdocs/major_contributions/adding_new_language.md +++ b/docs/pages/pmd/devdocs/major_contributions/adding_new_language.md @@ -69,15 +69,62 @@ folder: pmd/devdocs * Add for each version of your language a call to `addVersion` in your language module’s constructor. * Create the service registration via the text file `src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language`. Add your fully qualified class name as a single line into it. -## 12. Create an abstract rule class for the language +## 12. Add AST regression tests + +For languages, that use an external library for parsing, the AST can easily change when upgrading the library. +Also for languages, where we have the grammar under our control, it useful to have such tests. + +The tests parse one or more source files and generate a textual representation of the AST. This text is compared +against a previously recorded version. If there are differences, the test fails. + +This helps to detect anything in the AST structure, that changed, maybe unexpectedly. + +* Create a test class in the package `net.sourceforge.pmd.lang.$lang.ast` with the name `$langTreeDumpTest`. +* This test class must extend `net.sourceforge.pmd.lang.ast.test.BaseTreeDumpTest`. Note: This class + is written in kotlin and is available in the module "lang-test". +* Add a default constructor, that calls the super constructor like so: + + ```java + public $langTreeDumpTest() { + super(NodePrintersKt.getSimpleNodePrinter(), ".$extension"); + } + ``` + + Replace "$lang" and "$extension" accordingly. +* Implement the method `getParser()`. It must return a + subclass of `net.sourceforge.pmd.lang.ast.test.BaseParsingHelper`. See + `net.sourceforge.pmd.lang.ecmascript.ast.JsParsingHelper` for a example. + With this parser helper you can also specify, where the test files are searched, by using + the method `withResourceContext(Class, String)`. +* Add one or more test methods. Each test method parses one file and compares the result. The base + class has a helper method `doTest(String)` that does all the work. This method just needs to be called: + + ```java + @Test + public void myFirstAstTest() { + doTest("filename-without-extension"); + } + ``` +* On the first test run the test fails. A text file (with the extension `.txt`) is created, that records the + current AST. On the next run, the text file is used as comparison and the test should pass. Don't forget + to commit the generated text file. + +A complete example can be seen in the JavaScript module: `net.sourceforge.pmd.lang.ecmascript.ast.JsTreeDumpTest`. +The test resources are in the subpackage "testdata": `pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/`. + +The Scala module also has a test, written in Kotlin instead of Java: +`net.sourceforge.pmd.lang.scala.ast.ScalaParserTests`. + + +## 13. Create an abstract rule class for the language * Extend `AbstractRule` and implement the parser visitor interface for your language *(see AbstractVmRule for example)* * All other rules for your language should extend this class. The purpose of this class is to implement visit methods for all AST types to simply delegate to default behavior. This is useful because most rules care only about specific AST nodes, but PMD needs to know what to do with each node - so this just lets you use default behavior for nodes you don’t care about. -## 13. Create rules -* Rules are created by extending the abstract rule class created in step 12 *(see `EmptyForeachStmtRule` for example)* +## 14. Create rules +* Rules are created by extending the abstract rule class created in step 13 *(see `EmptyForeachStmtRule` for example)* * Creating rules is already pretty well documented in PMD - and it’s no different for a new language, except you may have different AST nodes. -## 14. Test the rules +## 15. Test the rules * See BasicRulesTest for example * You have to create a rule set for your language *(see vm/basic.xml for example)* * For each rule in this set you want to test, call `addRule` method in setUp of the unit test diff --git a/docs/pages/pmd/userdocs/cpd/cpd.md b/docs/pages/pmd/userdocs/cpd/cpd.md index bb341e8e2a..d56bfacf23 100644 --- a/docs/pages/pmd/userdocs/cpd/cpd.md +++ b/docs/pages/pmd/userdocs/cpd/cpd.md @@ -207,7 +207,6 @@ This behavior has been introduced to ease CPD integration into scripts or hooks, ## Supported Languages -* Apex * C# * C/C++ * Dart @@ -220,15 +219,18 @@ This behavior has been introduced to ease CPD integration into scripts or hooks, * Kotlin * Lua * Matlab +* Modelica * Objective-C * Perl * PHP * PL/SQL * Python * Ruby +* Salesforce.com Apex * Scala * Swift * Visualforce +* XML ## Available report formats diff --git a/docs/pages/pmd/userdocs/making_rulesets.md b/docs/pages/pmd/userdocs/making_rulesets.md index 16421036f2..ca5e32dca0 100644 --- a/docs/pages/pmd/userdocs/making_rulesets.md +++ b/docs/pages/pmd/userdocs/making_rulesets.md @@ -18,7 +18,7 @@ author: Tom Copeland , Clément Fournier + ``` Adding that element into the `ruleset` element adds the rule [EmptyCatchBlock](pmd_rules_java_errorprone.html#emptycatchblock) @@ -80,10 +80,10 @@ How you can configure individual rules is described on [Configuring Rules](pmd_u You can also reference rules in bulk by referencing a complete category or ruleset, possibly excluding certain rules, like in the following: ```xml - + - + ``` Here, the `ref` attribute references a whole category. You can also use a file system path or classpath relative path. In any case, the path must address an accessible ruleset XML file. @@ -108,13 +108,13 @@ You can exclude some files from being processed by a ruleset using **exclude pat xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> - My ruleset + My ruleset - .*/some/package/.* - .*/some/other/package/FunkyClassNamePrefix.* - .*/some/package/ButNotThisClass.* + .*/some/package/.* + .*/some/other/package/FunkyClassNamePrefix.* + .*/some/package/ButNotThisClass.* - + ``` diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 8abb75f161..e736d94e8a 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -19,11 +19,68 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy +#### CPD now supports XML as well + +Thanks to [Fernando Cosso](https://github.com/xnYi9wRezm) CPD can now find duplicates in XML files as well. +This is useful to find duplicated sections in XML files. + +#### New Rules + +* The new Java Rule {% rule "java/bestpractices/LiteralsFirstInComparisons" %} (`java-bestpractices`) + find String literals, that are used in comparisons and are not positioned first. Using the String literal + as the receiver of e.g. `equals` helps to avoid NullPointerExceptions. + + This rule is replacing the two old rules {% rule "java/bestpractices/PositionLiteralsFirstInComparisons" %} + and {% rule "java/bestpractices/PositionLiteralsFirstInCaseInsensitiveComparisons" %} and extends the check + for the methods `compareTo`, `compareToIgnoreCase` and `contentEquals` in addition to `equals` and + `equalsIgnoreCase`. + + Note: This rule also replaces the two mentioned rules in Java's quickstart ruleset. + ### Fixed Issues +* apex-bestpractices + * [#2468](https://github.com/pmd/pmd/issues/2468): \[apex] Unused Local Variable fails on blocks +* core + * [#2484](https://github.com/pmd/pmd/issues/2484): \[core] Update maven-enforcer-plugin to require Java 118 +* java + * [#2472](https://github.com/pmd/pmd/issues/2472): \[java] JavaCharStream throws an Error on invalid escape +* java-bestpractices + * [#2288](https://github.com/pmd/pmd/issues/2288): \[java] JUnitTestsShouldIncludeAssert: Add support for Hamcrest MatcherAssert.assertThat +* java-errorprone + * [#2477](https://github.com/pmd/pmd/issues/2477): \[java] JUnitSpelling false-positive for JUnit5/4 tests +* swift + * [#2473](https://github.com/pmd/pmd/issues/2473): \[swift] Swift 5 (up to 5.2) support for CPD + ### API Changes +#### Deprecated APIs + +* {% jdoc !ca!core::lang.BaseLanguageModule#addVersion(String, LanguageVersionHandler, boolean) %} +* Some members of {% jdoc core::lang.ast.TokenMgrError %}, in particular, a new constructor is available + that should be preferred to the old ones +* {% jdoc core::lang.antlr.AntlrTokenManager.ANTLRSyntaxError %} + +#### Experimental APIs + +**Note:** Experimental APIs are identified with the annotation {% jdoc core::annotation.Experimental %}, +see its javadoc for details + +* The experimental methods in {% jdoc !ca!core::lang.BaseLanguageModule %} have been replaced by a +definitive API. + ### External Contributions +* [#2446](https://github.com/pmd/pmd/pull/2446): \[core] Update maven-compiler-plugin to 3.8.1 - [Artem Krosheninnikov](https://github.com/KroArtem) +* [#2448](https://github.com/pmd/pmd/pull/2448): \[java] Operator Wrap check - [Harsh Kukreja](https://github.com/harsh-kukreja) +* [#2449](https://github.com/pmd/pmd/pull/2449): \[plsql] Additional info in SqlStatement, FormalParameter and FetchStatement - [Grzegorz Sudolski](https://github.com/zgrzyt93) +* [#2452](https://github.com/pmd/pmd/pull/2452): \[doc] Fix "Making Rulesets" doc sample code indentation - [Artur Dryomov](https://github.com/arturdryomov) +* [#2457](https://github.com/pmd/pmd/pull/2457): \[xml] Adding XML to CPD supported languages - [Fernando Cosso](https://github.com/xnYi9wRezm) +* [#2469](https://github.com/pmd/pmd/pull/2469): \[apex] fix false positive unused variable if only a method is called - [Gwilym Kuiper](https://github.com/gwilymatgearset) +* [#2475](https://github.com/pmd/pmd/pull/2475): \[swift] Swift 4.2-5.2 support - [kenji21](https://github.com/kenji21) +* [#2478](https://github.com/pmd/pmd/pull/2478): \[java] New rule: LiteralsFirstInComparisons - [John-Teng](https://github.com/John-Teng) +* [#2479](https://github.com/pmd/pmd/pull/2479): \[java] False positive with Hamcrest's assertThat - [andreoss](https://github.com/andreoss) +* [#2481](https://github.com/pmd/pmd/pull/2481): \[java] Fix JUnitSpellingRule false positive - [Artem Krosheninnikov](https://github.com/KroArtem) + {% endtocmaker %} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParser.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParser.java index dee36a94ce..59a4728f65 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParser.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParser.java @@ -8,8 +8,8 @@ import java.io.Reader; import net.sourceforge.pmd.lang.AbstractParser; import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.ParseException; +import net.sourceforge.pmd.lang.ast.RootNode; /** * Adapter for the Apex jorje parser @@ -23,7 +23,7 @@ public class ApexParser extends AbstractParser { } @Override - public Node parse(String fileName, Reader source) throws ParseException { + public RootNode parse(String fileName, Reader source) throws ParseException { return apexParser.parse(source); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNode.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNode.java index b21012ea31..f56ef63755 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNode.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNode.java @@ -5,37 +5,100 @@ package net.sourceforge.pmd.lang.apex.ast; import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.SourceCodePositioner; +import net.sourceforge.pmd.lang.ast.impl.AbstractNodeWithTextCoordinates; import apex.jorje.data.Location; import apex.jorje.data.Locations; import apex.jorje.semantic.ast.AstNode; import apex.jorje.semantic.exception.UnexpectedCodePathException; -abstract class AbstractApexNode extends AbstractApexNodeBase implements ApexNode { +abstract class AbstractApexNode extends AbstractNodeWithTextCoordinates, ApexNode> implements ApexNode { protected final T node; protected AbstractApexNode(T node) { - super(node.getClass()); this.node = node; } + // overridden to make them visible @Override - public ApexNode getChild(int index) { - return (ApexNode) super.getChild(index); + protected void addChild(AbstractApexNode child, int index) { + super.addChild(child, index); } @Override - public ApexNode getParent() { - return (ApexNode) super.getParent(); + protected void insertChild(AbstractApexNode child, int index) { + super.insertChild(child, index); + } + + /* package */ void calculateLineNumbers(SourceCodePositioner positioner, int startOffset, int endOffset) { + // end column will be interpreted as inclusive, while endOffset/endIndex + // is exclusive + endOffset -= 1; + + this.beginLine = positioner.lineNumberFromOffset(startOffset); + this.beginColumn = positioner.columnFromOffset(this.beginLine, startOffset); + this.endLine = positioner.lineNumberFromOffset(endOffset); + this.endColumn = positioner.columnFromOffset(this.endLine, endOffset); + + if (this.endColumn < 0) { + this.endColumn = 0; + } } @Override - @SuppressWarnings("unchecked") - public NodeStream> children() { - return (NodeStream>) super.children(); + public int getBeginLine() { + if (this.beginLine > 0) { + return this.beginLine; + } + Node parent = getParent(); + if (parent != null) { + return parent.getBeginLine(); + } + throw new RuntimeException("Unable to determine beginning line of Node."); + } + + @Override + public int getBeginColumn() { + if (this.beginColumn > 0) { + return this.beginColumn; + } + Node parent = getParent(); + if (parent != null) { + return parent.getBeginColumn(); + } + throw new RuntimeException("Unable to determine beginning column of Node."); + } + + @Override + public int getEndLine() { + if (this.endLine > 0) { + return this.endLine; + } + Node parent = getParent(); + if (parent != null) { + return parent.getEndLine(); + } + throw new RuntimeException("Unable to determine ending line of Node."); + } + + @Override + public int getEndColumn() { + if (this.endColumn > 0) { + return this.endColumn; + } + Node parent = getParent(); + if (parent != null) { + return parent.getEndColumn(); + } + throw new RuntimeException("Unable to determine ending column of Node."); + } + + @Override + public final String getXPathNodeName() { + return this.getClass().getSimpleName().replaceFirst("^AST", ""); } void calculateLineNumbers(SourceCodePositioner positioner) { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNodeBase.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNodeBase.java deleted file mode 100644 index ffde825c84..0000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNodeBase.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.ast; - -import net.sourceforge.pmd.lang.ast.AbstractNode; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.SourceCodePositioner; - -abstract class AbstractApexNodeBase extends AbstractNode { - - protected AbstractApexNodeBase(Class klass) { - super(klass.hashCode()); - } - - /* package */ void calculateLineNumbers(SourceCodePositioner positioner, int startOffset, int endOffset) { - // end column will be interpreted as inclusive, while endOffset/endIndex - // is exclusive - endOffset -= 1; - - this.beginLine = positioner.lineNumberFromOffset(startOffset); - this.beginColumn = positioner.columnFromOffset(this.beginLine, startOffset); - this.endLine = positioner.lineNumberFromOffset(endOffset); - this.endColumn = positioner.columnFromOffset(this.endLine, endOffset); - - if (this.endColumn < 0) { - this.endColumn = 0; - } - } - - /** - * Accept the visitor. * - */ - public abstract Object jjtAccept(ApexParserVisitor visitor, Object data); - - /** - * Accept the visitor. * - */ - public Object childrenAccept(ApexParserVisitor visitor, Object data) { - for (int i = 0; i < children.length; ++i) { - // we know that the children here are all ApexNodes - AbstractApexNodeBase apexNode = (AbstractApexNodeBase) children[i]; - apexNode.jjtAccept(visitor, data); - } - - return data; - } - - @Override - public int getBeginLine() { - if (this.beginLine > 0) { - return this.beginLine; - } - Node parent = getParent(); - if (parent != null) { - return parent.getBeginLine(); - } - throw new RuntimeException("Unable to determine beginning line of Node."); - } - - @Override - public int getBeginColumn() { - if (this.beginColumn > 0) { - return this.beginColumn; - } - Node parent = getParent(); - if (parent != null) { - return parent.getBeginColumn(); - } - throw new RuntimeException("Unable to determine beginning column of Node."); - } - - @Override - public int getEndLine() { - if (this.endLine > 0) { - return this.endLine; - } - Node parent = getParent(); - if (parent != null) { - return parent.getEndLine(); - } - throw new RuntimeException("Unable to determine ending line of Node."); - } - - @Override - public int getEndColumn() { - if (this.endColumn > 0) { - return this.endColumn; - } - Node parent = getParent(); - if (parent != null) { - return parent.getEndColumn(); - } - throw new RuntimeException("Unable to determine ending column of Node."); - } - - @Override - public final String getXPathNodeName() { - return this.getClass().getSimpleName().replaceFirst("^AST", ""); - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexNode.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexNode.java index d1dc78a4ae..3de1234882 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexNode.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexNode.java @@ -4,8 +4,7 @@ package net.sourceforge.pmd.lang.apex.ast; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.GenericNode; import apex.jorje.semantic.ast.AstNode; @@ -16,7 +15,7 @@ import apex.jorje.semantic.ast.AstNode; * * @param Type of the underlying Jorje node */ -public interface ApexNode extends Node { +public interface ApexNode extends GenericNode> { /** * Accept the visitor. @@ -24,16 +23,6 @@ public interface ApexNode extends Node { Object jjtAccept(ApexParserVisitor visitor, Object data); - /** - * Accept the visitor. * - * - * @deprecated This method is not useful, the logic for combining - * children values should be present on the visitor, not the node - */ - @Deprecated - Object childrenAccept(ApexParserVisitor visitor, Object data); - - /** * Get the underlying AST node. * @deprecated the underlying AST node should not be available outside of the AST node. @@ -43,18 +32,6 @@ public interface ApexNode extends Node { @Deprecated T getNode(); - - @Override - NodeStream> children(); - - - @Override - ApexNode getChild(int index); - - - @Override - ApexNode getParent(); - boolean hasRealLoc(); String getDefiningType(); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java index 7f0b5affa2..b5e83e4a88 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java @@ -39,7 +39,7 @@ public class ApexParser { return visitor.getTopLevel(); } - public ApexNode parse(final Reader reader) { + public ApexRootNode parse(final Reader reader) { try { final String sourceCode = IOUtils.toString(reader); final Compilation astRoot = parseApex(sourceCode); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java index 1ad0140bf2..9229ad18bc 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java @@ -16,7 +16,6 @@ import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.Token; import net.sourceforge.pmd.lang.apex.ApexParserOptions; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.SourceCodePositioner; import apex.jorje.data.Location; @@ -234,7 +233,7 @@ final class ApexTreeBuilder extends AstVisitor { } // The nodes having children built. - private final Stack nodes = new Stack<>(); + private final Stack> nodes = new Stack<>(); // The Apex nodes with children to build. private final Stack parents = new Stack<>(); @@ -281,10 +280,9 @@ final class ApexTreeBuilder extends AstVisitor { node.handleSourceCode(sourceCode); // Append to parent - Node parent = nodes.isEmpty() ? null : nodes.peek(); + AbstractApexNode parent = nodes.isEmpty() ? null : nodes.peek(); if (parent != null) { - parent.jjtAddChild(node, parent.getNumChildren()); - node.jjtSetParent(parent); + parent.addChild(node, parent.getNumChildren()); } // Build the children... @@ -304,27 +302,20 @@ final class ApexTreeBuilder extends AstVisitor { private void addFormalComments() { for (ApexDocTokenLocation tokenLocation : apexDocTokenLocations) { - ApexNode parent = tokenLocation.nearestNode; + AbstractApexNode parent = tokenLocation.nearestNode; if (parent != null) { ASTFormalComment comment = new ASTFormalComment(tokenLocation.token); comment.calculateLineNumbers(sourceCodePositioner, tokenLocation.index, tokenLocation.index + tokenLocation.token.getText().length()); - // move existing nodes so that we can insert the comment as the first node - for (int i = parent.getNumChildren(); i > 0; i--) { - parent.jjtAddChild(parent.getChild(i - 1), i); - } - - parent.jjtAddChild(comment, 0); - comment.jjtSetParent(parent); + parent.insertChild(comment, 0); } } } private void buildFormalComment(AstNode node) { if (parents.peek() == node) { - ApexNode parent = (ApexNode) nodes.peek(); - assignApexDocTokenToNode(node, parent); + assignApexDocTokenToNode(node, nodes.peek()); } } @@ -337,7 +328,7 @@ final class ApexTreeBuilder extends AstVisitor { * @param jorjeNode the original node * @param node the potential parent node, to which the comment could belong */ - private void assignApexDocTokenToNode(AstNode jorjeNode, ApexNode node) { + private void assignApexDocTokenToNode(AstNode jorjeNode, AbstractApexNode node) { Location loc = jorjeNode.getLoc(); if (!Locations.isReal(loc)) { // Synthetic nodes such as "" don't have a location in the @@ -411,7 +402,7 @@ final class ApexTreeBuilder extends AstVisitor { private static class ApexDocTokenLocation { int index; Token token; - ApexNode nearestNode; + AbstractApexNode nearestNode; int nearestNodeDistance; ApexDocTokenLocation(int index, Token token) { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierRule.java index ed12d54d1b..f01b49bfee 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierRule.java @@ -33,6 +33,10 @@ public class AvoidGlobalModifierRule extends AbstractApexRule { addViolation(data, node); } + // Note, the rule reports the whole class, since that's enough and stops to visit right here. + // It also doesn't use rulechain, since it the top level type needs to global. + // if a member is global, that class has to be global as well to be valid apex. + // See also https://github.com/pmd/pmd/issues/2298 return data; } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java index b898dd6999..8e1759caf9 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java @@ -4,11 +4,14 @@ package net.sourceforge.pmd.lang.apex.rule.bestpractices; +import java.util.ArrayList; import java.util.List; import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTReferenceExpression; import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration; import net.sourceforge.pmd.lang.apex.ast.ASTVariableExpression; +import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; public class UnusedLocalVariableRule extends AbstractApexRule { @@ -21,13 +24,20 @@ public class UnusedLocalVariableRule extends AbstractApexRule { String variableName = node.getImage(); ASTBlockStatement variableContext = node.getFirstParentOfType(ASTBlockStatement.class); - List potentialUsages = variableContext.findDescendantsOfType(ASTVariableExpression.class); - for (ASTVariableExpression usage : potentialUsages) { + List> potentialUsages = new ArrayList<>(); + + // Variable expression catch things like the `a` in `a + b` + potentialUsages.addAll(variableContext.findDescendantsOfType(ASTVariableExpression.class)); + // Reference expressions catch things like the `a` in `a.foo()` + potentialUsages.addAll(variableContext.findDescendantsOfType(ASTReferenceExpression.class)); + + for (ApexNode usage : potentialUsages) { if (usage.getParent() == node) { continue; } - if (usage.getImage().equals(variableName)) { + + if (usage.hasImageEqualTo(variableName)) { return data; } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidDmlStatementsInLoopsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidDmlStatementsInLoopsRule.java index b6c07d3085..c0478106ee 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidDmlStatementsInLoopsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidDmlStatementsInLoopsRule.java @@ -15,7 +15,6 @@ import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement; import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement; import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement; import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; -import net.sourceforge.pmd.lang.ast.AbstractNode; import net.sourceforge.pmd.lang.ast.Node; public class AvoidDmlStatementsInLoopsRule extends AbstractApexRule { @@ -68,7 +67,7 @@ public class AvoidDmlStatementsInLoopsRule extends AbstractApexRule { return data; } - private boolean insideLoop(AbstractNode node) { + private boolean insideLoop(Node node) { Node n = node.getParent(); while (n != null) { diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/AvoidGlobalModifier.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/AvoidGlobalModifier.xml index 470fbdee69..a182455e52 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/AvoidGlobalModifier.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/AvoidGlobalModifier.xml @@ -28,26 +28,32 @@ global interface Foo { Global method 1 + 1 - + ]]> + Global inner interface 1 + 1 - + ]]> + #1348 [apex] AvoidGlobalModifierRule gives warning even when its a REST webservice - false positive diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml index 5e5e7ed803..8d46b6c826 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml @@ -51,6 +51,16 @@ public class Foo { return 'some other string'; } } + + public void hasMethodCalledOnIt() { + String foo = 'foobar'; + foo.substringAfter('foo'); + } + + public void handlesChainedMethods() { + String foo = 'foobar'; + foo.substringAfter('f').substringAfter('b'); + } } ]]> diff --git a/pmd-core/src/main/ant/alljavacc.xml b/pmd-core/src/main/ant/alljavacc.xml index 49b8b00e7d..9d0d65f12e 100644 --- a/pmd-core/src/main/ant/alljavacc.xml +++ b/pmd-core/src/main/ant/alljavacc.xml @@ -97,6 +97,15 @@ + + + + + + + (); } - LanguageVersion languageVersion = new LanguageVersion(this, languageVersions[0], languageVersionHandler); + LanguageVersion languageVersion = new LanguageVersion(this, version, languageVersionHandler); distinctVersions.add(languageVersion); - for (String version : languageVersions) { - versions.put(version, languageVersion); + checkNotPresent(version); + versions.put(version, languageVersion); + for (String alias : versionAliases) { + checkNotPresent(alias); + versions.put(alias, languageVersion); } if (isDefault) { - assert defaultVersion == null - : "Default version already set to " + defaultVersion + ", cannot set it to " + languageVersion; + if (defaultVersion != null) { + throw new IllegalStateException( + "Default version already set to " + defaultVersion + ", cannot set it to " + languageVersion); + } defaultVersion = languageVersion; } } + private void checkNotPresent(String alias) { + if (versions.containsKey(alias)) { + throw new IllegalArgumentException("Version key '" + alias + "' is duplicated"); + } + } + + + /** + * Adds a non-default version with the given identifier. + * + * @throws IllegalArgumentException If the string key or any of the + * aliases conflict with other already + * recorded versions + */ + protected void addVersion(String version, LanguageVersionHandler languageVersionHandler, String... versionAliases) { + addVersion(version, languageVersionHandler, false, versionAliases); + } + + /** + * Adds a version with the given identifier, and sets it as the default. + * + * @throws IllegalStateException If the default version is already set + * @throws IllegalArgumentException If the string key or any of the + * aliases conflict with other already + * recorded versions + */ + protected void addDefaultVersion(String version, LanguageVersionHandler languageVersionHandler, String... versionAliases) { + addVersion(version, languageVersionHandler, true, versionAliases); + } + + /** + * @deprecated use {@link #addVersion(String, LanguageVersionHandler, String...)} or {@link #addDefaultVersion(String, LanguageVersionHandler, String...)} + */ + @Deprecated protected void addVersion(String version, LanguageVersionHandler languageVersionHandler, boolean isDefault) { - addVersions(languageVersionHandler, isDefault, version); + addVersion(version, languageVersionHandler, isDefault, new String[0]); } @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/Parser.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/Parser.java index cba8e671bb..77c18e0a9b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/Parser.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/Parser.java @@ -6,8 +6,8 @@ package net.sourceforge.pmd.lang; import java.io.Reader; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.ParseException; +import net.sourceforge.pmd.lang.ast.RootNode; /** * Produces an AST from a source file. Instances of this interface must @@ -39,7 +39,7 @@ public interface Parser { * In case the source code could not be parsed, probably due to * syntactical errors. */ - Node parse(String fileName, Reader source) throws ParseException; + RootNode parse(String fileName, Reader source) throws ParseException; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java deleted file mode 100644 index d5346f7220..0000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java +++ /dev/null @@ -1,404 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.ast; - -import java.util.Objects; - -import org.apache.commons.lang3.ArrayUtils; -import org.checkerframework.checker.nullness.qual.Nullable; - -import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.dfa.DataFlowNode; -import net.sourceforge.pmd.util.DataMap; -import net.sourceforge.pmd.util.DataMap.DataKey; -import net.sourceforge.pmd.util.DataMap.SimpleDataKey; - -/** - * Base class for all implementations of the Node interface. - * - *

Please use the {@link Node} interface wherever possible and - * not this class, unless you're compelled to do so. - * - *

Note that nearly all methods of the {@link Node} interface - * will have default implementations with PMD 7.0.0, so that it - * will not be necessary to extend this class directly. - */ -public abstract class AbstractNode implements Node { - - private static final Node[] EMPTY_ARRAY = new Node[0]; - - @Deprecated - public static final SimpleDataKey LEGACY_USER_DATA = DataMap.simpleDataKey("legacy user data"); - - // lazy initialized, many nodes don't need it - private @Nullable DataMap> userData; - - /** - * @deprecated Use {@link #getParent()} - */ - @Deprecated - protected Node parent; - // never null, never contains null elements - protected Node[] children = EMPTY_ARRAY; - /** @deprecated Use {@link #getIndexInParent()} */ - @Deprecated - protected int childIndex; - /** @deprecated Use {@link #jjtGetId()} if you are a jjtree node. */ - @Deprecated - protected int id; - /** @deprecated This will be removed to delegate to the tokens for nodes that are backed by tokens. */ - @Deprecated - protected int beginLine = -1; - /** @deprecated This will be removed to delegate to the tokens for nodes that are backed by tokens. */ - @Deprecated - protected int endLine; - /** @deprecated This will be removed to delegate to the tokens for nodes that are backed by tokens. */ - @Deprecated - protected int beginColumn = -1; - /** @deprecated This will be removed to delegate to the tokens for nodes that are backed by tokens. */ - @Deprecated - protected int endColumn; - // Those should have been private. - @Deprecated - protected GenericToken firstToken; - @Deprecated - protected GenericToken lastToken; - private DataFlowNode dataFlowNode; - // @Deprecated? - private String image; - - public AbstractNode(final int id) { - this.id = id; - } - - public AbstractNode(final int id, final int theBeginLine, final int theEndLine, final int theBeginColumn, - final int theEndColumn) { - this(id); - - beginLine = theBeginLine; - endLine = theEndLine; - beginColumn = theBeginColumn; - endColumn = theEndColumn; - } - - - @Override - public Node getParent() { - return jjtGetParent(); - } - - @Override - public int getIndexInParent() { - return childIndex; - } - - @Override - public Node getChild(final int index) { - if (children == null) { - throw new IndexOutOfBoundsException(); - } - return children[index]; - } - - @Override - public int getNumChildren() { - return jjtGetNumChildren(); - } - - - /** - * @deprecated This is never used and is trivial, will be removed from this class. - */ - @Deprecated - public boolean isSingleLine() { - return beginLine == endLine; - } - - @Override - @Deprecated - @InternalApi - public void jjtOpen() { - // to be overridden by subclasses - } - - @Override - @Deprecated - @InternalApi - public void jjtClose() { - // to be overridden by subclasses - } - - @Override - @Deprecated - @InternalApi - public void jjtSetParent(final Node parent) { - this.parent = parent; - } - - @Override - @Deprecated - public Node jjtGetParent() { - return parent; - } - - @Override - @Deprecated - @InternalApi - public void jjtAddChild(final Node child, final int index) { - if (index >= children.length) { - final Node[] newChildren = new Node[index + 1]; - System.arraycopy(children, 0, newChildren, 0, children.length); - children = newChildren; - } - children[index] = child; - child.jjtSetChildIndex(index); - child.jjtSetParent(this); - } - - @Override - @Deprecated - @InternalApi - public void jjtSetChildIndex(final int index) { - childIndex = index; - } - - - @Override - @Deprecated - public Node jjtGetChild(final int index) { - return children[index]; - } - - @Override - @Deprecated - public int jjtGetNumChildren() { - return children.length; - } - - - /** - * @deprecated Will be made protected with 7.0.0. - */ - @Override - @Deprecated - public int jjtGetId() { - return id; - } - - @Override - public String getImage() { - return image; - } - - @Override - @Deprecated - public void setImage(final String image) { - this.image = image; - } - - @Override - public boolean hasImageEqualTo(final String image) { - return Objects.equals(this.getImage(), image); - } - - @Override - public int getBeginLine() { - return beginLine; - } - - /** - * @deprecated This will be removed with 7.0.0 - */ - @Deprecated - @InternalApi - public void testingOnlySetBeginLine(int i) { - this.beginLine = i; - } - - @Override - public int getBeginColumn() { - if (beginColumn == -1) { - if (children.length > 0) { - return children[0].getBeginColumn(); - } else { - throw new RuntimeException("Unable to determine beginning line of Node."); - } - } else { - return beginColumn; - } - } - - /** - * @deprecated This will be removed with 7.0.0 - */ - @Deprecated - @InternalApi - public void testingOnlySetBeginColumn(final int i) { - this.beginColumn = i; - } - - @Override - public int getEndLine() { - return endLine; - } - - /** - * @deprecated This will be removed with 7.0.0 - */ - @Deprecated - @InternalApi - public void testingOnlySetEndLine(final int i) { - this.endLine = i; - } - - @Override - public int getEndColumn() { - return endColumn; - } - - /** - * @deprecated This will be removed with 7.0.0 - */ - @Deprecated - @InternalApi - public void testingOnlySetEndColumn(final int i) { - this.endColumn = i; - } - - @Override - public DataFlowNode getDataFlowNode() { - if (this.dataFlowNode == null) { - if (this.parent != null) { - return parent.getDataFlowNode(); - } - return null; // TODO wise? - } - return dataFlowNode; - } - - @Override - public void setDataFlowNode(final DataFlowNode dataFlowNode) { - this.dataFlowNode = dataFlowNode; - } - - /** - * Returns true if this node has a descendant of any type among the provided types. - * - * @param types Types to test - * - * @deprecated This is implemented inefficiently, with PMD 7 Node streams - * will provide a better alternative. We cannot ensure binary compatibility - * because the methods on 7.0 expect at least one class type, by requiring - * one Class parameter before the varargs (Effective Java 2nd ed., Item 42). - */ - @Deprecated - public final boolean hasDescendantOfAnyType(final Class... types) { - // TODO consider implementing that with a single traversal! - // -> this is done if you use node streams - for (final Class type : types) { - if (hasDescendantOfType(type)) { - return true; - } - } - return false; - } - - @Override - @Deprecated - public Object getUserData() { - return getUserMap().get(LEGACY_USER_DATA); - } - - @Override - @Deprecated - public void setUserData(final Object userData) { - getUserMap().set(LEGACY_USER_DATA, userData); - } - - @Override - public DataMap> getUserMap() { - if (userData == null) { - userData = DataMap.newDataMap(); - } - return userData; - } - - /** - * @deprecated Not all nodes are based on tokens, and this is an implementation detail - */ - @Deprecated - public GenericToken jjtGetFirstToken() { - return firstToken; - } - - /** - * @deprecated This is JJTree-specific and will be removed from this superclass. - */ - @Deprecated - public void jjtSetFirstToken(final GenericToken token) { - this.firstToken = token; - this.beginLine = token.getBeginLine(); - this.beginColumn = token.getBeginColumn(); - } - - /** - * @deprecated Not all nodes are based on tokens, and this is an implementation detail - */ - @Deprecated - public GenericToken jjtGetLastToken() { - return lastToken; - } - - /** - * @deprecated This is JJTree-specific and will be removed from this superclass. - */ - @Deprecated - public void jjtSetLastToken(final GenericToken token) { - this.lastToken = token; - this.endLine = token.getEndLine(); - this.endColumn = token.getEndColumn(); - } - - - /** - * @deprecated This is internal API - */ - @Deprecated - @InternalApi - @Override - public void remove() { - // Detach current node of its parent, if any - final Node parent = getParent(); - if (parent != null) { - parent.removeChildAtIndex(getIndexInParent()); - jjtSetParent(null); - } - - // TODO [autofix]: Notify action for handling text edition - } - - /** - * @deprecated This is internal API - */ - @Deprecated - @InternalApi - @Override - public void removeChildAtIndex(final int childIndex) { - if (0 <= childIndex && childIndex < getNumChildren()) { - // Remove the child at the given index - children = ArrayUtils.remove(children, childIndex); - // Update the remaining & left-shifted children indexes - for (int i = childIndex; i < getNumChildren(); i++) { - getChild(i).jjtSetChildIndex(i); - } - } - } - - @Override - public String toString() { - return getXPathNodeName(); - } - -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/DocumentUtils.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/DocumentUtils.java deleted file mode 100644 index 9460b15600..0000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/DocumentUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.ast; - -import java.util.Iterator; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import net.sourceforge.pmd.lang.ast.xpath.Attribute; -import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator; - -/** - * Remove when we have Java 9 support, and make all methods private on Node interface - */ -/* default */ final class DocumentUtils { - - private DocumentUtils() { - - } - - /* default */ static void appendElement(final Node node, final org.w3c.dom.Node parentNode) { - final DocumentNavigator docNav = new DocumentNavigator(); - Document ownerDocument = parentNode.getOwnerDocument(); - if (ownerDocument == null) { - // If the parentNode is a Document itself, it's ownerDocument is null - ownerDocument = (Document) parentNode; - } - final String elementName = docNav.getElementName(node); - final Element element = ownerDocument.createElement(elementName); - parentNode.appendChild(element); - for (final Iterator iter = docNav.getAttributeAxisIterator(node); iter.hasNext();) { - final Attribute attr = iter.next(); - element.setAttribute(attr.getName(), attr.getStringValue()); - } - for (final Iterator iter = docNav.getChildAxisIterator(node); iter.hasNext();) { - final AbstractNode child = (AbstractNode) iter.next(); - appendElement(child, element); - } - } -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java index 0a983e3852..b4aa8f5429 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java @@ -6,17 +6,13 @@ package net.sourceforge.pmd.lang.ast; import java.util.Iterator; import java.util.List; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.jaxen.BaseXPath; import org.jaxen.JaxenException; -import org.w3c.dom.Document; -import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.NodeStream.DescendantNodeStream; import net.sourceforge.pmd.lang.ast.internal.StreamImpl; import net.sourceforge.pmd.lang.ast.xpath.Attribute; @@ -24,7 +20,6 @@ import net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator; import net.sourceforge.pmd.lang.ast.xpath.internal.ContextualizedNavigator; import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttrLogger; import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttribute; -import net.sourceforge.pmd.lang.dfa.DataFlowNode; import net.sourceforge.pmd.util.DataMap; import net.sourceforge.pmd.util.DataMap.DataKey; @@ -38,8 +33,6 @@ import net.sourceforge.pmd.util.DataMap.DataKey; * {@link #getXPathNodeName()}, {@link #getXPathAttributesIterator()} *
  • Location metadata: eg {@link #getBeginLine()}, {@link #getBeginColumn()} * - * Additionally, the {@linkplain #getUserMap() user data map} is an extensibility - * mechanism with which any client can independently associate values to AST nodes. * *

    Every language implementation must publish a sub-interface of Node * which serves as a supertype for all nodes of that language (e.g. @@ -51,131 +44,9 @@ import net.sourceforge.pmd.util.DataMap.DataKey; * implementations should ensure that every node returned by these methods * are indeed of the same type. Possibly, a type parameter will be added to * the Node interface in 7.0.0 to enforce it at compile-time. - * - *

    A number of methods are deprecated and will be removed in 7.0.0. - * Most of them are implementation details that clutter this API and - * make implementation more difficult. Some methods prefixed with {@code jjt} - * have a more conventional counterpart (e.g. {@link #jjtGetParent()} and - * {@link #getParent()}) that should be preferred. */ public interface Node { - // COMMENT: is it ok to take the opportunity on PMD 7 to rename this API and take out of there the methods - // that are only needed for javaCC implementations? - - - /** - * This method is called after the node has been made the current node. It - * indicates that child nodes can now be added to it. - * - * @deprecated This is JJTree-specific and will be removed from this interface - */ - @Deprecated - default void jjtOpen() { - // do nothing - } - - - /** - * This method is called after all the child nodes have been added. - * - * @deprecated This is JJTree-specific and will be removed from this interface - */ - @Deprecated - default void jjtClose() { - // do nothing - } - - - /** - * Sets the parent of this node. - * - * @param parent The parent - * - * @deprecated This is JJTree-specific and will be removed from this interface - */ - @Deprecated - default void jjtSetParent(Node parent) { - throw new UnsupportedOperationException("JJTree specific"); - } - - - /** - * Returns the parent of this node. - * - * @return The parent of the node - * - * @deprecated Use {@link #getParent()} - */ - @Deprecated - @Nullable - default Node jjtGetParent() { - return getParent(); - } - - - /** - * This method tells the node to add its argument to the node's list of children. - * - * @param child The child to add - * @param index The index to which the child will be added - * - * @deprecated This is JJTree-specific and will be removed from this interface - */ - @Deprecated - default void jjtAddChild(Node child, int index) { - throw new UnsupportedOperationException("JJTree specific"); - } - - - /** - * Sets the index of this node from the perspective of its parent. This - * means: this.getParent().getChild(index) == this. - * - * @param index the child index - * - * @deprecated This is JJTree-specific and will be removed from this interface - */ - @Deprecated - default void jjtSetChildIndex(int index) { - throw new UnsupportedOperationException("JJTree specific"); - } - - - /** - * This method returns a child node. The children are numbered from zero, left to right. - * - * @param index the child index. Must be nonnegative and less than - * {@link #jjtGetNumChildren}. - * - * @deprecated Use {@link #getChild(int)} - */ - @Deprecated - default Node jjtGetChild(int index) { - return getChild(index); - } - - - /** - * Returns the number of children the node has. - * - * @deprecated Use {@link #getNumChildren()} - */ - @Deprecated - default int jjtGetNumChildren() { - return getNumChildren(); - } - - - /** - * @deprecated This is JJTree-specific and will be removed from this interface. - */ - @Deprecated - default int jjtGetId() { - throw new UnsupportedOperationException("JJTree specific"); - } - - /** * Returns a string token, usually filled-in by the parser, which describes some textual characteristic of this * node. This is usually an identifier, but you should check that using the Designer. On most nodes though, this @@ -186,23 +57,13 @@ public interface Node { } - /** - * @deprecated This is internal API, the image should never be set by developers. - */ - @InternalApi - @Deprecated - default void setImage(String image) { - throw new UnsupportedOperationException("setImage"); - } - - /** * Returns true if this node's image is equal to the given string. * * @param image The image to check */ default boolean hasImageEqualTo(String image) { - return getImage() != null && getImage().equals(image); + return Objects.equals(getImage(), image); } @@ -219,25 +80,6 @@ public interface Node { int getEndColumn(); - /** - * @deprecated This is Java-specific and will be removed from this interface - */ - @Deprecated - default DataFlowNode getDataFlowNode() { - throw new UnsupportedOperationException("JJTree specific"); - } - - - /** - * @deprecated This is Java-specific and will be removed from this interface - */ - @Deprecated - default void setDataFlowNode(DataFlowNode dataFlowNode) { - throw new UnsupportedOperationException("JJTree specific"); - } - - - /** * Returns true if this node is considered a boundary by traversal * methods. Traversal methods such as {@link #descendants()} @@ -295,27 +137,6 @@ public interface Node { return this.ancestors(parentType).toList(); } - /** - * Gets the first parent that's an instance of any of the given types. - * - * @param parentTypes Types to look for - * @param Most specific common type of the parameters - * @return The first parent with a matching type. Returns null if there is no such parent - * - * @deprecated This method causes an unchecked warning at call sites. - * PMD 7 will provide a way to do the same thing without the warning. - */ - @Deprecated - default T getFirstParentOfAnyType(Class... parentTypes) { - return ancestors().map(it -> { - for (final Class c : parentTypes) { - if (c.isInstance(it)) { - return c.cast(it); - } - } - return null; - }).first(); - } /** * Traverses the children to find all the instances of type childType or one of its subclasses. @@ -340,20 +161,6 @@ public interface Node { return this.descendants(targetType).toList(); } - /** - * Traverses down the tree to find all the descendant instances of type descendantType. - * - * @param targetType class which you want to find. - * @param results list to store the matching descendants - * @param crossFindBoundaries if false, recursion stops for nodes for which {@link #isFindBoundary()} - * is true - * @deprecated Use {@link #findDescendantsOfType(Class, boolean)} instead, which - * returns a result list. - */ - @Deprecated - default void findDescendantsOfType(Class targetType, List results, boolean crossFindBoundaries) { - this.descendants(targetType).crossFindBoundaries(crossFindBoundaries).forEach(results::add); - } /** * Traverses down the tree to find all the descendant instances of type @@ -437,91 +244,8 @@ public interface Node { } } - /** - * Get a DOM Document which contains Elements and Attributes representative of this Node and it's children. - * Essentially a DOM tree representation of the Node AST, thereby allowing tools which can operate upon DOM to also - * indirectly operate on the AST. - * - * @deprecated Converting a tree to a DOM is not a standard use case. - * The implementation rethrows a {@link ParserConfigurationException} - * as a {@link RuntimeException}, but a caller should handle - * it if he really wants to do this. Another problem is that - * this is available on any node, yet only the root node of - * a tree corresponds really to a document. The conversion - * is easy to implement anyway, and does not have to be part - * of this API. - */ - @Deprecated - default Document getAsDocument() { - try { - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - final DocumentBuilder db = dbf.newDocumentBuilder(); - final Document document = db.newDocument(); - DocumentUtils.appendElement(this, document); - return document; - } catch (final ParserConfigurationException pce) { - throw new RuntimeException(pce); - } - } - - /** - * Get the user data associated with this node. By default there is no data, unless it has been set via {@link - * #setUserData(Object)}. - * - * @return The user data set on this node. - * @deprecated Use {@link #getUserMap()} - */ - @Deprecated - Object getUserData(); - - /** - * Set the user data associated with this node. - *

    - *

    PMD itself will never set user data onto a node. Nor should any Rule - * implementation, as the AST nodes are shared between concurrently executing Rules (i.e. it is not - * thread-safe). - *

    - *

    This API is most useful for external applications looking to leverage - * PMD's robust support for AST structures, in which case application specific annotations on the AST nodes can be - * quite useful. - * - * @param userData The data to set on this node. - * @deprecated Use {@link #getUserMap()} - */ - @Deprecated - void setUserData(Object userData); - - - /** - * Remove the current node from its parent. - * - * @deprecated This is internal API and will be removed from this interface with 7.0.0 - */ - @Deprecated - @InternalApi - default void remove() { - throw new UnsupportedOperationException(); - } - - - /** - * This method tells the node to remove the child node at the given index from the node's list of children, if any; - * if not, no changes are done. - * - * @param childIndex The index of the child to be removed - * - * @deprecated This is internal API and will be removed from this interface with 7.0.0 - */ - @Deprecated - @InternalApi - default void removeChildAtIndex(int childIndex) { - throw new UnsupportedOperationException(); - } - - /** * Returns a data map used to store additional information on this node. - * This replaces the legacy {@link #getUserData()}/{@link #setUserData(Object)}. * * @return The user data map of this node */ @@ -531,8 +255,6 @@ public interface Node { * Returns the parent of this node, or null if this is the {@linkplain RootNode root} * of the tree. * - *

    This method should be preferred to {@link #jjtGetParent()}. - * * @return The parent of this node */ Node getParent(); @@ -565,6 +287,7 @@ public interface Node { */ String getXPathNodeName(); + /** * Returns an iterator enumerating all the attributes that are available from XPath for this node. * @@ -575,6 +298,22 @@ public interface Node { } + /** + * Returns the first child of this node, or null if it doesn't exist. + */ + default @Nullable Node getFirstChild() { + return getNumChildren() > 0 ? getChild(0) : null; + } + + + /** + * Returns the first last of this node, or null if it doesn't exist. + */ + default @Nullable Node getLastChild() { + return getNumChildren() > 0 ? getChild(getNumChildren() - 1) : null; + } + + /** * Returns a node stream containing only this node. * {@link NodeStream#of(Node)} is a null-safe version @@ -584,7 +323,7 @@ public interface Node { * * @see NodeStream#of(Node) */ - default NodeStream asStream() { + default NodeStream asStream() { return StreamImpl.singleton(this); } @@ -609,7 +348,7 @@ public interface Node { * * @see NodeStream#descendants() */ - default DescendantNodeStream descendants() { + default DescendantNodeStream descendants() { return StreamImpl.descendants(this); } @@ -622,7 +361,7 @@ public interface Node { * * @see NodeStream#descendantsOrSelf() */ - default DescendantNodeStream descendantsOrSelf() { + default DescendantNodeStream descendantsOrSelf() { return StreamImpl.descendantsOrSelf(this); } @@ -636,7 +375,7 @@ public interface Node { * * @see NodeStream#ancestors() */ - default NodeStream ancestors() { + default NodeStream ancestors() { return StreamImpl.ancestors(this); } @@ -650,7 +389,7 @@ public interface Node { * * @see NodeStream#ancestorsOrSelf() */ - default NodeStream ancestorsOrSelf() { + default NodeStream ancestorsOrSelf() { return StreamImpl.ancestorsOrSelf(this); } @@ -703,14 +442,18 @@ public interface Node { return StreamImpl.ancestors(this, rClass); } + + /** + * Returns the root of the tree this node is declared in. + */ @NonNull default RootNode getRoot() { Node r = this; - while (r != null && !(r instanceof RootNode)) { + while (r.getParent() != null) { r = r.getParent(); } - if (r == null) { - throw new IllegalStateException("No root node in tree ?"); + if (!(r instanceof RootNode)) { + throw new AssertionError("Root of the tree should implement RootNode"); } return (RootNode) r; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java index af7e73b7d0..7fe8b13654 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java @@ -70,8 +70,8 @@ import net.sourceforge.pmd.lang.ast.internal.StreamImpl; *

  • node.{@link Node#getParentsOfType(Class) getParentsOfType(t)} === node.{@link Node#descendants(Class) ancestors(t)}.{@link #toList()}
  • *
  • node.{@link Node#getNthParent(int) getNthParent(n)} === node.{@link Node#ancestors() ancestors()}.{@link #get(int) get(n - 1)}
  • *
  • node.{@link Node#hasDescendantOfType(Class) hasDescendantOfType(t)} === node.{@link Node#descendants(Class) descendants(t)}.{@link #nonEmpty()}
  • - *
  • node.{@link Node#getFirstParentOfAnyType(Class[]) getFirstParentOfAnyType(c1, c2)} === node.{@link Node#ancestors() ancestors()}.{@link #firstNonNull(Function) firstNonNull}({@link #asInstanceOf(Class, Class[]) asInstanceOf(c1, c2)})
  • - *
  • node.{@link AbstractNode#hasDescendantOfAnyType(Class[]) hasDescendantOfAnyType(c1, c2)} === node.{@link Node#descendants() descendants()}.{@link #map(Function) map}({@link #asInstanceOf(Class, Class[]) asInstanceOf(c1, c2)}).{@link #nonEmpty()}
  • + *
  • node.getFirstParentOfAnyType(c1, c2) === node.{@link Node#ancestors() ancestors()}.{@link #firstNonNull(Function) firstNonNull}({@link #asInstanceOf(Class, Class[]) asInstanceOf(c1, c2)})
  • + *
  • node.hasDescendantOfAnyType(c1, c2) === node.{@link Node#descendants() descendants()}.{@link #map(Function) map}({@link #asInstanceOf(Class, Class[]) asInstanceOf(c1, c2)}).{@link #nonEmpty()}
  • * * The new way to write those is as efficient as the old way. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/RootNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/RootNode.java index f90b65b102..e7be304c5f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/RootNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/RootNode.java @@ -11,10 +11,12 @@ import net.sourceforge.pmd.annotation.Experimental; import net.sourceforge.pmd.annotation.InternalApi; /** - * This interface can be used to tag the root node of various ASTs. + * This interface identifies the root node of an AST. Each language + * implementation must ensure that every AST its parser produces has + * a RootNode as its root, and that there is no other RootNode instance + * in the tree. */ public interface RootNode extends Node { - // that's only a marker interface. /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/AbstractNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/AbstractNode.java new file mode 100644 index 0000000000..d65d4dd231 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/AbstractNode.java @@ -0,0 +1,186 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast.impl; + +import org.apache.commons.lang3.ArrayUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.DataMap; +import net.sourceforge.pmd.util.DataMap.DataKey; + +/** + * Base class for implementations of the Node interface whose children + * are stored in an array. This class provides the basic utilities to + * link children and parent. It's used by most most nodes, but currently + * not the antlr nodes, so downcasting {@link Node} to this class may fail + * and is very bad practice. + * + * @param Self type (eg AbstractJavaNode in the java module), this + * must ultimately implement {@code }, though the java type + * system does not allow us to express that + * @param Public interface for nodes of this language (eg JavaNode + * in the java module). + */ +public abstract class AbstractNode, N extends GenericNode> implements GenericNode { + + private static final Node[] EMPTY_ARRAY = new Node[0]; + + // lazy initialized, many nodes don't need it + private @Nullable DataMap> userData; + + // never null, never contains null elements + private Node[] children = EMPTY_ARRAY; + private B parent; + private int childIndex; + + protected AbstractNode() { + // only for subclassing + } + + @Override + public N getParent() { + return (N) parent; + } + + @Override + public int getIndexInParent() { + return childIndex; + } + + @Override + public N getChild(final int index) { + return (N) children[index]; + } + + @Override + public int getNumChildren() { + return children.length; + } + + protected void setParent(final B parent) { + this.parent = parent; + } + + @SuppressWarnings("unchecked") + private B asSelf(Node n) { + return (B) n; + } + + /** + * Set the child at the given index to the given node. This resizes + * the children array to be able to contain the given index. Implementations + * must take care that this does not leave any "holes" in the array. + * This method throws if there is already a child at the given index. + * + *

    Note that it is more efficient to add children in reverse + * (from right to left), because the array is resized only the + * first time. + * + *

    This method also calls {@link #setParent(AbstractNode)}. + * + * @param child The child to add + * @param index The index to which the child will be added + */ + protected void addChild(final B child, final int index) { + assert index >= 0 : "Invalid index " + index; + assert index >= children.length || children[index] == null : "There is already a child at index " + index; + + if (index >= children.length) { + final Node[] newChildren = new Node[index + 1]; + System.arraycopy(children, 0, newChildren, 0, children.length); + children = newChildren; + } + + setChild(child, index); + } + + /** + * Set the child at the given index. The difference with {@link #addChild(AbstractNode, int) addChild} + * is that the index must exist, while addChild may resizes the array. + */ + protected void setChild(final B child, final int index) { + assert index >= 0 && index < children.length : "Invalid index " + index + " for length " + children.length; + children[index] = child; + child.setChildIndex(index); + child.setParent(asSelf(this)); + } + + /** + * Insert a child at the given index, shifting all the following + * children to the right. + * + * @param child New child + * @param index Index (must be 0 <= index <= getNumChildren()), ie + * you can insert a node beyond the end, because that + * would leave holes in the array + */ + protected void insertChild(final B child, final int index) { + assert index >= 0 && index <= children.length + : "Invalid index for insertion into array of length " + children.length + ": " + index; + + Node[] newChildren = new Node[children.length + 1]; + if (index != 0) { + System.arraycopy(children, 0, newChildren, 0, index); + } + if (index != children.length) { + System.arraycopy(children, index, newChildren, index + 1, children.length - index); + } + newChildren[index] = child; + child.setParent(asSelf(this)); + + for (int i = index; i < newChildren.length; i++) { + asSelf(newChildren[i]).setChildIndex(i); + } + this.children = newChildren; + } + + + protected void remove() { + // Detach current node of its parent, if any + if (parent != null) { + parent.removeChildAtIndex(getIndexInParent()); + setParent(null); + } + + // TODO [autofix]: Notify action for handling text edition + } + + protected void removeChildAtIndex(final int childIndex) { + if (0 <= childIndex && childIndex < getNumChildren()) { + // Remove the child at the given index + children = ArrayUtils.remove(children, childIndex); + // Update the remaining & left-shifted children indexes + for (int i = childIndex; i < getNumChildren(); i++) { + asSelf(getChild(i)).setChildIndex(i); + } + } + } + + /** + * Sets the index of this node from the perspective of its parent. This + * means: this.getParent().getChild(index) == this. + * + * @param index the child index + */ + void setChildIndex(final int index) { + childIndex = index; + } + + @Override + public DataMap> getUserMap() { + if (userData == null) { + userData = DataMap.newDataMap(); + } + return userData; + } + + + @Override + public String toString() { + return getXPathNodeName(); + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeWithTextCoordinates.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeWithTextCoordinates.java new file mode 100644 index 0000000000..0b179878b3 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeWithTextCoordinates.java @@ -0,0 +1,51 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast.impl; + +/** + * Base class for implementations that need fields to store text + * coordinates. + */ +public abstract class AbstractNodeWithTextCoordinates, T extends GenericNode> extends AbstractNode { + + protected int beginLine = -1; + protected int endLine = -1; + protected int beginColumn = -1; + protected int endColumn = -1; + + protected AbstractNodeWithTextCoordinates() { + // only for subclassing + } + + @Override + public int getBeginLine() { + return beginLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } + + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getEndColumn() { + return endColumn; + } + + protected void setCoords(int bline, int bcol, int eline, int ecol) { + assert bline >= 1 && bcol >= 1 && eline >= 1 && ecol >= 1 : "coordinates are 1-based"; + assert bline <= eline && (bline != eline || bcol <= ecol) : "coordinates must be ordered"; + beginLine = bline; + beginColumn = bcol; + endLine = eline; + endColumn = ecol; + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/GenericNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/GenericNode.java new file mode 100644 index 0000000000..9e277b59b0 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/GenericNode.java @@ -0,0 +1,86 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast.impl; + + +import org.checkerframework.checker.nullness.qual.Nullable; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.NodeStream.DescendantNodeStream; +import net.sourceforge.pmd.lang.ast.internal.StreamImpl; + +/** + * Interface that binds the return type of some node methods to a type + * parameter. This enforces that eg all children of such a node are from + * the same hierarchy (eg Java nodes only have Java nodes as parent, or + * as children). + * + *

    Although subinterfaces like JavaNode profit from the added type + * information, the Node interface and its usages in language-independent + * code would suffer from adding a type parameter directly to {@link Node}. + * + *

    Type safety of the unchecked casts here is the responsibility of + * the implementation, it should check that methods like setParent or + * addChild add an instance of {@code }. + * + * @param Self type (eg JavaNode) + */ +@SuppressWarnings("unchecked") +public interface GenericNode> extends Node { + + @Override + N getChild(int index); + + @Override + N getParent(); + + @Override + @Nullable + default N getFirstChild() { + return getNumChildren() > 0 ? getChild(0) : null; + } + + @Override + @Nullable + default N getLastChild() { + return getNumChildren() > 0 ? getChild(getNumChildren() - 1) : null; + } + + @Override + default NodeStream asStream() { + return StreamImpl.singleton((N) this); + } + + @Override + default N getNthParent(int n) { + return (N) Node.super.getNthParent(n); + } + + @Override + default NodeStream children() { + return (NodeStream) Node.super.children(); + } + + @Override + default DescendantNodeStream descendants() { + return (DescendantNodeStream) Node.super.descendants(); + } + + @Override + default DescendantNodeStream descendantsOrSelf() { + return (DescendantNodeStream) Node.super.descendantsOrSelf(); + } + + @Override + default NodeStream ancestorsOrSelf() { + return (NodeStream) Node.super.ancestorsOrSelf(); + } + + @Override + default NodeStream ancestors() { + return (NodeStream) Node.super.ancestors(); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseNode.java index 1dcaf06ab3..5f6e3f52d7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseNode.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.ast.impl.antlr4; import org.antlr.v4.runtime.ParserRuleContext; -import net.sourceforge.pmd.lang.ast.AbstractNode; import net.sourceforge.pmd.util.DataMap; import net.sourceforge.pmd.util.DataMap.DataKey; @@ -65,16 +64,6 @@ public abstract class AntlrBaseNode extends ParserRuleContext implements AntlrNo return stop.getCharPositionInLine(); // This goes from 0 to (n - 1) } - @Override - public Object getUserData() { - return userData.get(AbstractNode.LEGACY_USER_DATA); - } - - @Override - public void setUserData(Object userData) { - this.userData.set(AbstractNode.LEGACY_USER_DATA, userData); - } - @Override public DataMap> getUserMap() { return userData; @@ -95,7 +84,6 @@ public abstract class AntlrBaseNode extends ParserRuleContext implements AntlrNo return getChildCount(); } - // TODO: should we make it abstract due to the comment in AbstractNode ? @Override public String getXPathNodeName() { final String simpleName = getClass().getSimpleName(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseParser.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseParser.java index 10339ddd89..1f92c96862 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseParser.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseParser.java @@ -11,8 +11,8 @@ import org.antlr.v4.runtime.Lexer; import net.sourceforge.pmd.lang.Parser; import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.ParseException; +import net.sourceforge.pmd.lang.ast.RootNode; /** * Generic Antlr parser adapter for all Antlr parsers. @@ -31,7 +31,7 @@ public abstract class AntlrBaseParser imp } @Override - public Node parse(final String fileName, final Reader source) throws ParseException { + public RootNode parse(final String fileName, final Reader source) throws ParseException { try { return getRootNode(getParser(getLexer(source))); } catch (final IOException e) { @@ -39,7 +39,7 @@ public abstract class AntlrBaseParser imp } } - protected abstract AntlrBaseNode getRootNode(T parser); + protected abstract RootNode getRootNode(T parser); protected abstract Lexer getLexer(Reader source) throws IOException; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/PmdAntlrTerminalNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/PmdAntlrTerminalNode.java index c7a8dc5d25..95122c0665 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/PmdAntlrTerminalNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/PmdAntlrTerminalNode.java @@ -8,7 +8,6 @@ import org.antlr.v4.runtime.RuleContext; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.TerminalNodeImpl; -import net.sourceforge.pmd.lang.ast.AbstractNode; import net.sourceforge.pmd.util.DataMap; import net.sourceforge.pmd.util.DataMap.DataKey; @@ -74,16 +73,6 @@ public class PmdAntlrTerminalNode extends TerminalNodeImpl implements AntlrNode return getBeginColumn() + getSymbol().getText().length(); } - @Override - public Object getUserData() { - return userData.get(AbstractNode.LEGACY_USER_DATA); - } - - @Override - public void setUserData(Object data) { - this.userData.set(AbstractNode.LEGACY_USER_DATA, data); - } - @Override public DataMap> getUserMap() { return userData; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/AbstractJjtreeNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/AbstractJjtreeNode.java index 1b1e54baf6..0e358f7cea 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/AbstractJjtreeNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/AbstractJjtreeNode.java @@ -5,11 +5,8 @@ package net.sourceforge.pmd.lang.ast.impl.javacc; import net.sourceforge.pmd.annotation.Experimental; -import net.sourceforge.pmd.lang.ast.AbstractNode; -import net.sourceforge.pmd.lang.ast.GenericToken; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.NodeStream; -import net.sourceforge.pmd.lang.ast.TextAvailableNode; +import net.sourceforge.pmd.lang.ast.impl.AbstractNode; /** * Base class for node produced by JJTree. JJTree specific functionality @@ -20,59 +17,117 @@ import net.sourceforge.pmd.lang.ast.TextAvailableNode; * unforeseeable ways. Don't use it directly, use the node interfaces. */ @Experimental -public abstract class AbstractJjtreeNode extends AbstractNode implements TextAvailableNode { +public abstract class AbstractJjtreeNode, N extends JjtreeNode> extends AbstractNode implements JjtreeNode { + protected final int id; + private JavaccToken firstToken; + private JavaccToken lastToken; - public AbstractJjtreeNode(int id) { - super(id); + private String image; + + /** + * The id is an index in the constant names array generated by jjtree, + * it must be set to some value that depends on the node type, not some + * arbitrary "1" or "2", and not necessarily a unique value. + */ + protected AbstractJjtreeNode(int id) { + super(); + this.id = id; + } + + @Override + public String getImage() { + return image; + } + + protected void setImage(String image) { + this.image = image; } @Override public CharSequence getText() { - String fullText = jjtGetFirstToken().document.getFullText(); + String fullText = getFirstToken().document.getFullText(); return fullText.substring(getStartOffset(), getEndOffset()); } - @Override - public JavaccToken jjtGetFirstToken() { - return (JavaccToken) super.jjtGetFirstToken(); + /** + * This method is called after the node has been made the current node. It + * indicates that child nodes can now be added to it. + */ + protected void jjtOpen() { + // to be overridden + } + + /** + * This method is called after all the child nodes have been added. + */ + protected void jjtClose() { + // to be overridden + } + + @Override // override to make it protected + protected void addChild(B child, int index) { + super.addChild(child, index); } @Override - public JavaccToken jjtGetLastToken() { - return (JavaccToken) super.jjtGetLastToken(); + protected void insertChild(B child, int index) { + super.insertChild(child, index); + fitTokensToChildren(index); + } + + /** + * Ensures that the first (resp. last) token of this node is before + * (resp. after) the first (resp. last) token of the child at the + * given index. The index + */ + protected void fitTokensToChildren(int index) { + if (index == 0) { + enlargeLeft((B) getChild(index)); + } + if (index == getNumChildren()) { + enlargeRight((B) getChild(index)); + } + } + + private void enlargeLeft(B child) { + JavaccToken thisFst = this.getFirstToken(); + JavaccToken childFst = child.getFirstToken(); + + if (childFst.compareTo(thisFst) < 0) { + this.setFirstToken(childFst); + } + } + + private void enlargeRight(B child) { + JavaccToken thisLast = this.getLastToken(); + JavaccToken childLast = child.getLastToken(); + + if (childLast.compareTo(thisLast) > 0) { + this.setLastToken(childLast); + } + } + + @Override + public JavaccToken getFirstToken() { + return firstToken; + } + + @Override + public JavaccToken getLastToken() { + return lastToken; } // the super methods query line & column, which we want to avoid - @Override - public void jjtSetLastToken(GenericToken token) { + protected void setLastToken(JavaccToken token) { this.lastToken = token; } - @Override - public void jjtSetFirstToken(GenericToken token) { + protected void setFirstToken(JavaccToken token) { this.firstToken = token; } - @Override - @SuppressWarnings("unchecked") - public N getChild(int index) { - return (N) super.getChild(index); - } - - @Override - @SuppressWarnings("unchecked") - public N getParent() { - return (N) super.getParent(); - } - - @Override - @SuppressWarnings("unchecked") - public NodeStream children() { - return (NodeStream) super.children(); - } - @Override public int getBeginLine() { return firstToken.getBeginLine(); @@ -102,11 +157,10 @@ public abstract class AbstractJjtreeNode extends AbstractNode im } private int getStartOffset() { - return this.jjtGetFirstToken().getStartInDocument(); + return this.getFirstToken().getStartInDocument(); } - private int getEndOffset() { - return this.jjtGetLastToken().getEndInDocument(); + return this.getLastToken().getEndInDocument(); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/JjtreeBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/JjtreeBuilder.java index 9b6e3fc502..7d14321302 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/JjtreeBuilder.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/JjtreeBuilder.java @@ -10,9 +10,9 @@ import java.util.List; /** * Shared implementation of the tree builder generated by JJTree. * - * @param Type of node this takes + * @param Internal base class for nodes */ -public final class JjtreeBuilder> { +public final class JjtreeBuilder> { private final List nodes = new ArrayList<>(); private final List marks = new ArrayList<>(); @@ -141,7 +141,7 @@ public final class JjtreeBuilder> { numPendingInjection = 0; } - n.jjtSetFirstToken(firstToken); + n.setFirstToken(firstToken); n.jjtOpen(); } @@ -160,13 +160,13 @@ public final class JjtreeBuilder> { int i = num; while (i-- > 0) { child = popNode(); - n.jjtAddChild(child, i); + n.addChild(child, i); } if (child != null && num > a) { // this node has more children that what was in its node scope // (ie first token is wrong) - n.jjtSetFirstToken(child.jjtGetFirstToken()); + n.setFirstToken(child.getFirstToken()); } closeImpl(n, lastToken); @@ -189,7 +189,7 @@ public final class JjtreeBuilder> { int a = nodeArity(); mk = marks.remove(marks.size() - 1); while (a-- > 0) { - n.jjtAddChild(popNode(), a); + n.addChild(popNode(), a); } closeImpl(n, lastToken); } else { @@ -198,16 +198,17 @@ public final class JjtreeBuilder> { } } + private void closeImpl(N n, JavaccToken lastToken) { - if (lastToken.getNext() == n.jjtGetFirstToken()) { + if (lastToken.getNext() == n.getFirstToken()) { // this means, that the node has zero length. // create an implicit token to represent this case. JavaccToken implicit = JavaccToken.implicitBefore(lastToken.getNext()); - n.jjtSetFirstToken(implicit); - n.jjtSetLastToken(implicit); + n.setFirstToken(implicit); + n.setLastToken(implicit); } else { - n.jjtSetLastToken(lastToken); + n.setLastToken(lastToken); } // note that the last token has been set before jjtClose n.jjtClose(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/JjtreeNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/JjtreeNode.java new file mode 100644 index 0000000000..461c24584d --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/JjtreeNode.java @@ -0,0 +1,24 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast.impl.javacc; + +import net.sourceforge.pmd.lang.ast.TextAvailableNode; +import net.sourceforge.pmd.lang.ast.impl.GenericNode; + +/** + * Base interface for nodes that are produced by a JJTree parser. Our + * JJTree implementation gives {@link TextAvailableNode} for free, access + * to tokens is also guaranteed. + * + * @param Self type + */ +public interface JjtreeNode> extends GenericNode, TextAvailableNode { + + + JavaccToken getFirstToken(); + + JavaccToken getLastToken(); + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/internal/IteratorBasedNStream.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/internal/IteratorBasedNStream.java index b8e7c20339..afbf7d3a35 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/internal/IteratorBasedNStream.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/internal/IteratorBasedNStream.java @@ -91,7 +91,7 @@ abstract class IteratorBasedNStream implements NodeStream { @NonNull - protected DescendantNodeStream flatMapDescendants(Function> mapper) { + protected DescendantNodeStream flatMapDescendants(Function> mapper) { return new DescendantMapping<>(this, mapper); } @@ -254,18 +254,18 @@ abstract class IteratorBasedNStream implements NodeStream { private static class DescendantMapping extends IteratorBasedNStream implements DescendantNodeStream { - private final Function> fun; + private final Function> fun; private final TreeWalker walker; private final IteratorBasedNStream upstream; - private DescendantMapping(IteratorBasedNStream upstream, Function> fun, TreeWalker walker) { + private DescendantMapping(IteratorBasedNStream upstream, Function> fun, TreeWalker walker) { this.fun = fun; this.walker = walker; this.upstream = upstream; } - DescendantMapping(IteratorBasedNStream upstream, Function> fun) { + DescendantMapping(IteratorBasedNStream upstream, Function> fun) { this(upstream, fun, TreeWalker.DEFAULT); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/internal/StreamImpl.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/internal/StreamImpl.java index 62addf9e67..535653f401 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/internal/StreamImpl.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/internal/StreamImpl.java @@ -181,7 +181,7 @@ public final class StreamImpl { } @Override - protected @NonNull DescendantNodeStream flatMapDescendants(Function> mapper) { + protected @NonNull DescendantNodeStream flatMapDescendants(Function> mapper) { return StreamImpl.empty(); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIterator.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIterator.java index 79fa9ff888..b659590147 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIterator.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIterator.java @@ -17,8 +17,9 @@ import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.ast.AbstractNode; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.impl.AbstractNode; +import net.sourceforge.pmd.lang.ast.impl.AbstractNodeWithTextCoordinates; import net.sourceforge.pmd.lang.ast.xpath.NoAttribute.NoAttrScope; @@ -110,7 +111,7 @@ public class AttributeAxisIterator implements Iterator { Class declaration = method.getDeclaringClass(); if (method.isAnnotationPresent(NoAttribute.class)) { return true; - } else if (declaration == Node.class || declaration == AbstractNode.class) { + } else if (declaration == Node.class || declaration == AbstractNode.class || declaration == AbstractNodeWithTextCoordinates.class) { // attributes from Node and AbstractNode are never suppressed // we don't know what might go wrong if we do suppress them return false; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/NoAttribute.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/NoAttribute.java index 2d63dc60ab..a359b844f0 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/NoAttribute.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/NoAttribute.java @@ -9,8 +9,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import net.sourceforge.pmd.lang.ast.AbstractNode; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.impl.AbstractNode; /** * Filters out some methods from the XPath attributes of a node. diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/AbstractDataFlowNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/AbstractDataFlowNode.java index d5ae23be60..da66a63fc1 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/AbstractDataFlowNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/AbstractDataFlowNode.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.Set; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.DataMap; +import net.sourceforge.pmd.util.DataMap.SimpleDataKey; /** * Each data flow contains a set of DataFlowNodes. @@ -18,6 +20,8 @@ import net.sourceforge.pmd.lang.ast.Node; */ public abstract class AbstractDataFlowNode implements DataFlowNode { + static final SimpleDataKey DATAFLOW_KEY = DataMap.simpleDataKey("dataflow.cache"); + protected Node node; protected List parents = new ArrayList<>(); @@ -40,7 +44,7 @@ public abstract class AbstractDataFlowNode implements DataFlowNode { this(dataFlow); this.node = node; - node.setDataFlowNode(this); + node.getUserMap().set(DATAFLOW_KEY, this); this.line = node.getBeginLine(); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/DataFlowNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/DataFlowNode.java index 788edff78d..c0c3219187 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/DataFlowNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/DataFlowNode.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.lang.dfa; +import static net.sourceforge.pmd.lang.dfa.AbstractDataFlowNode.DATAFLOW_KEY; + import java.util.List; import net.sourceforge.pmd.lang.ast.Node; @@ -35,4 +37,13 @@ public interface DataFlowNode { void reverseParentPathsTo(DataFlowNode destination); + + static DataFlowNode get(Node node) { + DataFlowNode df = node.getUserMap().get(DATAFLOW_KEY); + if (df == null && node.getParent() != null) { + return get(node.getParent()); + } else { + return df; + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java index 45ea95de6a..47313a12f7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java @@ -153,7 +153,7 @@ public class RuleBuilder { if (minimumVersion != null) { LanguageVersion minimumLanguageVersion = rule.getLanguage().getVersion(minimumVersion); if (minimumLanguageVersion == null) { - throwUnknownLanguageVersionException("minimum", minimumVersion); + throwUnknownLanguageVersionException("minimum", minimumVersion, rule.getLanguage()); } else { rule.setMinimumLanguageVersion(minimumLanguageVersion); } @@ -162,7 +162,7 @@ public class RuleBuilder { if (maximumVersion != null) { LanguageVersion maximumLanguageVersion = rule.getLanguage().getVersion(maximumVersion); if (maximumLanguageVersion == null) { - throwUnknownLanguageVersionException("maximum", maximumVersion); + throwUnknownLanguageVersionException("maximum", maximumVersion, rule.getLanguage()); } else { rule.setMaximumLanguageVersion(maximumLanguageVersion); } @@ -171,12 +171,12 @@ public class RuleBuilder { checkLanguageVersionsAreOrdered(rule); } - private void throwUnknownLanguageVersionException(String minOrMax, String unknownVersion) { - throw new IllegalArgumentException("Unknown " + minOrMax + " language version '" + unknownVersion - + "' for language '" + language.getTerseName() - + "' for rule " + name - + "; supported language versions are: " - + language.getVersions().stream().map(LanguageVersion::getVersion).collect(Collectors.joining(", "))); + private void throwUnknownLanguageVersionException(String minOrMax, String unknownVersion, Language lang) { + throw new IllegalArgumentException("Unknown " + minOrMax + " Language Version '" + unknownVersion + + "' for Language '" + lang.getTerseName() + + "' for Rule " + name + + "; supported Language Versions are: " + + lang.getVersions().stream().map(LanguageVersion::getVersion).collect(Collectors.joining(", "))); } public Rule build() throws ClassNotFoundException, IllegalAccessException, InstantiationException { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/DataMap.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/DataMap.java index 05d4bcd2c5..978fba72ff 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/DataMap.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/DataMap.java @@ -17,7 +17,7 @@ import java.util.Map; */ public final class DataMap { - private final Map, Object> map = new IdentityHashMap<>(); + private Map, Object> map; private DataMap() { @@ -34,7 +34,7 @@ public final class DataMap { */ @SuppressWarnings("unchecked") public T set(DataKey key, T data) { - return (T) map.put(key, data); + return (T) getMap().put(key, data); } /** @@ -47,7 +47,20 @@ public final class DataMap { */ @SuppressWarnings("unchecked") public T get(DataKey key) { - return (T) map.get(key); + return map == null ? null : (T) map.get(key); + } + + private Map, Object> getMap() { + // the map is lazily created, it's only needed if set() is called + // at least once, but get() might be called many more times, as + // sometimes you cache a key sparsely on some nodes, and default + // to the first parent for which the key is set. The default expected + // max size is also 21, which is *way* bigger than what data maps + // typically contain (1/2 keys) + if (map == null) { + map = new IdentityHashMap<>(1); + } + return map; } /** @@ -58,7 +71,7 @@ public final class DataMap { * @return True if some value is set */ public boolean isSet(DataKey key) { - return map.containsKey(key); + return map != null && map.containsKey(key); } public static DataMap newDataMap() { diff --git a/pmd-core/src/main/resources/rulesets/releases/6240.xml b/pmd-core/src/main/resources/rulesets/releases/6240.xml new file mode 100644 index 0000000000..a7fe86d521 --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/6240.xml @@ -0,0 +1,13 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.24.0 + + + + + diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/AbstractRuleTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/AbstractRuleTest.java index 847ac33fdf..69850b8c33 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/AbstractRuleTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/AbstractRuleTest.java @@ -76,9 +76,8 @@ public class AbstractRuleTest { r.setRuleSetName("foo"); RuleContext ctx = new RuleContext(); ctx.setSourceCodeFile(new File("filename")); - DummyNode s = new DummyNode(1); - s.testingOnlySetBeginColumn(5); - s.testingOnlySetBeginLine(5); + DummyNode s = new DummyNode(); + s.setCoords(5, 5, 5, 10); RuleViolation rv = new ParametricRuleViolation(r, ctx, s, r.getMessage()); assertEquals("Line number mismatch!", 5, rv.getBeginLine()); assertEquals("Filename mismatch!", "filename", rv.getFilename()); @@ -92,9 +91,8 @@ public class AbstractRuleTest { MyRule r = new MyRule(); RuleContext ctx = new RuleContext(); ctx.setSourceCodeFile(new File("filename")); - DummyNode s = new DummyNode(1); - s.testingOnlySetBeginColumn(5); - s.testingOnlySetBeginLine(5); + DummyNode s = new DummyNode(); + s.setCoords(5, 5, 5, 10); RuleViolation rv = new ParametricRuleViolation<>(r, ctx, s, "specificdescription"); assertEquals("Line number mismatch!", 5, rv.getBeginLine()); assertEquals("Filename mismatch!", "filename", rv.getFilename()); @@ -112,7 +110,7 @@ public class AbstractRuleTest { ctx.setReport(new Report()); ctx.setSourceCodeFile(new File("filename")); DummyNode s = new DummyRoot(); - s.setCoords(5, 1, 6, 0); + s.setCoords(5, 1, 6, 1); s.setImage("TestImage"); r.addViolation(ctx, s); RuleViolation rv = ctx.getReport().getViolationTree().iterator().next(); @@ -127,7 +125,7 @@ public class AbstractRuleTest { m.put(5, ""); ctx.setSourceCodeFile(new File("filename")); DummyRoot n = new DummyRoot(m); - n.setCoords(5, 1, 6, 0); + n.setCoords(5, 1, 6, 1); DefaultRuleViolationFactory.defaultInstance().addViolation(ctx, r, n, "specificdescription", new Object[0]); assertTrue(ctx.getReport().isEmpty()); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/ReportTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/ReportTest.java index 473d5375b1..b6ac200e8f 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/ReportTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/ReportTest.java @@ -96,8 +96,8 @@ public class ReportTest implements ThreadSafeReportListener { r.addRuleViolation(new ParametricRuleViolation<>(mr, ctx, s2, mr.getMessage())); Map summary = r.getSummary(); assertEquals(summary.keySet().size(), 2); - assertTrue(summary.values().contains(Integer.valueOf(1))); - assertTrue(summary.values().contains(Integer.valueOf(2))); + assertTrue(summary.containsValue(1)); + assertTrue(summary.containsValue(2)); } @Test @@ -128,21 +128,18 @@ public class ReportTest implements ThreadSafeReportListener { } private static Node getNode(int line, int column) { - DummyNode s = new DummyNode(2); - DummyNode parent = new DummyNode(1); - parent.testingOnlySetBeginLine(line); - parent.testingOnlySetBeginColumn(column); - s.jjtSetParent(parent); - s.testingOnlySetBeginLine(line); - s.testingOnlySetBeginColumn(column); + DummyNode s = new DummyNode(); + DummyNode parent = new DummyNode(); + parent.setCoords(line, column, line, column + 1); + parent.addChild(s, 0); + s.setCoords(line, column, line, column + 1); return s; } private static Node getNode(int line, int column, boolean nextLine) { DummyNode s = (DummyNode) getNode(line, column); if (nextLine) { - s.testingOnlySetBeginLine(line + 1); - s.testingOnlySetBeginColumn(column + 4); + s.setCoords(line + 1, column + 4, line + 4, 1); } return s; } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java index 61726f393b..def2261c04 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java @@ -600,6 +600,23 @@ public class RuleSetFactoryTest { loadFirstRule(INCORRECT_MINIMUM_LANGUAGE_VERSION); } + @Test(expected = IllegalArgumentException.class) + public void testIncorrectMinimumLanugageVersionWithLanguageSetInJava() throws RuleSetNotFoundException { + loadFirstRule("\n" + + "\n" + + " TODO\n" + + "\n" + + " \n" + + " TODO\n" + + " 2\n" + + " \n" + + "\n" + + ""); + } + @Test public void testMaximumLanguageVersion() throws RuleSetNotFoundException { Rule r = loadFirstRule(MAXIMUM_LANGUAGE_VERSION); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationComparatorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationComparatorTest.java index e0643c0e01..45d3571c5c 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationComparatorTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationComparatorTest.java @@ -33,23 +33,23 @@ public class RuleViolationComparatorTest { int index = 0; // Different begin line - expectedOrder[index++] = createJavaRuleViolation(rule1, "file1", 10, "desc1", 0, 20, 80); - expectedOrder[index++] = createJavaRuleViolation(rule1, "file1", 20, "desc1", 0, 20, 80); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file1", 10, "desc1", 1, 20, 80); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file1", 20, "desc1", 1, 20, 80); // Different description - expectedOrder[index++] = createJavaRuleViolation(rule1, "file2", 10, "desc1", 0, 20, 80); - expectedOrder[index++] = createJavaRuleViolation(rule1, "file2", 10, "desc2", 0, 20, 80); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file2", 10, "desc1", 1, 20, 80); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file2", 10, "desc2", 1, 20, 80); // Different begin column - expectedOrder[index++] = createJavaRuleViolation(rule1, "file3", 10, "desc1", 0, 20, 80); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file3", 10, "desc1", 1, 20, 80); expectedOrder[index++] = createJavaRuleViolation(rule1, "file3", 10, "desc1", 10, 20, 80); // Different end line - expectedOrder[index++] = createJavaRuleViolation(rule1, "file4", 10, "desc1", 0, 20, 80); - expectedOrder[index++] = createJavaRuleViolation(rule1, "file4", 10, "desc1", 0, 30, 80); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file4", 10, "desc1", 1, 20, 80); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file4", 10, "desc1", 1, 30, 80); // Different end column - expectedOrder[index++] = createJavaRuleViolation(rule1, "file5", 10, "desc1", 0, 20, 80); - expectedOrder[index++] = createJavaRuleViolation(rule1, "file5", 10, "desc1", 0, 20, 90); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file5", 10, "desc1", 1, 20, 80); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file5", 10, "desc1", 1, 20, 90); // Different rule name - expectedOrder[index++] = createJavaRuleViolation(rule1, "file6", 10, "desc1", 0, 20, 80); - expectedOrder[index++] = createJavaRuleViolation(rule2, "file6", 10, "desc1", 0, 20, 80); + expectedOrder[index++] = createJavaRuleViolation(rule1, "file6", 10, "desc1", 1, 20, 80); + expectedOrder[index++] = createJavaRuleViolation(rule2, "file6", 10, "desc1", 1, 20, 80); // Randomize List ruleViolations = new ArrayList<>(Arrays.asList(expectedOrder)); @@ -73,12 +73,8 @@ public class RuleViolationComparatorTest { int beginColumn, int endLine, int endColumn) { RuleContext ruleContext = new RuleContext(); ruleContext.setSourceCodeFile(new File(fileName)); - DummyNode simpleNode = new DummyNode(1); - simpleNode.testingOnlySetBeginLine(beginLine); - simpleNode.testingOnlySetBeginColumn(beginColumn); - simpleNode.testingOnlySetEndLine(endLine); - simpleNode.testingOnlySetEndColumn(endColumn); - RuleViolation ruleViolation = new ParametricRuleViolation(rule, ruleContext, simpleNode, description); - return ruleViolation; + DummyNode simpleNode = new DummyNode(); + simpleNode.setCoords(beginLine, beginColumn, endLine, endColumn); + return new ParametricRuleViolation(rule, ruleContext, simpleNode, description); } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationTest.java index 9fd8554203..d9717e3e4c 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleViolationTest.java @@ -26,9 +26,8 @@ public class RuleViolationTest { Rule rule = new MockRule("name", "desc", "msg", "rulesetname"); RuleContext ctx = new RuleContext(); ctx.setSourceCodeFile(new File("filename")); - DummyNode s = new DummyNode(1); - s.testingOnlySetBeginLine(2); - s.testingOnlySetBeginColumn(1); + DummyNode s = new DummyNode(); + s.setCoords(2, 1, 2, 3); RuleViolation r = new ParametricRuleViolation(rule, ctx, s, rule.getMessage()); assertEquals("object mismatch", rule, r.getRule()); assertEquals("line number is wrong", 2, r.getBeginLine()); @@ -40,9 +39,8 @@ public class RuleViolationTest { Rule rule = new MockRule("name", "desc", "msg", "rulesetname"); RuleContext ctx = new RuleContext(); ctx.setSourceCodeFile(new File("filename")); - DummyNode s = new DummyNode(1); - s.testingOnlySetBeginLine(2); - s.testingOnlySetBeginColumn(1); + DummyNode s = new DummyNode(); + s.setCoords(2, 1, 2, 3); RuleViolation r = new ParametricRuleViolation(rule, ctx, s, "description"); assertEquals("object mismatch", rule, r.getRule()); assertEquals("line number is wrong", 2, r.getBeginLine()); @@ -56,14 +54,12 @@ public class RuleViolationTest { RuleViolationComparator comp = RuleViolationComparator.INSTANCE; RuleContext ctx = new RuleContext(); ctx.setSourceCodeFile(new File("filename1")); - DummyNode s = new DummyNode(1); - s.testingOnlySetBeginLine(10); - s.testingOnlySetBeginColumn(1); + DummyNode s = new DummyNode(); + s.setCoords(10, 1, 11, 3); RuleViolation r1 = new ParametricRuleViolation(rule, ctx, s, "description"); ctx.setSourceCodeFile(new File("filename2")); - DummyNode s1 = new DummyNode(1); - s1.testingOnlySetBeginLine(10); - s1.testingOnlySetBeginColumn(1); + DummyNode s1 = new DummyNode(); + s1.setCoords(10, 1, 11, 3); RuleViolation r2 = new ParametricRuleViolation(rule, ctx, s1, "description"); assertEquals(-1, comp.compare(r1, r2)); assertEquals(1, comp.compare(r2, r1)); @@ -75,12 +71,10 @@ public class RuleViolationTest { RuleViolationComparator comp = RuleViolationComparator.INSTANCE; RuleContext ctx = new RuleContext(); ctx.setSourceCodeFile(new File("filename")); - DummyNode s = new DummyNode(1); - s.testingOnlySetBeginLine(10); - s.testingOnlySetBeginColumn(1); - DummyNode s1 = new DummyNode(1); - s1.testingOnlySetBeginLine(20); - s1.testingOnlySetBeginColumn(1); + DummyNode s = new DummyNode(); + s.setCoords(10, 1, 15, 10); + DummyNode s1 = new DummyNode(); + s1.setCoords(20, 1, 25, 10); RuleViolation r1 = new ParametricRuleViolation(rule, ctx, s, "description"); RuleViolation r2 = new ParametricRuleViolation(rule, ctx, s1, "description"); assertTrue(comp.compare(r1, r2) < 0); @@ -94,12 +88,10 @@ public class RuleViolationTest { RuleViolationComparator comp = RuleViolationComparator.INSTANCE; RuleContext ctx = new RuleContext(); ctx.setSourceCodeFile(new File("filename")); - DummyNode s = new DummyNode(1); - s.testingOnlySetBeginLine(10); - s.testingOnlySetBeginColumn(1); - DummyNode s1 = new DummyNode(1); - s1.testingOnlySetBeginLine(10); - s1.testingOnlySetBeginColumn(1); + DummyNode s = new DummyNode(); + s.setCoords(10, 1, 15, 10); + DummyNode s1 = new DummyNode(); + s.setCoords(10, 1, 15, 10); RuleViolation r1 = new ParametricRuleViolation(rule, ctx, s, "description"); RuleViolation r2 = new ParametricRuleViolation(rule, ctx, s1, "description"); assertEquals(1, comp.compare(r1, r2)); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/AttributeAxisIteratorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/AttributeAxisIteratorTest.java index 311963f2b5..a8b5939a26 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/AttributeAxisIteratorTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/AttributeAxisIteratorTest.java @@ -13,9 +13,7 @@ public class AttributeAxisIteratorTest { @Test(expected = UnsupportedOperationException.class) public void testRemove() { - DummyNode n = new DummyNode(0); - n.testingOnlySetBeginColumn(1); - n.testingOnlySetBeginLine(1); + DummyNode n = new DummyNode(); AttributeAxisIterator iter = new AttributeAxisIterator(n); iter.remove(); } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/AttributeTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/AttributeTest.java index 7d5b28091d..eec54c965d 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/AttributeTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/AttributeTest.java @@ -17,13 +17,14 @@ public class AttributeTest { @Test public void testConstructor() { - DummyNode p = new DummyNode(1); - p.testingOnlySetBeginLine(5); + DummyNode p = new DummyNode(); + p.setCoords(5, 5, 5, 10); + Method[] methods = p.getClass().getMethods(); Method m = null; - for (int i = 0; i < methods.length; i++) { - if (methods[i].getName().equals("getBeginLine")) { - m = methods[i]; + for (Method method : methods) { + if (method.getName().equals("getBeginLine")) { + m = method; break; } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/MatchesFunctionTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/MatchesFunctionTest.java index 5bbedef6df..c5be1ce2c5 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/MatchesFunctionTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/MatchesFunctionTest.java @@ -13,17 +13,18 @@ import org.jaxen.Context; import org.jaxen.FunctionCallException; import org.junit.Test; -import net.sourceforge.pmd.lang.ast.AbstractNode; +import net.sourceforge.pmd.lang.ast.impl.AbstractNodeWithTextCoordinates; import net.sourceforge.pmd.lang.ast.xpath.Attribute; import net.sourceforge.pmd.lang.xpath.MatchesFunction; public class MatchesFunctionTest { - public static class MyNode extends AbstractNode { + public static class MyNode extends AbstractNodeWithTextCoordinates { + private String className; public MyNode() { - super(1); + super(); } @Override diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java index bbc6b8522d..f700c527d9 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java @@ -13,7 +13,6 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.lang.ast.DummyAstStages; -import net.sourceforge.pmd.lang.ast.DummyNode; import net.sourceforge.pmd.lang.ast.DummyRoot; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.ParseException; @@ -35,15 +34,15 @@ public class DummyLanguageModule extends BaseLanguageModule { public DummyLanguageModule() { super(NAME, null, TERSE_NAME, DummyRuleChainVisitor.class, "dummy"); - addVersion("1.0", new Handler(), false); - addVersion("1.1", new Handler(), false); - addVersion("1.2", new Handler(), false); - addVersion("1.3", new Handler(), false); - addVersion("1.4", new Handler(), false); - addVersions(new Handler(), false, "1.5", "5"); - addVersions(new Handler(), false, "1.6", "6"); - addVersions(new Handler(), true, "1.7", "7"); - addVersions(new Handler(), false, "1.8", "8"); + addVersion("1.0", new Handler()); + addVersion("1.1", new Handler()); + addVersion("1.2", new Handler()); + addVersion("1.3", new Handler()); + addVersion("1.4", new Handler()); + addVersion("1.5", new Handler(), "5"); + addVersion("1.6", new Handler(), "6"); + addDefaultVersion("1.7", new Handler(), "7"); + addVersion("1.8", new Handler(), "8"); } public static class DummyRuleChainVisitor extends AbstractRuleChainVisitor { @@ -101,8 +100,8 @@ public class DummyLanguageModule extends BaseLanguageModule { public Parser getParser(ParserOptions parserOptions) { return new AbstractParser(parserOptions) { @Override - public Node parse(String fileName, Reader source) throws ParseException { - DummyNode node = new DummyRoot(); + public DummyRoot parse(String fileName, Reader source) throws ParseException { + DummyRoot node = new DummyRoot(); node.setCoords(1, 1, 2, 10); node.setImage("Foo"); return node; diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/BoundaryTraversalTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/BoundaryTraversalTest.java index 4a9c24184a..b3d394903f 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/BoundaryTraversalTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/BoundaryTraversalTest.java @@ -14,29 +14,23 @@ import org.junit.Before; import org.junit.Test; /** - * Unit test for {@link AbstractNode} tree traversal methods + * Unit test for {@link Node} tree traversal methods */ public class BoundaryTraversalTest { - private int id; - private Node rootNode; - private int nextId() { - return id++; + private DummyNode rootNode; + + private DummyNode newDummyNode(boolean boundary) { + return new DummyNode(boundary); } - private Node newDummyNode(boolean boundary) { - return new DummyNode(nextId(), boundary); - } - - private Node addChild(final Node parent, final Node child) { - parent.jjtAddChild(child, parent.getNumChildren()); // Append child at the end - child.jjtSetParent(parent); + private DummyNode addChild(final DummyNode parent, final DummyNode child) { + parent.addChild(child, parent.getNumChildren()); // Append child at the end return parent; } @Before public void setUpSampleNodeTree() { - id = 0; rootNode = newDummyNode(false); } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java index 2fa20bcbfb..d87c27be11 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java @@ -7,47 +7,52 @@ package net.sourceforge.pmd.lang.ast; import java.util.HashMap; import java.util.Map; -public class DummyNode extends AbstractNode { +import net.sourceforge.pmd.lang.ast.impl.AbstractNodeWithTextCoordinates; + +public class DummyNode extends AbstractNodeWithTextCoordinates { + private final boolean findBoundary; private final String xpathName; private final Map userData = new HashMap<>(); - - public DummyNode(int id) { - this(id, false); - } + private String image; public DummyNode() { - this(0, false); + this(false); } - public DummyNode(int id, boolean findBoundary) { - this(id, findBoundary, "dummyNode"); + public DummyNode(boolean findBoundary) { + this(findBoundary, "dummyNode"); } - public DummyNode(int id, boolean findBoundary, String xpathName) { - super(id); + public DummyNode(boolean findBoundary, String xpathName) { this.findBoundary = findBoundary; this.xpathName = xpathName; } - public void setBeginColumn(int i) { - beginColumn = i; - } - - public void setBeginLine(int i) { - beginLine = i; + public void publicSetChildren(DummyNode... children) { + assert getNumChildren() == 0; + for (int i = children.length - 1; i >= 0; i--) { + addChild(children[i], i); + } } + @Override public void setCoords(int bline, int bcol, int eline, int ecol) { - beginLine = bline; - beginColumn = bcol; - endLine = eline; - endColumn = ecol; + super.setCoords(bline, bcol, eline, ecol); + } + + public void setImage(String image) { + this.image = image; + } + + @Override + public String getImage() { + return image; } @Override public String toString() { - return xpathName; + return getImage(); } @Override @@ -60,18 +65,22 @@ public class DummyNode extends AbstractNode { return findBoundary; } - @Override public Map getUserData() { return userData; } + @Override + public void addChild(DummyNode child, int index) { + super.addChild(child, index); + } + + @Override + public DummyNode getChild(int index) { + return super.getChild(index); + } public static class DummyNodeTypeB extends DummyNode { - @Override - public String toString() { - return getImage(); - } } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java index 0e5dd20606..81460ce5a3 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java @@ -14,7 +14,7 @@ public class DummyNodeWithDeprecatedAttribute extends DummyNode { public DummyNodeWithDeprecatedAttribute(int id) { - super(id); + super(); } // this is the deprecated attribute diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyRoot.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyRoot.java index 633d38db87..a8ddf5ca52 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyRoot.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyRoot.java @@ -12,7 +12,7 @@ public class DummyRoot extends DummyNode implements RootNode { private final Map suppressMap; public DummyRoot(Map suppressMap) { - super(0); + super(); this.suppressMap = suppressMap; } @@ -25,11 +25,6 @@ public class DummyRoot extends DummyNode implements RootNode { return suppressMap; } - @Override - public String toString() { - return "dummyRootNode"; - } - @Override public String getXPathNodeName() { return "dummyRootNode"; diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeTest.java similarity index 76% rename from pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java rename to pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeTest.java index d23db81a28..e9b0f3b513 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/AbstractNodeTest.java @@ -2,13 +2,18 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.ast; +package net.sourceforge.pmd.lang.ast.impl; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.node; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.root; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.tree; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.List; + import org.jaxen.JaxenException; import org.junit.Before; import org.junit.Rule; @@ -16,6 +21,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import net.sourceforge.pmd.junit.JavaUtilLoggingRule; +import net.sourceforge.pmd.lang.ast.DummyNode; +import net.sourceforge.pmd.lang.ast.DummyNodeWithDeprecatedAttribute; +import net.sourceforge.pmd.lang.ast.DummyRoot; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.RootNode; import net.sourceforge.pmd.lang.ast.xpath.Attribute; import junitparams.JUnitParamsRunner; @@ -64,36 +74,25 @@ public class AbstractNodeTest { return indexes; } - private int id; - private Node rootNode; - - private int nextId() { - return id++; - } - - private Node newDummyNode() { - return new DummyNode(nextId()); - } - - private static Node addChild(final Node parent, final Node child) { - parent.jjtAddChild(child, parent.getNumChildren()); // Append child at the end - child.jjtSetParent(parent); - return parent; - } + private DummyRoot rootNode; @Before public void setUpSampleNodeTree() { - id = 0; - rootNode = newDummyNode(); + rootNode = tree( + () -> { + DummyRoot root = root(); - for (int i = 0; i < NUM_CHILDREN; i++) { - final Node child = newDummyNode(); - for (int j = 0; j < NUM_GRAND_CHILDREN; j++) { - final Node grandChild = newDummyNode(); - addChild(child, grandChild); + for (int i = 0; i < NUM_CHILDREN; i++) { + final DummyNode child = node(); + for (int j = 0; j < NUM_GRAND_CHILDREN; j++) { + child.addChild(node(), j); + } + root.addChild(child, i); + } + return root; } - addChild(rootNode, child); - } + ); + } /** @@ -102,12 +101,8 @@ public class AbstractNodeTest { @Test @Parameters(method = "childrenIndexes") public void testRemoveChildOfRootNode(final int childIndex) { - final Node child = rootNode.getChild(childIndex); - final Node[] grandChildren = new Node[child.getNumChildren()]; - for (int i = 0; i < grandChildren.length; i++) { - final Node grandChild = child.getChild(i); - grandChildren[i] = grandChild; - } + final DummyNode child = rootNode.getChild(childIndex); + final List grandChildren = child.children().toList(); // Do the actual removal child.remove(); @@ -129,11 +124,7 @@ public class AbstractNodeTest { @Test public void testRemoveRootNode() { // Check that the root node has the expected properties - final Node[] children = new Node[rootNode.getNumChildren()]; - for (int i = 0; i < children.length; i++) { - final Node child = rootNode.getChild(i); - children[i] = child; - } + final List children = rootNode.children().toList(); // Do the actual removal rootNode.remove(); @@ -154,8 +145,8 @@ public class AbstractNodeTest { @Test @Parameters(method = "childrenAndGrandChildrenIndexes") public void testRemoveGrandChildNode(final int childIndex, final int grandChildIndex) { - final Node child = rootNode.getChild(childIndex); - final Node grandChild = child.getChild(grandChildIndex); + final DummyNode child = rootNode.getChild(childIndex); + final DummyNode grandChild = child.getChild(grandChildIndex); // Do the actual removal grandChild.remove(); @@ -172,11 +163,7 @@ public class AbstractNodeTest { @Test @Parameters(method = "childrenIndexes") public void testRemoveRootNodeChildAtIndex(final int childIndex) { - final Node[] originalChildren = new Node[rootNode.getNumChildren()]; - - for (int i = 0; i < originalChildren.length; i++) { - originalChildren[i] = rootNode.getChild(i); - } + final List originalChildren = rootNode.children().toList(); // Do the actual removal rootNode.removeChildAtIndex(childIndex); @@ -189,7 +176,7 @@ public class AbstractNodeTest { j++; } // Check that the nodes have been rightly shifted - assertEquals(originalChildren[j], rootNode.getChild(i)); + assertEquals(originalChildren.get(j), rootNode.getChild(i)); // Check that the child index has been updated assertEquals(i, rootNode.getChild(i).getIndexInParent()); j++; @@ -218,7 +205,7 @@ public class AbstractNodeTest { @Parameters(method = "grandChildrenIndexes") public void testRemoveChildAtIndexOnNodeWithNoChildren(final int grandChildIndex) { // grandChild does not have any child - final Node grandChild = rootNode.getChild(grandChildIndex).getChild(grandChildIndex); + final DummyNode grandChild = rootNode.getChild(grandChildIndex).getChild(grandChildIndex); // Do the actual removal grandChild.removeChildAtIndex(0); @@ -228,17 +215,18 @@ public class AbstractNodeTest { assertEquals(0, grandChild.getNumChildren()); } + @Test public void testDeprecatedAttributeXPathQuery() throws JaxenException { class MyRootNode extends DummyNode implements RootNode { - private MyRootNode(int id) { - super(id); + private MyRootNode() { + super(); } } - Node root = addChild(new MyRootNode(nextId()), new DummyNodeWithDeprecatedAttribute(2)); - root.findChildNodesWithXPath("//dummyNode[@Size=1]"); + tree(() -> root(new DummyNodeWithDeprecatedAttribute(2))) + .findChildNodesWithXPath("//dummyNode[@Size=1]"); String log = loggingRule.getLog(); @@ -247,4 +235,5 @@ public class AbstractNodeTest { assertTrue(log.contains("dummyNode/@Size")); } + } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyTreeUtil.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/DummyTreeUtil.java similarity index 72% rename from pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyTreeUtil.java rename to pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/DummyTreeUtil.java index a6b4f86377..d8ead26590 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyTreeUtil.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/impl/DummyTreeUtil.java @@ -1,15 +1,19 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.ast; +package net.sourceforge.pmd.lang.ast.impl; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; +import net.sourceforge.pmd.lang.ast.DummyNode; import net.sourceforge.pmd.lang.ast.DummyNode.DummyNodeTypeB; +import net.sourceforge.pmd.lang.ast.DummyRoot; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.NodeStream; /** @@ -23,15 +27,13 @@ public final class DummyTreeUtil { } + public static DummyRoot root(DummyNode... children) { + return nodeImpl(new DummyRoot(), children); + } + /** Creates a dummy node with the given children. */ public static DummyNode node(DummyNode... children) { - DummyNode node = new DummyNode() { - @Override - public String toString() { - return getImage(); - } - }; - return nodeImpl(node, children); + return nodeImpl(new DummyNode(), children); } /** Creates a dummy node with the given children. */ @@ -39,12 +41,8 @@ public final class DummyTreeUtil { return nodeImpl(new DummyNodeTypeB(), children); } - private static DummyNode nodeImpl(DummyNode node, DummyNode... children) { - node.children = children; - for (int i = 0; i < children.length; i++) { - children[i].jjtSetParent(node); - children[i].jjtSetChildIndex(i); - } + private static T nodeImpl(T node, DummyNode... children) { + node.publicSetChildren(children); return node; } @@ -77,14 +75,14 @@ public final class DummyTreeUtil { * ) * */ - public static DummyNode tree(Supplier supplier) { - DummyNode dummyNode = supplier.get(); + public static DummyRoot tree(Supplier supplier) { + DummyRoot dummyNode = supplier.get(); assignPathImage(dummyNode, ""); return dummyNode; } - private static void assignPathImage(Node node, String curPath) { + private static void assignPathImage(DummyNode node, String curPath) { node.setImage(curPath); for (int i = 0; i < node.getNumChildren(); i++) { diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/internal/NodeStreamBlanketTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/internal/NodeStreamBlanketTest.java index 5732863c96..0c2994908a 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/internal/NodeStreamBlanketTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/internal/NodeStreamBlanketTest.java @@ -6,9 +6,10 @@ package net.sourceforge.pmd.lang.ast.internal; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertSame; -import static net.sourceforge.pmd.lang.ast.DummyTreeUtil.node; -import static net.sourceforge.pmd.lang.ast.DummyTreeUtil.nodeB; -import static net.sourceforge.pmd.lang.ast.DummyTreeUtil.tree; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.node; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.nodeB; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.root; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.tree; import java.util.ArrayList; import java.util.Arrays; @@ -41,7 +42,8 @@ public class NodeStreamBlanketTest { private static final List ASTS = Arrays.asList( tree( () -> - node( + root( + node( node(), nodeB( @@ -55,7 +57,7 @@ public class NodeStreamBlanketTest { ), tree( () -> - node( + root( node(), node(), nodeB( diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/internal/NodeStreamTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/internal/NodeStreamTest.java index f3f2b19d49..10d5af03d1 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/internal/NodeStreamTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/internal/NodeStreamTest.java @@ -4,10 +4,11 @@ package net.sourceforge.pmd.lang.ast.internal; -import static net.sourceforge.pmd.lang.ast.DummyTreeUtil.followPath; -import static net.sourceforge.pmd.lang.ast.DummyTreeUtil.node; -import static net.sourceforge.pmd.lang.ast.DummyTreeUtil.pathsOf; -import static net.sourceforge.pmd.lang.ast.DummyTreeUtil.tree; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.followPath; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.node; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.pathsOf; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.root; +import static net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil.tree; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.collection.IsIterableContainingInOrder.contains; import static org.junit.Assert.assertEquals; @@ -25,6 +26,7 @@ import org.junit.Test; import net.sourceforge.pmd.lang.ast.DummyNode; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.NodeStream.DescendantNodeStream; /** @@ -35,7 +37,7 @@ public class NodeStreamTest { private final DummyNode tree1 = tree( () -> - node(// "" + root(// "" node(// 0 node(), // 00 node(// 01 @@ -52,7 +54,7 @@ public class NodeStreamTest { private final DummyNode tree2 = tree( () -> - node( + root( node(), node(), node( @@ -186,7 +188,7 @@ public class NodeStreamTest { @Test public void testGet() { // ("0", "00", "01", "010", "011", "012", "013", "1") - NodeStream stream = tree1.descendants(); + DescendantNodeStream stream = tree1.descendants(); assertEquals("0", stream.get(0).getImage()); assertEquals("00", stream.get(1).getImage()); @@ -197,7 +199,7 @@ public class NodeStreamTest { @Test public void testNodeStreamsCanBeIteratedSeveralTimes() { - NodeStream stream = tree1.descendants(); + DescendantNodeStream stream = tree1.descendants(); assertThat(stream.count(), equalTo(8)); assertThat(stream.count(), equalTo(8)); @@ -252,7 +254,7 @@ public class NodeStreamTest { MutableInt upstreamEvals = new MutableInt(); MutableInt downstreamEvals = new MutableInt(); - NodeStream stream = + NodeStream stream = tree1.descendants() .filter(n -> n.getImage().matches("0.*")) .take(4) @@ -299,7 +301,7 @@ public class NodeStreamTest { MutableInt tree1Evals = new MutableInt(); - NodeStream unionStream = tree1.descendantsOrSelf().peek(n -> tree1Evals.increment()); + NodeStream unionStream = tree1.descendantsOrSelf().peek(n -> tree1Evals.increment()); int i = 0; diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java index a1dda2cb58..8017a09fd6 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java @@ -32,40 +32,38 @@ public class AttributeAxisIteratorTest { */ @Test public void testAttributeAxisIterator() { - DummyNode dummyNode = new DummyNode(1); - dummyNode.testingOnlySetBeginLine(1); - dummyNode.testingOnlySetBeginColumn(1); + DummyNode dummyNode = new DummyNode(); + dummyNode.setCoords(1, 1, 2, 2); AttributeAxisIterator it = new AttributeAxisIterator(dummyNode); Map atts = toMap(it); - Assert.assertEquals(7, atts.size()); + Assert.assertEquals(6, atts.size()); assertTrue(atts.containsKey("BeginColumn")); assertTrue(atts.containsKey("BeginLine")); assertTrue(atts.containsKey("FindBoundary")); assertTrue(atts.containsKey("Image")); - assertTrue(atts.containsKey("SingleLine")); assertTrue(atts.containsKey("EndColumn")); assertTrue(atts.containsKey("EndLine")); } @Test public void testAttributeAxisIteratorWithEnum() { - DummyNodeWithEnum dummyNode = new DummyNodeWithEnum(1); + DummyNodeWithEnum dummyNode = new DummyNodeWithEnum(); AttributeAxisIterator it = new AttributeAxisIterator(dummyNode); Map atts = toMap(it); - Assert.assertEquals(8, atts.size()); + Assert.assertEquals(7, atts.size()); assertTrue(atts.containsKey("Enum")); assertEquals(DummyNodeWithEnum.MyEnum.FOO, atts.get("Enum").getValue()); } @Test public void testAttributeAxisIteratorWithList() { - DummyNodeWithList dummyNode = new DummyNodeWithList(1); + DummyNodeWithList dummyNode = new DummyNodeWithList(); AttributeAxisIterator it = new AttributeAxisIterator(dummyNode); Map atts = toMap(it); - Assert.assertEquals(8, atts.size()); + Assert.assertEquals(7, atts.size()); assertTrue(atts.containsKey("List")); assertEquals(Arrays.asList("A", "B"), atts.get("List").getValue()); assertFalse(atts.containsKey("NodeList")); @@ -82,10 +80,6 @@ public class AttributeAxisIteratorTest { public static class DummyNodeWithEnum extends DummyNode { - public DummyNodeWithEnum(int id) { - super(id); - } - public enum MyEnum { FOO, BAR } @@ -97,10 +91,6 @@ public class AttributeAxisIteratorTest { public static class DummyNodeWithList extends DummyNode { - public DummyNodeWithList(int id) { - super(id); - } - public List getList() { return Arrays.asList("A", "B"); } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/DocumentNavigatorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/DocumentNavigatorTest.java index aa316e9313..cf57b85a52 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/DocumentNavigatorTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/DocumentNavigatorTest.java @@ -12,7 +12,6 @@ import org.junit.Test; import net.sourceforge.pmd.lang.ast.DummyNode; import net.sourceforge.pmd.lang.ast.DummyRoot; -import net.sourceforge.pmd.lang.ast.Node; /** * Unit test for {@link DocumentNavigator} @@ -30,10 +29,9 @@ public class DocumentNavigatorTest { assertNotNull(e); } - Node root = new DummyRoot(); - Node n = new DummyNode(1); - root.jjtAddChild(n, 0); - n.jjtSetParent(root); + DummyNode root = new DummyRoot(); + DummyNode n = new DummyNode(); + root.addChild(n, 0); assertSame(root, nav.getDocumentNode(n)); } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/NoAttributeTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/NoAttributeTest.java index 0b5781ce6b..9f9b93e3f6 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/NoAttributeTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/NoAttributeTest.java @@ -25,7 +25,7 @@ public class NoAttributeTest { @Test public void testNoAttrInherited() { - Node child = new NodeNoInherited(12); + Node child = new NodeNoInherited(); Set attrNames = IteratorUtil.toList(child.getXPathAttributesIterator()).stream().map(Attribute::getName).collect(Collectors.toSet()); @@ -45,7 +45,7 @@ public class NoAttributeTest { assertTrue(0 < IteratorUtil.count(new NodeAllAttr(12).getXPathAttributesIterator())); - NodeNoAttrAll child = new NodeNoAttrAll(12); + NodeNoAttrAll child = new NodeNoAttrAll(); Set attrNames = IteratorUtil.toList(child.getXPathAttributesIterator()).stream().map(Attribute::getName).collect(Collectors.toSet()); // from Noded, so not suppressed @@ -57,7 +57,7 @@ public class NoAttributeTest { @Test public void testNoAttrAllIsNotInherited() { - NodeNoAttrAllChild child = new NodeNoAttrAllChild(12); + NodeNoAttrAllChild child = new NodeNoAttrAllChild(); Set attrNames = IteratorUtil.toList(child.getXPathAttributesIterator()).stream().map(Attribute::getName).collect(Collectors.toSet()); @@ -70,12 +70,8 @@ public class NoAttributeTest { private static class DummyNodeParent extends DummyNode { - DummyNodeParent(int id) { - super(id); - } - - DummyNodeParent(int id, boolean findBoundary) { - super(id, findBoundary); + DummyNodeParent() { + super(); } public String getSomeName() { @@ -100,10 +96,6 @@ public class NoAttributeTest { @NoAttribute(scope = NoAttrScope.INHERITED) private static class NodeNoInherited extends DummyNodeParent { - NodeNoInherited(int id) { - super(id); - } - // getSomeName is inherited and filtered out by NoAttrScope.INHERITED // getSomeInt is inherited but overridden here, so NoAttrScope.INHERITED has no effect // getSomeLong is inherited and overridden here, @@ -140,18 +132,13 @@ public class NoAttributeTest { private static class NodeAllAttr extends DummyNodeParent { NodeAllAttr(int id) { - super(id); + super(); } } @NoAttribute(scope = NoAttrScope.ALL) private static class NodeNoAttrAll extends DummyNodeParent { - - NodeNoAttrAll(int id) { - super(id); - } - public int getMySuppressedAttr() { return 12; } @@ -161,11 +148,6 @@ public class NoAttributeTest { private static class NodeNoAttrAllChild extends NodeNoAttrAll { - - NodeNoAttrAllChild(int id) { - super(id); - } - public int getNotSuppressedAttr() { return 12; } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/XPathRuleTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/XPathRuleTest.java index 83e85788c2..41888278c7 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/XPathRuleTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/XPathRuleTest.java @@ -94,7 +94,7 @@ public class XPathRuleTest { DummyRoot root = new DummyRoot(); DummyNode dummy = new DummyNodeWithDeprecatedAttribute(2); dummy.setCoords(1, 1, 1, 2); - root.jjtAddChild(dummy, 0); + root.addChild(dummy, 0); return root; } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/DummyNodeWithListAndEnum.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/DummyNodeWithListAndEnum.java index 0ca895486a..d066e3afa3 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/DummyNodeWithListAndEnum.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/DummyNodeWithListAndEnum.java @@ -12,7 +12,7 @@ import net.sourceforge.pmd.lang.ast.DummyNode; public class DummyNodeWithListAndEnum extends DummyNode { public DummyNodeWithListAndEnum(int id) { - super(id); + super(); beginLine = 1; beginColumn = 1; endLine = 1; diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/saxon/ElementNodeTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/saxon/ElementNodeTest.java index ead5c73553..0a4cf00195 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/saxon/ElementNodeTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/saxon/ElementNodeTest.java @@ -15,13 +15,13 @@ public class ElementNodeTest { @Test public void testCompareOrder() { - DummyNode node = new DummyNode(1, false, "dummy"); - DummyNode foo1 = new DummyNode(2, false, "foo"); - foo1.testingOnlySetBeginLine(1); - DummyNode foo2 = new DummyNode(2, false, "foo"); - foo2.testingOnlySetBeginLine(2); - node.jjtAddChild(foo1, 0); - node.jjtAddChild(foo2, 1); + DummyNode node = new DummyNode(false, "dummy"); + DummyNode foo1 = new DummyNode(false, "foo"); + foo1.setCoords(1, 1, 2, 2); + DummyNode foo2 = new DummyNode(false, "foo"); + foo2.setCoords(2, 1, 2, 2); + node.addChild(foo1, 0); + node.addChild(foo2, 1); DocumentNode document = new DocumentNode(node); ElementNode elementFoo1 = document.nodeToElementNode.get(foo1); @@ -37,15 +37,13 @@ public class ElementNodeTest { @Test public void testCompareOrderSamePosition() { - DummyNode node = new DummyNode(1, false, "dummy"); - DummyNode foo1 = new DummyNode(2, false, "foo"); - foo1.testingOnlySetBeginLine(1); - foo1.testingOnlySetBeginColumn(1); - DummyNode foo2 = new DummyNode(2, false, "foo"); - foo2.testingOnlySetBeginLine(1); - foo2.testingOnlySetBeginColumn(1); - node.jjtAddChild(foo1, 0); - node.jjtAddChild(foo2, 1); + DummyNode node = new DummyNode(false, "dummy"); + DummyNode foo1 = new DummyNode(false, "foo"); + foo1.setCoords(1, 1, 5, 5); + DummyNode foo2 = new DummyNode(false, "foo"); + foo2.setCoords(1, 1, 5, 5); + node.addChild(foo1, 0); + node.addChild(foo2, 1); DocumentNode document = new DocumentNode(node); ElementNode elementFoo1 = document.nodeToElementNode.get(foo1); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/AbstractRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/AbstractRendererTest.java index 97e0e6664d..cc959120ea 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/AbstractRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/AbstractRendererTest.java @@ -82,11 +82,8 @@ public abstract class AbstractRendererTest { } protected static DummyNode createNode(int endColumn) { - DummyNode node = new DummyNode(1); - node.testingOnlySetBeginLine(1); - node.testingOnlySetBeginColumn(1); - node.testingOnlySetEndLine(1); - node.testingOnlySetEndColumn(endColumn); + DummyNode node = new DummyNode(); + node.setCoords(1, 1, 1, endColumn); return node; } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XMLRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XMLRendererTest.java index 675574e4ac..c9dab07c9b 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XMLRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XMLRendererTest.java @@ -75,16 +75,12 @@ public class XMLRendererTest extends AbstractRendererTest { @Override public String filter(String expected) { - String result = expected.replaceAll(" timestamp=\"[^\"]+\">", " timestamp=\"\">"); - return result; + return expected.replaceAll(" timestamp=\"[^\"]+\">", " timestamp=\"\">"); } private RuleViolation createRuleViolation(String description) { - DummyNode node = new DummyNode(1); - node.testingOnlySetBeginLine(1); - node.testingOnlySetBeginColumn(1); - node.testingOnlySetEndLine(1); - node.testingOnlySetEndColumn(1); + DummyNode node = new DummyNode(); + node.setCoords(1, 1, 1, 1); RuleContext ctx = new RuleContext(); ctx.setSourceCodeFile(new File(getSourceCodeFilename())); return new ParametricRuleViolation(new FooRule(), ctx, node, description); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XSLTRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XSLTRendererTest.java index 200c0fc0e6..2cc3f0c9f4 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XSLTRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XSLTRendererTest.java @@ -22,9 +22,8 @@ public class XSLTRendererTest { public void testDefaultStylesheet() throws Exception { XSLTRenderer renderer = new XSLTRenderer(); Report report = new Report(); - DummyNode node = new DummyNode(1); - node.testingOnlySetBeginLine(1); - node.testingOnlySetBeginColumn(1); + DummyNode node = new DummyNode(); + node.setCoords(1, 1, 1, 2); RuleViolation rv = new ParametricRuleViolation(new FooRule(), new RuleContext(), node, "violation message"); report.addRuleViolation(rv); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/util/FooRuleWithLanguageSetInJava.java b/pmd-core/src/test/java/net/sourceforge/pmd/util/FooRuleWithLanguageSetInJava.java new file mode 100644 index 0000000000..2363f63aac --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/util/FooRuleWithLanguageSetInJava.java @@ -0,0 +1,26 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util; + +import java.util.List; + +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.DummyLanguageModule; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.AbstractRule; + +public class FooRuleWithLanguageSetInJava extends AbstractRule { + + public FooRuleWithLanguageSetInJava() { + setLanguage(LanguageRegistry.getLanguage(DummyLanguageModule.NAME)); + } + + + @Override + public void apply(List nodes, RuleContext ctx) { + // do nothing + } +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/util/treeexport/TreeRenderersTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/util/treeexport/TreeRenderersTest.java index 263f94bbe0..e791d57b59 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/util/treeexport/TreeRenderersTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/util/treeexport/TreeRenderersTest.java @@ -85,8 +85,8 @@ public class TreeRenderersTest { MyDummyNode dummy2 = new MyDummyNode(); - dummy.jjtAddChild(dummy1, 0); - dummy.jjtAddChild(dummy2, 1); + dummy.addChild(dummy1, 0); + dummy.addChild(dummy2, 1); return dummy; } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/util/treeexport/XmlTreeRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/util/treeexport/XmlTreeRendererTest.java index d327cf8dfb..af6cc024b0 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/util/treeexport/XmlTreeRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/util/treeexport/XmlTreeRendererTest.java @@ -269,8 +269,8 @@ public class XmlTreeRendererTest { MyDummyNode dummy2 = new MyDummyNode(); - dummy.jjtAddChild(dummy1, 0); - dummy.jjtAddChild(dummy2, 1); + dummy.addChild(dummy1, 0); + dummy.addChild(dummy2, 1); return dummy; } diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index 4e7ca4754d..c6f1e12878 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -21,6 +21,8 @@ import net.sourceforge.pmd.PMDVersion; public class BinaryDistributionIT extends AbstractBinaryDistributionTest { + private static final String SUPPORTED_LANGUAGES = "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, go, groovy, java, jsp, kotlin, lua, matlab, modelica, objectivec, perl, php, plsql, python, ruby, scala, swift, vf, xml]"; + @Test public void testFileExistence() { assertTrue(getBinaryDistribution().exists()); @@ -81,7 +83,7 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { ExecutionResult result; result = CpdExecutor.runCpd(tempDir, "-h"); - result.assertExecutionResult(0, "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, go, groovy, java, jsp, kotlin, lua, matlab, modelica, objectivec, perl, php, plsql, python, ruby, scala, swift, vf]"); + result.assertExecutionResult(0, SUPPORTED_LANGUAGES); result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "10", "--format", "text", "--files", srcDir); result.assertExecutionResult(4, "Found a 10 line (55 tokens) duplication in the following files:"); diff --git a/pmd-doc/pom.xml b/pmd-doc/pom.xml index 9236a3521c..72179a18a3 100644 --- a/pmd-doc/pom.xml +++ b/pmd-doc/pom.xml @@ -13,9 +13,6 @@ 8 - - 1.${java.version} - 1.${java.version} 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 index 124015b67e..2c4f6a4d21 100644 --- a/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoLanguage.java +++ b/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoLanguage.java @@ -5,15 +5,10 @@ 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 index ff99a98129..1369c28a5e 100644 --- a/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoTokenizer.java +++ b/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoTokenizer.java @@ -10,9 +10,6 @@ import net.sourceforge.pmd.cpd.internal.AntlrTokenizer; import net.sourceforge.pmd.lang.ast.impl.antlr4.AntlrTokenManager; import net.sourceforge.pmd.lang.go.antlr4.GolangLexer; -/** - * The Go tokenizer. - */ public class GoTokenizer extends AntlrTokenizer { @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java index 04858ee65e..12f5ea4cc1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java @@ -18,20 +18,20 @@ public class JavaLanguageModule extends BaseLanguageModule { public JavaLanguageModule() { super(NAME, null, TERSE_NAME, JavaRuleChainVisitor.class, "java"); - addVersion("1.3", new JavaLanguageHandler(3), false); - addVersion("1.4", new JavaLanguageHandler(4), false); - addVersions(new JavaLanguageHandler(5), false, "1.5", "5"); - addVersions(new JavaLanguageHandler(6), false, "1.6", "6"); - addVersions(new JavaLanguageHandler(7), false, "1.7", "7"); - addVersions(new JavaLanguageHandler(8), false, "1.8", "8"); - addVersions(new JavaLanguageHandler(9), false, "9", "1.9"); - addVersions(new JavaLanguageHandler(10), false, "10", "1.10"); - addVersion("11", new JavaLanguageHandler(11), false); - addVersion("12", new JavaLanguageHandler(12), false); - addVersion("13", new JavaLanguageHandler(13), false); - addVersion("13-preview", new JavaLanguageHandler(13, true), false); - addVersion("14", new JavaLanguageHandler(14), true); // 14 is the default - addVersion("14-preview", new JavaLanguageHandler(14, true), false); + addVersion("1.3", new JavaLanguageHandler(3)); + addVersion("1.4", new JavaLanguageHandler(4)); + addVersion("1.5", new JavaLanguageHandler(5), "5"); + addVersion("1.6", new JavaLanguageHandler(6), "6"); + addVersion("1.7", new JavaLanguageHandler(7), "7"); + addVersion("1.8", new JavaLanguageHandler(8), "8"); + addVersion("9", new JavaLanguageHandler(9), "1.9"); + addVersion("10", new JavaLanguageHandler(10), "1.10"); + addVersion("11", new JavaLanguageHandler(11)); + addVersion("12", new JavaLanguageHandler(12)); + addVersion("13", new JavaLanguageHandler(13)); + addVersion("13-preview", new JavaLanguageHandler(13, true)); + addDefaultVersion("14", new JavaLanguageHandler(14)); // 14 is the default + addVersion("14-preview", new JavaLanguageHandler(14, true)); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArguments.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArguments.java index 7739488082..0204618248 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArguments.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArguments.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttribute; public class ASTArguments extends AbstractJavaNode { @@ -29,6 +30,7 @@ public class ASTArguments extends AbstractJavaNode { * @deprecated for removal. Use {@link #size()} or {@link ASTArgumentList#size()} instead. */ @Deprecated + @DeprecatedAttribute(replaceWith = "@Size") public int getArgumentCount() { return size(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java index b371dcdecc..b86b857dae 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java @@ -4,7 +4,6 @@ package net.sourceforge.pmd.lang.java.ast; -import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; @@ -19,9 +18,7 @@ import net.sourceforge.pmd.lang.ast.Node; */ public class ASTClassOrInterfaceType extends AbstractJavaTypeNode { - @InternalApi - @Deprecated - public ASTClassOrInterfaceType(int id) { + ASTClassOrInterfaceType(int id) { super(id); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCompilationUnit.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCompilationUnit.java index b463754c52..f188c18439 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCompilationUnit.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCompilationUnit.java @@ -67,7 +67,7 @@ public class ASTCompilationUnit extends AbstractJavaTypeNode implements RootNode } @Override - public ASTCompilationUnit getRoot() { + public @NonNull ASTCompilationUnit getRoot() { return this; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConstructorDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConstructorDeclaration.java index 628e5f57f8..086047c941 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConstructorDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConstructorDeclaration.java @@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttribute; public class ASTConstructorDeclaration extends AbstractMethodOrConstructorDeclaration { @@ -57,6 +58,7 @@ public class ASTConstructorDeclaration extends AbstractMethodOrConstructorDeclar * @deprecated Use {@link #getArity()} */ @Deprecated + @DeprecatedAttribute(replaceWith = "@Arity") public int getParameterCount() { return getArity(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclaration.java index d91fbaf9aa..13a23aa3ea 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclaration.java @@ -8,6 +8,7 @@ import java.util.Iterator; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.SignedNode; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttribute; import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSignature; import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; @@ -178,6 +179,7 @@ public class ASTFieldDeclaration extends AbstractJavaAccessTypeNode implements D * Iterate on the {@linkplain ASTVariableDeclaratorId VariableDeclaratorIds} instead */ @Deprecated + @DeprecatedAttribute(replaceWith = "VariableDeclaratorId/@Name") public String getVariableName() { ASTVariableDeclaratorId decl = getFirstDescendantOfType(ASTVariableDeclaratorId.class); if (decl != null) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameter.java index 5155ce5597..90d39e998d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameter.java @@ -23,9 +23,7 @@ public class ASTFormalParameter extends AbstractJavaAccessTypeNode implements Di private boolean isVarargs; - @InternalApi - @Deprecated - public ASTFormalParameter(int id) { + ASTFormalParameter(int id) { super(id); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameters.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameters.java index 3d84e9a065..e684955e66 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameters.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameters.java @@ -7,14 +7,12 @@ package net.sourceforge.pmd.lang.java.ast; import java.util.Iterator; import java.util.List; -import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttribute; public class ASTFormalParameters extends AbstractJavaNode implements Iterable { - @InternalApi - @Deprecated - public ASTFormalParameters(int id) { + ASTFormalParameters(int id) { super(id); } @@ -28,6 +26,7 @@ public class ASTFormalParameters extends AbstractJavaNode implements Iterable 0) { - AbstractNode firstChild = (AbstractNode) getChild(0); - jjtSetFirstToken(firstChild.jjtGetFirstToken()); + setFirstToken(getFirstChild().getFirstToken()); } } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledExpression.java index 109f9665a3..70afff9dd0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledExpression.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.ast.AbstractNode; public class ASTSwitchLabeledExpression extends AbstractJavaNode implements ASTSwitchLabeledRule { @@ -29,8 +28,7 @@ public class ASTSwitchLabeledExpression extends AbstractJavaNode implements ASTS public void jjtClose() { super.jjtClose(); if (getNumChildren() > 0) { - AbstractNode firstChild = (AbstractNode) getChild(0); - jjtSetFirstToken(firstChild.jjtGetFirstToken()); + setFirstToken(getFirstChild().getFirstToken()); } } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledThrowStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledThrowStatement.java index d306fc5228..b52c1b9f83 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledThrowStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledThrowStatement.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.ast.AbstractNode; public class ASTSwitchLabeledThrowStatement extends AbstractJavaNode implements ASTSwitchLabeledRule { @@ -29,8 +28,7 @@ public class ASTSwitchLabeledThrowStatement extends AbstractJavaNode implements public void jjtClose() { super.jjtClose(); if (getNumChildren() > 0) { - AbstractNode firstChild = (AbstractNode) getChild(0); - jjtSetFirstToken(firstChild.jjtGetFirstToken()); + setFirstToken(getFirstChild().getFirstToken()); } } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java index 3b7a086ed5..3ad749aa84 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java @@ -42,9 +42,7 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim private VariableNameDeclaration nameDeclaration; private boolean explicitReceiverParameter = false; - @InternalApi - @Deprecated - public ASTVariableDeclaratorId(int id) { + ASTVariableDeclaratorId(int id) { super(id); } @@ -96,6 +94,7 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim */ @Override @Deprecated + @DeprecatedAttribute(replaceWith = "@ArrayType") public boolean isArray() { return arrayDepth > 0; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWildcardBounds.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWildcardBounds.java index c5373c131d..8153aae042 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWildcardBounds.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWildcardBounds.java @@ -29,7 +29,7 @@ public class ASTWildcardBounds extends AbstractJavaTypeNode { * in {@code }. */ public boolean isUpperBound() { - return jjtGetFirstToken().getImage().equals("extends"); + return getFirstToken().getImage().equals("extends"); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java index ec3b3cd2ff..2a4c13c95a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java @@ -7,13 +7,12 @@ package net.sourceforge.pmd.lang.java.ast; import org.checkerframework.checker.nullness.qual.NonNull; import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.impl.javacc.AbstractJjtreeNode; import net.sourceforge.pmd.lang.symboltable.Scope; @Deprecated @InternalApi -public abstract class AbstractJavaNode extends AbstractJjtreeNode implements JavaNode { +public abstract class AbstractJavaNode extends AbstractJjtreeNode implements JavaNode { private Scope scope; private Comment comment; @@ -25,33 +24,24 @@ public abstract class AbstractJavaNode extends AbstractJjtreeNode impl super(id); } - @Override - public Object childrenAccept(JavaParserVisitor visitor, Object data) { - for (Node child : children()) { - ((JavaNode) child).jjtAccept(visitor, data); - } - - return data; - } - - - @Override - public void childrenAccept(SideEffectingVisitor visitor, T data) { - for (Node child : children()) { - ((JavaNode) child).jjtAccept(visitor, data); - } - - } - - @Override public Scope getScope() { if (scope == null) { - return ((JavaNode) parent).getScope(); + return getParent().getScope(); } return scope; } + @Override // override to make it accessible to tests that build nodes (which have been removed on java-grammar) + protected void addChild(AbstractJavaNode child, int index) { + super.addChild(child, index); + } + + @Override // override to make it accessible to parser + protected void setImage(String image) { + super.setImage(image); + } + @InternalApi @Deprecated @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Comment.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Comment.java index fa66566766..22ebe37c10 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Comment.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Comment.java @@ -13,10 +13,11 @@ import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.PMD; -import net.sourceforge.pmd.lang.ast.AbstractNode; +import net.sourceforge.pmd.lang.ast.impl.javacc.AbstractJjtreeNode; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; -public abstract class Comment extends AbstractNode { +public abstract class Comment extends AbstractJjtreeNode { + // single regex, that captures: the start of a multi-line comment (/**|/*), the start of a single line comment (//) // or the start of line within a multine comment (*). It removes the end of the comment (*/) if existing. private static final Pattern COMMENT_LINE_COMBINED = Pattern.compile("^(?://|/\\*\\*?|\\*)?(.*?)(?:\\*/|/)?$"); @@ -25,11 +26,11 @@ public abstract class Comment extends AbstractNode { static final Pattern NEWLINES_PATTERN = Pattern.compile("\\u000D\\u000A|[\\u000A\\u000B\\u000C\\u000D\\u0085\\u2028\\u2029]"); protected Comment(JavaccToken t) { - super(-1); + super(0); setImage(t.getImage()); - jjtSetFirstToken(t); - jjtSetLastToken(t); + setFirstToken(t); + setLastToken(t); } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DummyJavaNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DummyJavaNode.java index 96aaf0892e..04fe6b5a67 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DummyJavaNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DummyJavaNode.java @@ -20,6 +20,11 @@ public class DummyJavaNode extends AbstractJavaNode { super(id); } + @Override + public void setImage(String image) { + super.setImage(image); + } + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return data; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/FormalComment.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/FormalComment.java index 8920d25414..27103bc54a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/FormalComment.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/FormalComment.java @@ -5,11 +5,10 @@ package net.sourceforge.pmd.lang.java.ast; import java.util.ArrayList; -import java.util.Collection; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.java.javadoc.JavadocTag; @@ -29,19 +28,21 @@ public class FormalComment extends Comment { } private void findJavadocs() { - Collection kids = new ArrayList<>(); + List kids = new ArrayList<>(); Matcher javadocTagMatcher = JAVADOC_TAG.matcher(getFilteredComment()); while (javadocTagMatcher.find()) { JavadocTag tag = JavadocTag.tagFor(javadocTagMatcher.group(1)); int tagStartIndex = javadocTagMatcher.start(1); if (tag != null) { - kids.add(new JavadocElement(getBeginLine(), getBeginLine(), + kids.add(new JavadocElement(getFirstToken(), getBeginLine(), getBeginLine(), // TODO valid? tagStartIndex, tagStartIndex + tag.label.length() + 1, tag)); } } - children = kids.toArray(new Node[0]); + for (int i = kids.size() - 1; i >= 0; i--) { + addChild(kids.get(i), i); + } } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java index 8c1dd08f88..7c7cbbfba4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/InternalApiBridge.java @@ -5,6 +5,9 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.HashSet; +import java.util.Set; + import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument; @@ -17,10 +20,100 @@ import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument; @InternalApi public final class InternalApiBridge { + + private static final Set PRIMITIVE_TYPES; + + static { + PRIMITIVE_TYPES = new HashSet<>(); + PRIMITIVE_TYPES.add("boolean"); + PRIMITIVE_TYPES.add("char"); + PRIMITIVE_TYPES.add("byte"); + PRIMITIVE_TYPES.add("short"); + PRIMITIVE_TYPES.add("int"); + PRIMITIVE_TYPES.add("long"); + PRIMITIVE_TYPES.add("float"); + PRIMITIVE_TYPES.add("double"); + } + private InternalApiBridge() { } + @Deprecated + public static ASTVariableDeclaratorId newVarId(String image) { + ASTVariableDeclaratorId varid = new ASTVariableDeclaratorId(JavaParserImplTreeConstants.JJTVARIABLEDECLARATORID); + varid.setImage(image); + return varid; + } + + + /** + * Creates a fake method name declaration for built-in methods from Java + * like the Enum Method "valueOf". + * + * @param methodName the method name + * @param parameterTypes the reference types of each parameter of the method + * + * @return a method name declaration + */ + public static ASTMethodDeclarator createBuiltInMethodDeclaration(final String methodName, final String... parameterTypes) { + ASTMethodDeclaration methodDeclaration = new ASTMethodDeclaration(0); + methodDeclaration.setPublic(true); + + ASTMethodDeclarator methodDeclarator = new ASTMethodDeclarator(0); + methodDeclarator.setImage(methodName); + + ASTFormalParameters formalParameters = new ASTFormalParameters(0); + methodDeclaration.addChild(methodDeclarator, 0); + methodDeclarator.addChild(formalParameters, 0); + + /* + * jjtAddChild resizes it's child node list according to known indexes. + * Going backwards makes sure the first time it gets the right size avoiding copies. + */ + for (int i = parameterTypes.length - 1; i >= 0; i--) { + ASTFormalParameter formalParameter = new ASTFormalParameter(0); + formalParameters.addChild(formalParameter, i); + + ASTVariableDeclaratorId variableDeclaratorId = new ASTVariableDeclaratorId(0); + variableDeclaratorId.setImage("arg" + i); + formalParameter.addChild(variableDeclaratorId, 1); + + ASTType type = new ASTType(0); + formalParameter.addChild(type, 0); + + if (PRIMITIVE_TYPES.contains(parameterTypes[i])) { + ASTPrimitiveType primitiveType = new ASTPrimitiveType(0); + primitiveType.setImage(parameterTypes[i]); + type.addChild(primitiveType, 0); + } else { + ASTReferenceType referenceType = new ASTReferenceType(0); + type.addChild(referenceType, 0); + + // TODO : this could actually be a primitive array... + ASTClassOrInterfaceType classOrInterfaceType = new ASTClassOrInterfaceType(0); + classOrInterfaceType.setImage(parameterTypes[i]); + referenceType.addChild(classOrInterfaceType, 0); + } + } + + return methodDeclarator; + } + + + @Deprecated + public static ASTPrimaryPrefix newThisSuperPrefix(String image, boolean isThis) { + ASTPrimaryPrefix prefix = new ASTPrimaryPrefix(JavaParserImplTreeConstants.JJTPRIMARYPREFIX); + if (isThis) { + prefix.setUsesThisModifier(); + } else { + prefix.setUsesSuperModifier(); + } + ASTName name = new ASTName(JavaParserImplTreeConstants.JJTNAME); + name.setImage(image); + prefix.addChild(name, 0); + return prefix; + } public static JavaccTokenDocument javaTokenDoc(String fullText) { return new JavaTokenDocument(fullText); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaNode.java index b65a1ec9f8..f39565a941 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaNode.java @@ -8,9 +8,7 @@ package net.sourceforge.pmd.lang.java.ast; import org.checkerframework.checker.nullness.qual.NonNull; import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.ast.NodeStream; -import net.sourceforge.pmd.lang.ast.TextAvailableNode; -import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; +import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeNode; import net.sourceforge.pmd.lang.symboltable.Scope; import net.sourceforge.pmd.lang.symboltable.ScopedNode; @@ -18,7 +16,7 @@ import net.sourceforge.pmd.lang.symboltable.ScopedNode; /** * Root interface for all Nodes of the Java AST. */ -public interface JavaNode extends ScopedNode, TextAvailableNode { +public interface JavaNode extends ScopedNode, JjtreeNode { /** * Calls back the visitor's visit method corresponding to the runtime type of this Node. @@ -29,24 +27,6 @@ public interface JavaNode extends ScopedNode, TextAvailableNode { Object jjtAccept(JavaParserVisitor visitor, Object data); - /** - * Dispatches the given visitor to the children of this node. This is the default implementation - * of {@link JavaParserVisitor#visit(JavaNode, Object)}, to which all other default - * implementations for visit methods delegate. Unless visit methods are overridden without calling - * {@code super.visit}, the visitor performs a depth-first tree walk. - * - *

    The return value of the visit methods called on children are ignored. - * - * @param visitor Visitor to dispatch - * @param data Visit data - * - * @deprecated This method is not useful, the logic for combining - * children values should be present on the visitor, not the node - */ - @Deprecated - Object childrenAccept(JavaParserVisitor visitor, Object data); - - /** * Calls back the visitor's visit method corresponding to the runtime type of this Node. * @@ -57,42 +37,10 @@ public interface JavaNode extends ScopedNode, TextAvailableNode { void jjtAccept(SideEffectingVisitor visitor, T data); - /** - * Dispatches the given visitor to the children of this node. This is the default implementation - * of {@link SideEffectingVisitor#visit(JavaNode, Object)}, to which all other default - * implementations for visit methods delegate. Unless visit methods are overridden without calling - * {@code super.visit}, the visitor performs a depth-first tree walk. - * - * @param visitor Visitor to dispatch - * @param data Visit data - * @param Type of data - */ - void childrenAccept(SideEffectingVisitor visitor, T data); - - - @Override - JavaNode getChild(int index); - - - @Override - JavaNode getParent(); - - - @Override - NodeStream children(); - - @InternalApi @Deprecated void setScope(Scope scope); - - JavaccToken jjtGetFirstToken(); - - - JavaccToken jjtGetLastToken(); - - @Override @NonNull ASTCompilationUnit getRoot(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavadocElement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavadocElement.java index 54f489499f..a233a8f225 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavadocElement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavadocElement.java @@ -4,25 +4,50 @@ package net.sourceforge.pmd.lang.java.ast; -import net.sourceforge.pmd.lang.ast.AbstractNode; +import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.java.javadoc.JavadocTag; -public class JavadocElement extends AbstractNode { +public class JavadocElement extends Comment { + + private final int beginLine; + private final int endLine; + private final int beginColumn; + private final int endColumn; private final JavadocTag tag; - public JavadocElement(int theBeginLine, int theEndLine, int theBeginColumn, int theEndColumn, JavadocTag theTag) { - super(-1, theBeginLine, theEndLine, theBeginColumn, theEndColumn); - - tag = theTag; + public JavadocElement(JavaccToken t, int theBeginLine, int theEndLine, int theBeginColumn, int theEndColumn, JavadocTag theTag) { + super(t); + this.tag = theTag; + this.beginLine = theBeginLine; + this.endLine = theEndLine; + this.beginColumn = theBeginColumn; + this.endColumn = theEndColumn; } public JavadocTag tag() { return tag; } + @Override + public int getBeginLine() { + return beginLine; + } + @Override + public int getEndColumn() { + return endColumn; + } + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } @Override public String getXPathNodeName() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/VariableAccessVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/VariableAccessVisitor.java index 775ebbf2f0..1883a5b9e7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/VariableAccessVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/VariableAccessVisitor.java @@ -54,7 +54,7 @@ public class VariableAccessVisitor extends JavaParserVisitorAdapter { private void computeNow(Node node) { - DataFlowNode inode = node.getDataFlowNode(); + DataFlowNode inode = DataFlowNode.get(node); List undefinitions = markUsages(inode); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java index 70c06df799..48a5ee69e6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java @@ -24,9 +24,9 @@ public abstract class AbstractJUnitRule extends AbstractJavaRule { protected static final String JUNIT4_CLASS_NAME = "org.junit.Test"; protected static final String JUNIT5_CLASS_NAME = "org.junit.jupiter.api.Test"; - private boolean isJUnit3Class; - private boolean isJUnit4Class; - private boolean isJUnit5Class; + protected boolean isJUnit3Class; + protected boolean isJUnit4Class; + protected boolean isJUnit5Class; @Override public Object visit(ASTCompilationUnit node, Object data) { @@ -105,10 +105,10 @@ public abstract class AbstractJUnitRule extends AbstractJavaRule { if (((ASTClassOrInterfaceType) extendsList.getChild(0)).getImage().endsWith("TestCase")) { return true; } - String className = cid.getImage(); + String className = cid.getSimpleName(); return className.endsWith("Test"); } else if (hasImports(node, JUNIT3_CLASS_NAME)) { - return cid.getImage().endsWith("Test"); + return cid.getSimpleName().endsWith("Test"); } return false; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java index 3482bde1d0..0f6e66eabf 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningLoopVariablesRule.java @@ -13,7 +13,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import net.sourceforge.pmd.lang.ast.AbstractNode; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; import net.sourceforge.pmd.lang.java.ast.ASTBlock; @@ -294,7 +293,7 @@ public class AvoidReassigningLoopVariablesRule extends AbstractOptimizationRule /** * Add a violation, if the node image is one of the loop variables. */ - private void checkVariable(Object data, Set loopVariables, AbstractNode node) { + private void checkVariable(Object data, Set loopVariables, Node node) { if (node != null && loopVariables.contains(node.getImage())) { addViolation(data, node, node.getImage()); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertRule.java index 2952d14069..9f1dd81212 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertRule.java @@ -61,6 +61,7 @@ public class JUnitTestsShouldIncludeAssertRule extends AbstractJUnitRule { if (n instanceof ASTStatementExpression) { if (isExpectStatement((ASTStatementExpression) n, expectables) || isAssertOrFailStatement((ASTStatementExpression) n) + || isHamcrestAssert((ASTStatementExpression) n) || isVerifyStatement((ASTStatementExpression) n) || isSoftAssertionStatement((ASTStatementExpression) n, variables)) { return true; @@ -134,43 +135,42 @@ public class JUnitTestsShouldIncludeAssertRule extends AbstractJUnitRule { return false; } - /** - * Tells if the expression is an assert statement or not. - */ - private boolean isAssertOrFailStatement(ASTStatementExpression expression) { + private String getMethodCallNameOrNull(ASTStatementExpression expression) { if (expression != null) { ASTPrimaryExpression pe = expression.getFirstChildOfType(ASTPrimaryExpression.class); if (pe != null) { Node name = pe.getFirstDescendantOfType(ASTName.class); if (name != null) { - String img = name.getImage(); - if (img != null && (img.startsWith("assert") || img.startsWith("fail") - || img.startsWith("Assert.assert") || img.startsWith("Assert.fail"))) { - return true; - } + return name.getImage(); } } } - return false; + return null; + } + + /** + * Tells if the expression is an Hamcrest assert + */ + private boolean isHamcrestAssert(ASTStatementExpression expression) { + String img = getMethodCallNameOrNull(expression); + return "assertThat".equals(img) || "MatcherAssert.assertThat".equals(img); + } + + /** + * Tells if the expression is an assert statement or not. + */ + private boolean isAssertOrFailStatement(ASTStatementExpression expression) { + String img = getMethodCallNameOrNull(expression); + return img != null && (img.startsWith("assert") || img.startsWith("fail") + || img.startsWith("Assert.assert") || img.startsWith("Assert.fail")); } /** * Tells if the expression is verify statement or not */ private boolean isVerifyStatement(ASTStatementExpression expression) { - if (expression != null) { - ASTPrimaryExpression pe = expression.getFirstChildOfType(ASTPrimaryExpression.class); - if (pe != null) { - Node name = pe.getFirstDescendantOfType(ASTName.class); - if (name != null) { - String img = name.getImage(); - if (img != null && (img.startsWith("verify") || img.startsWith("Mockito.verify"))) { - return true; - } - } - } - } - return false; + String img = getMethodCallNameOrNull(expression); + return img != null && (img.startsWith("verify") || img.startsWith("Mockito.verify")); } private boolean isExpectStatement(ASTStatementExpression expression, diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LiteralsFirstInComparisonsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LiteralsFirstInComparisonsRule.java new file mode 100644 index 0000000000..667240f5d8 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LiteralsFirstInComparisonsRule.java @@ -0,0 +1,127 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.java.ast.ASTArguments; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression; +import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; +import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; +import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + +public class LiteralsFirstInComparisonsRule extends AbstractJavaRule { + + private static final String[] COMPARISON_OPS = {".equals", ".equalsIgnoreCase", ".compareTo", ".compareToIgnoreCase", ".contentEquals"}; + + public LiteralsFirstInComparisonsRule() { + addRuleChainVisit(ASTPrimaryExpression.class); + } + + @Override + public Object visit(ASTPrimaryExpression node, Object data) { + ASTPrimaryPrefix primaryPrefix = node.getFirstChildOfType(ASTPrimaryPrefix.class); + ASTPrimarySuffix primarySuffix = node.getFirstChildOfType(ASTPrimarySuffix.class); + if (primaryPrefix != null && primarySuffix != null) { + ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class); + if (name == null || isIrrelevantImage(name.getImage())) { + return data; + } + if (!isSingleStringLiteralArgument(primarySuffix)) { + return data; + } + if (isWithinNullComparison(node)) { + return data; + } + addViolation(data, node); + } + return node; + } + + private boolean isIrrelevantImage(String image) { + for (String comparisonOp : COMPARISON_OPS) { + if (image.endsWith(comparisonOp)) { + return false; + } + } + return true; + } + + private boolean isWithinNullComparison(ASTPrimaryExpression node) { + for (ASTExpression parentExpr : node.getParentsOfType(ASTExpression.class)) { + if (isComparisonWithNull(parentExpr, "==", ASTConditionalOrExpression.class) + || isComparisonWithNull(parentExpr, "!=", ASTConditionalAndExpression.class)) { + return true; + } + } + return false; + } + + /* + * Expression/ConditionalAndExpression//EqualityExpression(@Image='!=']//NullLiteral + * Expression/ConditionalOrExpression//EqualityExpression(@Image='==']//NullLiteral + */ + private boolean isComparisonWithNull(ASTExpression parentExpr, String equalOperator, Class condition) { + Node condExpr = null; + ASTEqualityExpression eqExpr = null; + if (parentExpr != null) { + condExpr = parentExpr.getFirstChildOfType(condition); + } + if (condExpr != null) { + eqExpr = condExpr.getFirstDescendantOfType(ASTEqualityExpression.class); + } + if (eqExpr != null) { + return eqExpr.hasImageEqualTo(equalOperator) && eqExpr.hasDescendantOfType(ASTNullLiteral.class); + } + return false; + } + + /* + * This corresponds to the following XPath expression: + * (../PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral= true()]) + * and + * ( count(../PrimarySuffix/Arguments/ArgumentList/Expression) = 1 ) + */ + private boolean isSingleStringLiteralArgument(ASTPrimarySuffix primarySuffix) { + if (!primarySuffix.isArguments() || primarySuffix.getArgumentCount() != 1) { + return false; + } + Node node = primarySuffix; + node = node.getFirstChildOfType(ASTArguments.class); + if (node != null) { + node = node.getFirstChildOfType(ASTArgumentList.class); + if (node.getNumChildren() != 1) { + return false; + } + } + if (node != null) { + node = node.getFirstChildOfType(ASTExpression.class); + } + if (node != null) { + node = node.getFirstChildOfType(ASTPrimaryExpression.class); + } + if (node != null) { + node = node.getFirstChildOfType(ASTPrimaryPrefix.class); + } + if (node != null) { + node = node.getFirstChildOfType(ASTLiteral.class); + } + if (node != null) { + ASTLiteral literal = (ASTLiteral) node; + if (literal.isStringLiteral()) { + return true; + } + } + return false; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsRule.java index 09cb006aef..abd54134bf 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsRule.java @@ -4,17 +4,19 @@ package net.sourceforge.pmd.lang.java.rule.codestyle; +import static net.sourceforge.pmd.lang.ast.NodeStream.asInstanceOf; + import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; import net.sourceforge.pmd.properties.PropertyBuilder.RegexPropertyBuilder; import net.sourceforge.pmd.properties.PropertyDescriptor; @@ -50,7 +52,7 @@ public class MethodNamingConventionsRule extends AbstractNamingConventionRule(); couplingCount = 0; - Object returnObj = cu.childrenAccept(this, data); + for (JavaNode child : cu.children()) { + child.jjtAccept(this, data); + } if (couplingCount > getProperty(THRESHOLD_DESCRIPTOR)) { addViolation(data, cu, "A value of " + couplingCount + " may denote a high amount of coupling within the class"); } - return returnObj; + return data; } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java index 532aa4961e..2ec9076391 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java @@ -225,7 +225,7 @@ public class CommentRequiredRule extends AbstractCommentRule { && field.isStatic() && field.isFinal() && field.isArray() - && "ObjectStreamField".equals(field.jjtGetFirstToken().getImage()); // .getType() returns null + && "ObjectStreamField".equals(field.getFirstToken().getImage()); // .getType() returns null } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableRule.java index 0566a1ee45..495e9bf31c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableRule.java @@ -91,8 +91,8 @@ public class CloneMethodMustImplementCloneableRule extends AbstractJavaRule { // Is the clone method just throwing CloneNotSupportedException? final ASTClassOrInterfaceDeclaration classOrInterface = node.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); - if (classOrInterface != null && //Don't analyze enums, which cannot subclass clone() - (node.isFinal() || classOrInterface.isFinal())) { + if (classOrInterface != null //Don't analyze enums, which cannot subclass clone() + && (node.isFinal() || classOrInterface.isFinal())) { if (node.findDescendantsOfType(ASTBlock.class).size() == 1) { final List blocks = node.findDescendantsOfType(ASTBlockStatement.class); if (blocks.size() == 1) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisRule.java index cb41b65f40..94373f6149 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisRule.java @@ -85,7 +85,7 @@ public class DataflowAnomalyAnalysisRule extends AbstractJavaRule implements Exe rc = (RuleContext) data; daaRuleViolations = new ArrayList<>(); - final DataFlowNode node = methodDeclaration.getDataFlowNode().getFlow().get(0); + final DataFlowNode node = DataFlowNode.get(methodDeclaration).getFlow().get(0); final DAAPathFinder pathFinder = new DAAPathFinder(node, this, getProperty(MAX_PATH_DESCRIPTOR)); pathFinder.run(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingRule.java index 8fd2249133..573b5a1c6d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingRule.java @@ -12,6 +12,10 @@ public class JUnitSpellingRule extends AbstractJUnitRule { @Override public Object visit(ASTMethodDeclaration node, Object data) { + if (isJUnit5Class || isJUnit4Class) { + return super.visit(node, data); + } + if (node.getArity() != 0) { return super.visit(node, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerRule.java index 96647cea68..2c7bcd023d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerRule.java @@ -49,7 +49,9 @@ public class MoreThanOneLoggerRule extends AbstractJavaRule { stack.push(count); count = NumericConstants.ZERO; - node.childrenAccept(this, data); + for (JavaNode child : node.children()) { + child.jjtAccept(this, data); + } if (count > 1) { addViolation(data, node); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassScope.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassScope.java index a409c76d88..a9534b22a3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassScope.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassScope.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.lang.java.symboltable; +import static net.sourceforge.pmd.lang.java.ast.InternalApiBridge.createBuiltInMethodDeclaration; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -21,19 +23,13 @@ import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; import net.sourceforge.pmd.lang.java.ast.ASTImplementsList; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; -import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType; -import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; -import net.sourceforge.pmd.lang.java.ast.ASTType; import net.sourceforge.pmd.lang.java.ast.ASTTypeParameter; import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.symboltable.Applier; import net.sourceforge.pmd.lang.symboltable.ImageFinderFunction; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; @@ -46,20 +42,6 @@ import net.sourceforge.pmd.lang.symboltable.Scope; */ public class ClassScope extends AbstractJavaScope { - private static final Set PRIMITIVE_TYPES; - - static { - PRIMITIVE_TYPES = new HashSet<>(); - PRIMITIVE_TYPES.add("boolean"); - PRIMITIVE_TYPES.add("char"); - PRIMITIVE_TYPES.add("byte"); - PRIMITIVE_TYPES.add("short"); - PRIMITIVE_TYPES.add("int"); - PRIMITIVE_TYPES.add("long"); - PRIMITIVE_TYPES.add("float"); - PRIMITIVE_TYPES.add("double"); - } - // FIXME - this breaks given sufficiently nested code private static ThreadLocal anonymousInnerClassCounter = new ThreadLocal() { @Override @@ -191,7 +173,9 @@ public class ClassScope extends AbstractJavaScope { matchMethodDeclaration(occurrence, methodDeclarations.keySet(), hasAuxclasspath, result); if (isEnum && "valueOf".equals(occurrence.getImage())) { - result.add(createBuiltInMethodDeclaration("valueOf", "String")); + ASTMethodDeclarator declarator = createBuiltInMethodDeclaration("valueOf", "String"); + declarator.setScope(this); + result.add(new MethodNameDeclaration(declarator)); } if (result.isEmpty()) { @@ -282,73 +266,6 @@ public class ClassScope extends AbstractJavaScope { } } - /** - * Creates a fake method name declaration for built-in methods from Java - * like the Enum Method "valueOf". - * - * @param methodName - * the method name - * @param parameterTypes - * the reference types of each parameter of the method - * @return a method name declaration - */ - private MethodNameDeclaration createBuiltInMethodDeclaration(final String methodName, - final String... parameterTypes) { - ASTMethodDeclaration methodDeclaration = new ASTMethodDeclaration(0); - methodDeclaration.setPublic(true); - methodDeclaration.setScope(this); - - ASTMethodDeclarator methodDeclarator = new ASTMethodDeclarator(0); - methodDeclarator.setImage(methodName); - methodDeclarator.setScope(this); - - ASTFormalParameters formalParameters = new ASTFormalParameters(0); - formalParameters.setScope(this); - - methodDeclaration.jjtAddChild(methodDeclarator, 0); - methodDeclarator.jjtSetParent(methodDeclaration); - methodDeclarator.jjtAddChild(formalParameters, 0); - formalParameters.jjtSetParent(methodDeclarator); - - /* - * jjtAddChild resizes it's child node list according to known indexes. - * Going backwards makes sure the first time it gets the right size avoiding copies. - */ - for (int i = parameterTypes.length - 1; i >= 0; i--) { - ASTFormalParameter formalParameter = new ASTFormalParameter(0); - formalParameters.jjtAddChild(formalParameter, i); - formalParameter.jjtSetParent(formalParameters); - - ASTVariableDeclaratorId variableDeclaratorId = new ASTVariableDeclaratorId(0); - variableDeclaratorId.setImage("arg" + i); - formalParameter.jjtAddChild(variableDeclaratorId, 1); - variableDeclaratorId.jjtSetParent(formalParameter); - - ASTType type = new ASTType(0); - formalParameter.jjtAddChild(type, 0); - type.jjtSetParent(formalParameter); - - if (PRIMITIVE_TYPES.contains(parameterTypes[i])) { - ASTPrimitiveType primitiveType = new ASTPrimitiveType(0); - primitiveType.setImage(parameterTypes[i]); - type.jjtAddChild(primitiveType, 0); - primitiveType.jjtSetParent(type); - } else { - ASTReferenceType referenceType = new ASTReferenceType(0); - type.jjtAddChild(referenceType, 0); - referenceType.jjtSetParent(type); - - // TODO : this could actually be a primitive array... - ASTClassOrInterfaceType classOrInterfaceType = new ASTClassOrInterfaceType(0); - classOrInterfaceType.setImage(parameterTypes[i]); - referenceType.jjtAddChild(classOrInterfaceType, 0); - classOrInterfaceType.jjtSetParent(referenceType); - } - } - - return new MethodNameDeclaration(methodDeclarator); - } - /** * Provide a list of types of the parameters of the given method * declaration. The types are simple type images. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java index f49d5a3be9..f33c38b4e1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java @@ -897,7 +897,7 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter implements Nulla // skip children which already have their type assigned if (currentChild.getType() == null) { // Last token, because if 'this' is a Suffix, it'll have tokens '.' and 'this' - if (currentChild.jjtGetLastToken().kind == JavaTokenKinds.THIS) { + if (currentChild.getLastToken().kind == JavaTokenKinds.THIS) { if (previousChild != null) { // Qualified 'this' expression currentChild.setTypeDefinition(previousChild.getTypeDefinition()); @@ -911,7 +911,7 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter implements Nulla } // Last token, because if 'super' is a Suffix, it'll have tokens '.' and 'super' - } else if (currentChild.jjtGetLastToken().kind == JavaTokenKinds.SUPER) { + } else if (currentChild.getLastToken().kind == JavaTokenKinds.SUPER) { if (previousChild != null) { // Qualified 'super' expression // anonymous classes can't have qualified super expression, thus @@ -929,7 +929,7 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter implements Nulla String currentChildImage = currentChild.getImage(); if (currentChildImage == null) { // this.foo(); foo would be in a Suffix and would have a null image - currentChildImage = currentChild.jjtGetLastToken().getImage(); + currentChildImage = currentChild.getLastToken().getImage(); } ASTArguments astArguments = nextChild != null diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/GetCommentOnFunction.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/GetCommentOnFunction.java index 9e79e5328f..2f1dae6f88 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/GetCommentOnFunction.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/GetCommentOnFunction.java @@ -13,7 +13,6 @@ import org.jaxen.SimpleFunctionContext; import org.jaxen.XPathFunctionContext; import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.ast.AbstractNode; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.Comment; @@ -41,15 +40,13 @@ public class GetCommentOnFunction implements Function { return Boolean.FALSE; } Node n = (Node) context.getNodeSet().get(0); - if (n instanceof AbstractNode) { - int codeBeginLine = ((AbstractNode) n).getBeginLine(); - int codeEndLine = ((AbstractNode) n).getEndLine(); + int codeBeginLine = n.getBeginLine(); + int codeEndLine = n.getEndLine(); - List commentList = ((AbstractNode) n).getFirstParentOfType(ASTCompilationUnit.class).getComments(); - for (Comment comment : commentList) { - if (comment.getBeginLine() == codeBeginLine || comment.getEndLine() == codeEndLine) { - return comment.getImage(); - } + List commentList = n.getFirstParentOfType(ASTCompilationUnit.class).getComments(); + for (Comment comment : commentList) { + if (comment.getBeginLine() == codeBeginLine || comment.getEndLine() == codeEndLine) { + return comment.getImage(); } } return Boolean.FALSE; diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml index c9dfa04ff7..6ce8af8d8b 100644 --- a/pmd-java/src/main/resources/category/java/bestpractices.xml +++ b/pmd-java/src/main/resources/category/java/bestpractices.xml @@ -875,6 +875,41 @@ public class MyTest { + + + Position literals first in all String comparisons, if the second argument is null then NullPointerExceptions + can be avoided, they will just return false. Note that switching literal positions for compareTo and + compareToIgnoreCase may change the result, see examples. + + 3 + + 0); // should be: "bar".compareTo(x) < 0 + } + boolean bar(String x) { + return (x.compareToIgnoreCase("bar") > 0); // should be: "bar".compareToIgnoreCase(x) < 0 + } + boolean bar(String x) { + return x.contentEquals("bar"); // should be "bar".contentEquals(x) + } +} +]]> + + + -Some JUnit framework methods are easy to misspell. + In JUnit 3, the setUp method is used to set up all data entities required in running tests. + The tearDown method is used to clean up all data entities required in running tests. + You should not misspell method name if you want your test to set up and clean up everything correctly. 3 diff --git a/pmd-java/src/main/resources/rulesets/java/quickstart.xml b/pmd-java/src/main/resources/rulesets/java/quickstart.xml index 57a9f973d7..5b9d911b7a 100644 --- a/pmd-java/src/main/resources/rulesets/java/quickstart.xml +++ b/pmd-java/src/main/resources/rulesets/java/quickstart.xml @@ -30,12 +30,11 @@ + - - diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java index 7c217d0a25..e8a2bb5506 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java @@ -35,25 +35,26 @@ public class LanguageVersionTest extends AbstractLanguageVersionTest { { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "1.8", LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("1.8"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "9", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("9"), }, + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("9"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "10", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("10"), }, + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("10"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "11", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("11"), }, + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("11"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "12", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("12"), }, + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("12"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "12-preview", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("12-preview"), }, + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("12-preview"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "13", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("13"), }, + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("13"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "13-preview", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("13-preview"), }, + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("13-preview"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "14", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("14"), }, + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("14"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "14-preview", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("14-preview"), }, + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("14-preview"), }, // this one won't be found: case sensitive! - { "JAVA", "JAVA", "1.7", null, }, }); + { "JAVA", "JAVA", "1.7", null, }, + }); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTBlockStatementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTBlockStatementTest.java index b0e682d49a..2dafc666f7 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTBlockStatementTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTBlockStatementTest.java @@ -14,14 +14,14 @@ public class ASTBlockStatementTest { @Test public void testIsAllocation() { ASTBlockStatement bs = new ASTBlockStatement(0); - bs.jjtAddChild(new ASTAllocationExpression(1), 0); + bs.addChild(new ASTAllocationExpression(1), 0); assertTrue(bs.isAllocation()); } @Test public void testIsAllocation2() { ASTBlockStatement bs = new ASTBlockStatement(0); - bs.jjtAddChild(new ASTAssertStatement(1), 0); + bs.addChild(new ASTAssertStatement(1), 0); assertFalse(bs.isAllocation()); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclarationTest.java index 34dd5438f9..e25a9b6a6c 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclarationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclarationTest.java @@ -77,9 +77,9 @@ public class ASTFieldDeclarationTest extends BaseParserTest { ASTType t = new ASTType(id++); ASTVariableDeclarator decl = new ASTVariableDeclarator(id++); ASTVariableDeclaratorId declid = new ASTVariableDeclaratorId(id++); - n.jjtAddChild(t, 0); - t.jjtAddChild(decl, 0); - decl.jjtAddChild(declid, 0); + n.addChild(t, 0); + t.addChild(decl, 0); + decl.addChild(declid, 0); declid.setImage("foo"); assertEquals("foo", n.getVariableName()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java index 673f893777..19d289d8fc 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java @@ -15,6 +15,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.lang.java.JavaParsingHelper; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; @@ -31,6 +32,14 @@ public class ParserCornersTest { @Rule public ExpectedException expect = ExpectedException.none(); + + @Test + public void testInvalidUnicodeEscape() { + expect.expect(TokenMgrError.class); // previously Error + expect.expectMessage("Lexical error at line 1, column 2. Encountered: Invalid unicode escape"); + java.parse("\\u00k0"); + } + /** * #1107 PMD 5.0.4 couldn't parse call of parent outer java class method * from inner class. diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java index 0f9e3e15e5..91eebd6cd9 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java @@ -101,15 +101,15 @@ public class SimpleNodeTest extends BaseParserTest { @Test public void testFindDescendantsOfType() { ASTBlock block = new ASTBlock(2); - block.jjtAddChild(new ASTReturnStatement(1), 0); + block.addChild(new ASTReturnStatement(1), 0); assertEquals(1, block.findDescendantsOfType(ASTReturnStatement.class).size()); } @Test public void testFindDescendantsOfTypeMultiple() { ASTBlock block = new ASTBlock(1); - block.jjtAddChild(new ASTBlockStatement(2), 0); - block.jjtAddChild(new ASTBlockStatement(3), 1); + block.addChild(new ASTBlockStatement(2), 0); + block.addChild(new ASTBlockStatement(3), 1); List nodes = block.findDescendantsOfType(ASTBlockStatement.class); assertEquals(2, nodes.size()); } @@ -118,8 +118,8 @@ public class SimpleNodeTest extends BaseParserTest { public void testFindDescendantsOfTypeRecurse() { ASTBlock block = new ASTBlock(1); ASTBlock childBlock = new ASTBlock(2); - block.jjtAddChild(childBlock, 0); - childBlock.jjtAddChild(new ASTMethodDeclaration(3), 0); + block.addChild(childBlock, 0); + childBlock.addChild(new ASTMethodDeclaration(3), 0); List nodes = block.findDescendantsOfType(ASTMethodDeclaration.class); assertEquals(1, nodes.size()); } @@ -128,8 +128,8 @@ public class SimpleNodeTest extends BaseParserTest { public void testGetFirstChild() { ASTBlock block = new ASTBlock(1); ASTStatement x = new ASTStatement(2); - block.jjtAddChild(x, 0); - block.jjtAddChild(new ASTStatement(3), 1); + block.addChild(x, 0); + block.addChild(new ASTStatement(3), 1); Node n = block.getFirstDescendantOfType(ASTStatement.class); assertNotNull(n); @@ -142,9 +142,9 @@ public class SimpleNodeTest extends BaseParserTest { ASTBlock block = new ASTBlock(1); ASTStatement x = new ASTStatement(2); ASTAssignmentOperator x1 = new ASTAssignmentOperator(4); - x.jjtAddChild(x1, 0); - block.jjtAddChild(x, 0); - block.jjtAddChild(new ASTStatement(3), 1); + x.addChild(x1, 0); + block.addChild(x, 0); + block.addChild(new ASTStatement(3), 1); Node n = block.getFirstDescendantOfType(ASTAssignmentOperator.class); assertNotNull(n); @@ -159,10 +159,10 @@ public class SimpleNodeTest extends BaseParserTest { ASTAssignmentOperator x1 = new ASTAssignmentOperator(4); ASTName x2 = new ASTName(5); - x.jjtAddChild(x1, 0); - x1.jjtAddChild(x2, 0); - block.jjtAddChild(x, 0); - block.jjtAddChild(new ASTStatement(3), 1); + x.addChild(x1, 0); + x1.addChild(x2, 0); + block.addChild(x, 0); + block.addChild(new ASTStatement(3), 1); Node n = block.getFirstDescendantOfType(ASTName.class); assertNotNull(n); @@ -258,17 +258,6 @@ public class SimpleNodeTest extends BaseParserTest { assertFalse(c.hasDescendantMatchingXPath("//MethodDeclaration")); } - @Test - public void testUserData() { - ASTClassOrInterfaceDeclaration c = java.getNodes(ASTClassOrInterfaceDeclaration.class, HAS_EXPLICIT_EXTENDS) - .iterator().next(); - assertNull(c.getUserData()); - c.setUserData("foo"); - assertEquals("foo", c.getUserData()); - c.setUserData(null); - assertNull(c.getUserData()); - } - private void verifyNode(Node node, int beginLine, int beginCol, int endLine, int endCol) { assertEquals("Unexpected beginning line: ", beginLine, node.getBeginLine()); assertEquals("Unexpected beginning column: ", beginCol, node.getBeginColumn()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/AcceptanceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/AcceptanceTest.java index ed5f9e89dd..9f0683348f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/AcceptanceTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/AcceptanceTest.java @@ -39,7 +39,7 @@ public class AcceptanceTest extends BaseNonParserTest { private boolean check(int[][] array, List methodNodes) { for (int i = 0; i < methodNodes.size(); i++) { ASTMethodDeclarator decl = methodNodes.get(i); - DataFlowNode inode = decl.getDataFlowNode(); + DataFlowNode inode = DataFlowNode.get(decl); for (int j = 0; j < inode.getChildren().size(); j++) { DataFlowNode child = inode.getChildren().get(j); if (array[i][j] != child.getIndex() - 1) { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/DAAPathFinderTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/DAAPathFinderTest.java index 2835f27d2b..c1d6aa3633 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/DAAPathFinderTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/DAAPathFinderTest.java @@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.java.dfa; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.dfa.DataFlowNode; import net.sourceforge.pmd.lang.dfa.pathfinder.CurrentPath; import net.sourceforge.pmd.lang.dfa.pathfinder.DAAPathFinder; import net.sourceforge.pmd.lang.dfa.pathfinder.Executable; @@ -18,7 +19,7 @@ public class DAAPathFinderTest { @Test public void testTwoUpdateDefs() { ASTMethodDeclarator meth = JavaParsingHelper.WITH_PROCESSING.getNodes(ASTMethodDeclarator.class, TWO_UPDATE_DEFS).get(0); - DAAPathFinder a = new DAAPathFinder(meth.getDataFlowNode().getFlow().get(0), new Executable() { + DAAPathFinder a = new DAAPathFinder(DataFlowNode.get(meth).getFlow().get(0), new Executable() { @Override public void execute(CurrentPath path) { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/GeneralFiddlingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/GeneralFiddlingTest.java index 854bad3f1f..437a9927e5 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/GeneralFiddlingTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/GeneralFiddlingTest.java @@ -33,7 +33,7 @@ public class GeneralFiddlingTest extends BaseNonParserTest { public void test1() { ASTCompilationUnit acu = java.parse(TEST1); ASTMethodDeclarator meth = acu.findDescendantsOfType(ASTMethodDeclarator.class).get(0); - DataFlowNode n = meth.getDataFlowNode(); + DataFlowNode n = DataFlowNode.get(meth); List f = n.getFlow(); assertEquals(6, f.size()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/StatementAndBraceFinderTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/StatementAndBraceFinderTest.java index ce9d9d37eb..99606982f0 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/StatementAndBraceFinderTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/dfa/StatementAndBraceFinderTest.java @@ -24,23 +24,23 @@ public class StatementAndBraceFinderTest extends BaseNonParserTest { @Test public void testStatementExpressionParentChildLinks() { ASTStatementExpression se = getOrderedNodes(ASTStatementExpression.class, TEST1).get(0); - ASTMethodDeclaration seParent = (ASTMethodDeclaration) se.getDataFlowNode().getParents().get(0).getNode(); - assertEquals(se, seParent.getDataFlowNode().getChildren().get(0).getNode()); - assertEquals(seParent, se.getDataFlowNode().getParents().get(0).getNode()); + ASTMethodDeclaration seParent = (ASTMethodDeclaration) DataFlowNode.get(se).getParents().get(0).getNode(); + assertEquals(se, DataFlowNode.get(seParent).getChildren().get(0).getNode()); + assertEquals(seParent, DataFlowNode.get(se).getParents().get(0).getNode()); } @Test public void testVariableDeclaratorParentChildLinks() { ASTVariableDeclarator vd = getOrderedNodes(ASTVariableDeclarator.class, TEST2).get(0); - ASTMethodDeclaration vdParent = (ASTMethodDeclaration) vd.getDataFlowNode().getParents().get(0).getNode(); - assertEquals(vd, vdParent.getDataFlowNode().getChildren().get(0).getNode()); - assertEquals(vdParent, vd.getDataFlowNode().getParents().get(0).getNode()); + ASTMethodDeclaration vdParent = (ASTMethodDeclaration) DataFlowNode.get(vd).getParents().get(0).getNode(); + assertEquals(vd, DataFlowNode.get(vdParent).getChildren().get(0).getNode()); + assertEquals(vdParent, DataFlowNode.get(vd).getParents().get(0).getNode()); } @Test public void testIfStmtHasCorrectTypes() { ASTExpression exp = getOrderedNodes(ASTExpression.class, TEST3).get(0); - DataFlowNode dfn = exp.getDataFlowNode().getFlow().get(2); + DataFlowNode dfn = DataFlowNode.get(exp).getFlow().get(2); assertTrue(dfn.isType(NodeType.IF_EXPR)); assertTrue(dfn.isType(NodeType.IF_LAST_STATEMENT_WITHOUT_ELSE)); } @@ -48,7 +48,7 @@ public class StatementAndBraceFinderTest extends BaseNonParserTest { @Test public void testWhileStmtHasCorrectTypes() { ASTExpression exp = getOrderedNodes(ASTExpression.class, TEST4).get(0); - DataFlowNode dfn = exp.getDataFlowNode().getFlow().get(2); + DataFlowNode dfn = DataFlowNode.get(exp).getFlow().get(2); assertTrue(dfn.isType(NodeType.WHILE_EXPR)); assertTrue(dfn.isType(NodeType.WHILE_LAST_STATEMENT)); } @@ -56,11 +56,11 @@ public class StatementAndBraceFinderTest extends BaseNonParserTest { @Test public void testForStmtHasCorrectTypes() { ASTExpression exp = getOrderedNodes(ASTExpression.class, TEST5).get(0); - DataFlowNode dfn = exp.getDataFlowNode().getFlow().get(2); + DataFlowNode dfn = DataFlowNode.get(exp).getFlow().get(2); assertTrue(dfn.isType(NodeType.FOR_INIT)); - dfn = exp.getDataFlowNode().getFlow().get(3); + dfn = DataFlowNode.get(exp).getFlow().get(3); assertTrue(dfn.isType(NodeType.FOR_EXPR)); - dfn = exp.getDataFlowNode().getFlow().get(4); + dfn = DataFlowNode.get(exp).getFlow().get(4); assertTrue(dfn.isType(NodeType.FOR_UPDATE)); assertTrue(dfn.isType(NodeType.FOR_BEFORE_FIRST_STATEMENT)); assertTrue(dfn.isType(NodeType.FOR_END)); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LiteralsFirstInComparisonsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LiteralsFirstInComparisonsTest.java new file mode 100644 index 0000000000..afec19e09a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LiteralsFirstInComparisonsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LiteralsFirstInComparisonsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java index a847fa13ec..c0c305c63d 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java @@ -21,7 +21,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.DummyJavaNode; -import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.ast.InternalApiBridge; import net.sourceforge.pmd.lang.java.symboltable.testdata.InnerClass; import net.sourceforge.pmd.lang.java.symboltable.testdata.InnerClass.TheInnerClass; import net.sourceforge.pmd.lang.java.symboltable.testdata.InnerClass.TheInnerClass.EnumTest; @@ -61,8 +61,7 @@ public class ClassScopeTest extends BaseNonParserTest { public void testContains() { ClassNameDeclaration classDeclaration = new ClassNameDeclaration(null); ClassScope s = new ClassScope("Foo", classDeclaration); - ASTVariableDeclaratorId node = new ASTVariableDeclaratorId(1); - node.setImage("bar"); + ASTVariableDeclaratorId node = InternalApiBridge.newVarId("bar"); s.addDeclaration(new VariableNameDeclaration(node)); assertTrue(s.getDeclarations().keySet().iterator().hasNext()); } @@ -71,7 +70,7 @@ public class ClassScopeTest extends BaseNonParserTest { public void testCantContainsSuperToString() { ClassNameDeclaration classDeclaration = new ClassNameDeclaration(null); ClassScope s = new ClassScope("Foo", classDeclaration); - JavaNode node = new DummyJavaNode(1); + DummyJavaNode node = new DummyJavaNode(1); node.setImage("super.toString"); assertFalse(s.contains(new JavaNameOccurrence(node, node.getImage()))); } @@ -80,11 +79,11 @@ public class ClassScopeTest extends BaseNonParserTest { public void testContainsStaticVariablePrefixedWithClassName() { ClassNameDeclaration classDeclaration = new ClassNameDeclaration(null); ClassScope s = new ClassScope("Foo", classDeclaration); - ASTVariableDeclaratorId node = new ASTVariableDeclaratorId(1); - node.setImage("X"); + ASTVariableDeclaratorId node = InternalApiBridge.newVarId("X"); + s.addDeclaration(new VariableNameDeclaration(node)); - JavaNode node2 = new DummyJavaNode(2); + DummyJavaNode node2 = new DummyJavaNode(2); node2.setImage("Foo.X"); assertTrue(s.contains(new JavaNameOccurrence(node2, node2.getImage()))); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ImageFinderFunctionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ImageFinderFunctionTest.java index fc94a49c65..ad0b042a9b 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ImageFinderFunctionTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ImageFinderFunctionTest.java @@ -12,6 +12,7 @@ import java.util.List; import org.junit.Test; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.InternalApiBridge; import net.sourceforge.pmd.lang.symboltable.ImageFinderFunction; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; @@ -20,8 +21,10 @@ public class ImageFinderFunctionTest { @Test public void testSingleImage() { ImageFinderFunction f = new ImageFinderFunction("foo"); - ASTVariableDeclaratorId node = new ASTVariableDeclaratorId(1); - node.setImage("foo"); + // These tests were completely broken, they built a PrimaryPrefix + // that is a child of a Name (not the reverse) + // This is an example of why tests should never build nodes manually + ASTVariableDeclaratorId node = InternalApiBridge.newVarId("foo"); NameDeclaration decl = new VariableNameDeclaration(node); f.applyTo(decl); assertEquals(decl, f.getDecl()); @@ -33,8 +36,7 @@ public class ImageFinderFunctionTest { imgs.add("Foo.foo"); imgs.add("foo"); ImageFinderFunction f = new ImageFinderFunction(imgs); - ASTVariableDeclaratorId node = new ASTVariableDeclaratorId(1); - node.setImage("foo"); + ASTVariableDeclaratorId node = InternalApiBridge.newVarId("foo"); NameDeclaration decl = new VariableNameDeclaration(node); f.applyTo(decl); assertEquals(decl, f.getDecl()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java index ca55bace54..c00f697dae 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java @@ -16,9 +16,9 @@ import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.InternalApiBridge; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; @@ -27,12 +27,8 @@ public class LocalScopeTest extends BaseNonParserTest { @Test public void testNameWithThisOrSuperIsNotFlaggedAsUnused() { LocalScope scope = new LocalScope(); - ASTName name = new ASTName(1); - name.setImage("foo"); - ASTPrimaryPrefix prefix = new ASTPrimaryPrefix(2); - prefix.setUsesThisModifier(); - name.jjtAddChild(prefix, 1); - JavaNameOccurrence occ = new JavaNameOccurrence(name, "foo"); + ASTPrimaryPrefix prefix = InternalApiBridge.newThisSuperPrefix("foo", true); + JavaNameOccurrence occ = new JavaNameOccurrence(prefix.getFirstChild(), "foo"); scope.addNameOccurrence(occ); assertFalse(scope.getDeclarations().keySet().iterator().hasNext()); } @@ -40,12 +36,8 @@ public class LocalScopeTest extends BaseNonParserTest { @Test public void testNameWithSuperIsNotFlaggedAsUnused() { LocalScope scope = new LocalScope(); - ASTName name = new ASTName(1); - name.setImage("foo"); - ASTPrimaryPrefix prefix = new ASTPrimaryPrefix(2); - prefix.setUsesSuperModifier(); - name.jjtAddChild(prefix, 1); - JavaNameOccurrence occ = new JavaNameOccurrence(name, "foo"); + ASTPrimaryPrefix prefix = InternalApiBridge.newThisSuperPrefix("foo", false); + JavaNameOccurrence occ = new JavaNameOccurrence(prefix.getFirstChild(), "foo"); scope.addNameOccurrence(occ); assertFalse(scope.getDeclarations().keySet().iterator().hasNext()); } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestsShouldIncludeAssert.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestsShouldIncludeAssert.xml index b59f2caf25..30a157cdf2 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestsShouldIncludeAssert.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestsShouldIncludeAssert.xml @@ -512,4 +512,22 @@ public class DigitalAssetManagerTest { } }]]> + + #2288 JUnitTestsShouldIncludeAssertRule should support `Matchers.assertThat` + 0 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/LiteralsFirstInComparisons.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/LiteralsFirstInComparisons.xml new file mode 100644 index 0000000000..024ff927ab --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/LiteralsFirstInComparisons.xml @@ -0,0 +1,278 @@ + + + + + ok, literal comes first in .equals comparison + 0 + + + + + bad, literal comes last in .equals comparison + 1 + + + + + ok, empty literal in .equals comparison + 0 + + + + + Test case from bug [1472195] - PositionLiteralsFirstInComparisons gives many .equals false positives + 0 + + + + + Test case from bug [1472195] - PositionLiteralsFirstInComparisons gives many .equals false positives + 0 + + + + + #1256 PositionLiteralsFirstInComparisons .equals false positive with Characters + 0 + + + + + ok, literal comes first in .equalsIgnoreCase comparison + 0 + + + + + bad, literal comes last in .equalsIgnoreCase comparison + 1 + + + + + ok, testing .equalsIgnoreCase false positive + 0 + + + + + Test case from bug [1472195] - PositionLiteralsFirstInComparisons gives many .equalsIgnoreCase false positives + 0 + + + + + Test case from bug [1472195] - PositionLiteralsFirstInComparisons gives many .equalsIgnoreCase false positives + 0 + + + + + ok, literal comes first in .compareTo comparison + 0 + + + + + bad, literal comes last in .compareTo comparison + 1 + 0; + } +} + ]]> + + + + ok, testing for .compareTo false positive + 0 + + + + + ok, literal comes first in .compareToIgnoreCase comparison + 0 + + + + + bad, literal comes last in .compareToIgnoreCase comparison + 1 + 0; + } +} + ]]> + + + + ok, testing ,compareToIgnoreCase false positive + 0 + + + + + ok, literal comes first in .contentEquals comparison + 0 + + + + + bad, literal comes last in .contentEquals comparison + 1 + + + + + ok, testing .contentEquals false positive + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JUnitSpelling.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JUnitSpelling.xml index 244268c26a..f0557725f2 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JUnitSpelling.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JUnitSpelling.xml @@ -79,4 +79,39 @@ public class Foo { } ]]> + + + No problem - usual JUnit5 test + 0 + + + + + No problem - usual JUnit4 test + 0 + + diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractEcmascriptNode.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractEcmascriptNode.java index 940a056c7b..c90dbe4a8f 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractEcmascriptNode.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractEcmascriptNode.java @@ -6,19 +6,32 @@ package net.sourceforge.pmd.lang.ecmascript.ast; import org.mozilla.javascript.ast.AstNode; -import net.sourceforge.pmd.lang.ast.AbstractNode; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.SourceCodePositioner; +import net.sourceforge.pmd.lang.ast.impl.AbstractNodeWithTextCoordinates; -abstract class AbstractEcmascriptNode extends AbstractNode implements EcmascriptNode { +abstract class AbstractEcmascriptNode extends AbstractNodeWithTextCoordinates, EcmascriptNode> implements EcmascriptNode { protected final T node; + private String image; AbstractEcmascriptNode(T node) { - super(node.getType()); this.node = node; } + @Override + protected void addChild(AbstractEcmascriptNode child, int index) { + super.addChild(child, index); + } + + @Override + public String getImage() { + return image; + } + + protected void setImage(String image) { + this.image = image; + } + /* package private */ void calculateLineNumbers(SourceCodePositioner positioner) { int startOffset = node.getAbsolutePosition(); @@ -42,21 +55,6 @@ abstract class AbstractEcmascriptNode extends AbstractNode im return visitor.visit(this, data); } - /** - * Accept the visitor. * - */ - @Override - public Object childrenAccept(EcmascriptParserVisitor visitor, Object data) { - for (Node child : children) { - // we know that the children here - // are all EcmascriptNodes - @SuppressWarnings("unchecked") - EcmascriptNode ecmascriptNode = (EcmascriptNode) child; - ecmascriptNode.jjtAccept(visitor, data); - } - return data; - } - @Override @Deprecated public T getNode() { diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractInfixEcmascriptNode.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractInfixEcmascriptNode.java index a9cbdd4549..50cb501378 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractInfixEcmascriptNode.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractInfixEcmascriptNode.java @@ -26,10 +26,10 @@ abstract class AbstractInfixEcmascriptNode extends Ab } public EcmascriptNode getLeft() { - return (EcmascriptNode) getChild(0); + return getChild(0); } public EcmascriptNode getRight() { - return (EcmascriptNode) getChild(1); + return getChild(1); } } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptNode.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptNode.java index e8da9af25f..5ec0fe88b5 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptNode.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptNode.java @@ -6,24 +6,15 @@ package net.sourceforge.pmd.lang.ecmascript.ast; import org.mozilla.javascript.ast.AstNode; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.GenericNode; -public interface EcmascriptNode extends Node { +public interface EcmascriptNode extends GenericNode> { /** * Accept the visitor. * */ Object jjtAccept(EcmascriptParserVisitor visitor, Object data); - /** - * Accept the visitor. * - * - * @deprecated This method is not useful, the logic for combining - * children values should be present on the visitor, not the node - */ - @Deprecated - Object childrenAccept(EcmascriptParserVisitor visitor, Object data); /** * Get the underlying Rhino AST node. @@ -32,13 +23,6 @@ public interface EcmascriptNode extends Node { @Deprecated T getNode(); - - @Override - default NodeStream> children() { - return (NodeStream) Node.super.children(); - } - - /** * Get the JsDoc associated with the given node. If there is no JsDoc on * this node, it may be associated with a parent node, on more diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptTreeBuilder.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptTreeBuilder.java index 992e23500e..5fd044d849 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptTreeBuilder.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptTreeBuilder.java @@ -66,7 +66,6 @@ import org.mozilla.javascript.ast.XmlExpression; import org.mozilla.javascript.ast.XmlMemberGet; import org.mozilla.javascript.ast.XmlString; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.SourceCodePositioner; final class EcmascriptTreeBuilder implements NodeVisitor { @@ -130,7 +129,7 @@ final class EcmascriptTreeBuilder implements NodeVisitor { private final Map> parseProblemToNode = new HashMap<>(); // The nodes having children built. - private final Stack nodes = new Stack<>(); + private final Stack> nodes = new Stack<>(); // The Rhino nodes with children to build. private final Stack parents = new Stack<>(); @@ -187,10 +186,9 @@ final class EcmascriptTreeBuilder implements NodeVisitor { AbstractEcmascriptNode node = createNodeAdapter(astNode); // Append to parent - Node parent = nodes.isEmpty() ? null : nodes.peek(); + AbstractEcmascriptNode parent = nodes.isEmpty() ? null : nodes.peek(); if (parent != null) { - parent.jjtAddChild(node, parent.getNumChildren()); - node.jjtSetParent(parent); + parent.addChild(node, parent.getNumChildren()); } handleParseProblems(node); diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/JsTreeDumpTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/JsTreeDumpTest.java new file mode 100644 index 0000000000..862afc6095 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/JsTreeDumpTest.java @@ -0,0 +1,33 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + +package net.sourceforge.pmd.lang.ecmascript.ast; + +import org.junit.Test; + +import net.sourceforge.pmd.lang.ast.test.BaseParsingHelper; +import net.sourceforge.pmd.lang.ast.test.BaseTreeDumpTest; +import net.sourceforge.pmd.lang.ast.test.NodePrintersKt; + +public class JsTreeDumpTest extends BaseTreeDumpTest { + public JsTreeDumpTest() { + super(NodePrintersKt.getSimpleNodePrinter(), ".js"); + } + + @Override + public BaseParsingHelper getParser() { + return JsParsingHelper.DEFAULT.withResourceContext(JsTreeDumpTest.class, "testdata"); + } + + @Test + public void simpleJavascriptFile() { + doTest("SimpleJavascriptFile"); + } + + @Test + public void jquerySelector() { + doTest("jquery-selector"); + } +} diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/SimpleJavascriptFile.js b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/SimpleJavascriptFile.js new file mode 100644 index 0000000000..85bded1b96 --- /dev/null +++ b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/SimpleJavascriptFile.js @@ -0,0 +1,6 @@ +var x = 1; +let z = 0; +function foo() { + var y = x + 1; + return y; +} diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/SimpleJavascriptFile.txt b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/SimpleJavascriptFile.txt new file mode 100644 index 0000000000..1e3390bb20 --- /dev/null +++ b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/SimpleJavascriptFile.txt @@ -0,0 +1,20 @@ ++- AstRoot + +- VariableDeclaration + | +- VariableInitializer + | +- Name + | +- NumberLiteral + +- VariableDeclaration + | +- VariableInitializer + | +- Name + | +- NumberLiteral + +- FunctionNode + +- Name + +- Block + +- VariableDeclaration + | +- VariableInitializer + | +- Name + | +- InfixExpression + | +- Name + | +- NumberLiteral + +- ReturnStatement + +- Name diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/jquery-selector.js b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/jquery-selector.js new file mode 100644 index 0000000000..c2f0bf10e5 --- /dev/null +++ b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/jquery-selector.js @@ -0,0 +1,1638 @@ +// from: https://raw.githubusercontent.com/jquery/jquery/88eb22e0599d546f98f6145c53deb086e1d82857/src/selector.js +import jQuery from "./core.js"; +import nodeName from "./core/nodeName.js"; +import document from "./var/document.js"; +import documentElement from "./var/documentElement.js"; +import indexOf from "./var/indexOf.js"; +import pop from "./var/pop.js"; +import push from "./var/push.js"; +import whitespace from "./selector/var/whitespace.js"; +import rbuggyQSA from "./selector/rbuggyQSA.js"; +import support from "./selector/support.js"; + +// The following utils are attached directly to the jQuery object. +import "./selector/contains.js"; +import "./selector/escapeSelector.js"; +import "./selector/uniqueSort.js"; + +var preferredDoc = document, + matches = documentElement.matches || documentElement.msMatchesSelector; + +( function() { + +var i, + Expr, + outermostContext, + + // Local document vars + document, + documentElement, + documentIsHTML, + + // Instance-specific data + expando = jQuery.expando, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + + "loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( + "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + bool: new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + needsContext: new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + if ( nonHex ) { + + // Strip the backslash prefix from a non-hex escape sequence + return nonHex; + } + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + return high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // Used for iframes; see `setDocument`. + // Support: IE 9 - 11+, Edge 12 - 18+ + // Removing the function wrapper causes a "Permission Denied" + // error in IE/Edge. + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && nodeName( elem, "fieldset" ); + }, + { dir: "parentNode", next: "legend" } + ); + +function selectorError( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +} + +function find( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + push.call( results, elem ); + } + return results; + + // Element context + } else { + if ( newContext && ( elem = newContext.getElementById( m ) ) && + jQuery.contains( context, elem ) ) { + + push.call( results, elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = jQuery.escapeSelector( nid ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by jQuery selector module + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + return nodeName( elem, "input" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && + elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11+ + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a jQuery selector context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [node] An element or document object to use to set the document + */ +function setDocument( node ) { + var subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 ) { + return; + } + + // Update global variables + document = doc; + documentElement = document.documentElement; + documentIsHTML = !jQuery.isXMLDoc( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 9 - 11+, Edge 12 - 18+ + subWindow.addEventListener( "unload", unloadHandler ); + } +} + +find.matches = function( expr, elements ) { + return find( expr, null, null, elements ); +}; + +find.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + return matches.call( elem, expr ); + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return find( expr, document, null, [ elem ] ).length > 0; +}; + +Expr = jQuery.expr = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + find: { + ID: function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }, + + TAG: function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else { + return context.querySelectorAll( tag ); + } + }, + + CLASS: function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + } + }, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + ATTR: function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ) + .replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + CHILD: function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + selectorError( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) + ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + selectorError( match[ 0 ] ); + } + + return match; + }, + + PSEUDO: function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr.CHILD.test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + ID: function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }, + + TAG: function( nodeNameSelector ) { + var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; +} : + function( elem ) { + return nodeName( elem, expectedNodeName ); + }; + }, + + CLASS: function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + ")" + className + + "(" + whitespace + "|$)" ) ) && + classCache( className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + ATTR: function( name, operator, check ) { + return function( elem ) { + var result = jQuery.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + if ( operator === "=" ) { + return result === check; + } + if ( operator === "!=" ) { + return result !== check; + } + if ( operator === "^=" ) { + return check && result.indexOf( check ) === 0; + } + if ( operator === "*=" ) { + return check && result.indexOf( check ) > -1; + } + if ( operator === "$=" ) { + return check && result.slice( -check.length ) === check; + } + if ( operator === "~=" ) { + return ( " " + result.replace( rwhitespace, " " ) + " " ) + .indexOf( check ) > -1; + } + if ( operator === "|=" ) { + return result === check || result.slice( 0, check.length + 1 ) === check + "-"; + } + + return false; + }; + }, + + CHILD: function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || ( parent[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + cache = outerCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + nodeName( node, name ) : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + outerCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + PSEUDO: function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // https://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + selectorError( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as jQuery does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + not: markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + has: markFunction( function( selector ) { + return function( elem ) { + return find( selector, elem ).length > 0; + }; + } ), + + contains: markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // https://www.w3.org/TR/selectors/#lang-pseudo + lang: markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + selectorError( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + target: function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + root: function( elem ) { + return elem === documentElement; + }, + + focus: function( elem ) { + return elem === document.activeElement && + document.hasFocus() && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + enabled: createDisabledPseudo( false ), + disabled: createDisabledPseudo( true ), + + checked: function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + return ( nodeName( elem, "input" ) && !!elem.checked ) || + ( nodeName( elem, "option" ) && !!elem.selected ); + }, + + selected: function( elem ) { + + // Support: IE <=11+ + // Accessing the selectedIndex property + // forces the browser to treat the default option as + // selected when in an optgroup. + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + empty: function( elem ) { + + // https://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + parent: function( elem ) { + return !Expr.pseudos.empty( elem ); + }, + + // Element/input types + header: function( elem ) { + return rheader.test( elem.nodeName ); + }, + + input: function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + button: function( elem ) { + return nodeName( elem, "input" ) && elem.type === "button" || + nodeName( elem, "button" ); + }, + + text: function( elem ) { + return nodeName( elem, "input" ) && elem.type === "text"; + }, + + // Position-in-collection + first: createPositionalPseudo( function() { + return [ 0 ]; + } ), + + last: createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + even: createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + odd: createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + lt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i; + + if ( argument < 0 ) { + i = argument + length; + } else if ( argument > length ) { + i = length; + } else { + i = argument; + } + + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + gt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos.nth = Expr.pseudos.eq; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + if ( parseOnly ) { + return soFar.length; + } + + return soFar ? + selectorError( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + if ( skip && nodeName( elem, skip ) ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = outerCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + outerCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + find( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, matcherOut, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || + multipleContexts( selector || "*", + context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems; + + if ( matcher ) { + + // If we have a postFinder, or filtered seed, or non-seed postFilter + // or preexisting results, + matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results; + + // Find primary matches + matcher( matcherIn, matcherOut, context, xml ); + } else { + matcherOut = matcherIn; + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find.TAG( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ); + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + for ( ; ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + push.call( results, elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + jQuery.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +function compile( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +} + +/** + * A low-level selection function that works with jQuery's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with jQuery selector compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find.ID( + token.matches[ 0 ].replace( runescape, funescape ), + context + ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && + testContext( context.parentNode ) || context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +} + +// Initialize against the default document +setDocument(); + +jQuery.find = find; + +} )(); diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/jquery-selector.txt b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/jquery-selector.txt new file mode 100644 index 0000000000..92588d6387 --- /dev/null +++ b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/ast/testdata/jquery-selector.txt @@ -0,0 +1,5219 @@ ++- AstRoot + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- EmptyStatement + +- VariableDeclaration + | +- VariableInitializer + | | +- Name + | | +- Name + | +- VariableInitializer + | +- Name + | +- InfixExpression + | +- PropertyGet + | | +- Name + | | +- Name + | +- PropertyGet + | +- Name + | +- Name + +- ExpressionStatement + +- FunctionCall + +- ParenthesizedExpression + +- FunctionNode + +- Block + +- VariableDeclaration + | +- VariableInitializer + | | +- Name + | +- VariableInitializer + | | +- Name + | +- VariableInitializer + | | +- Name + | +- VariableInitializer + | | +- Name + | +- VariableInitializer + | | +- Name + | +- VariableInitializer + | | +- Name + | +- VariableInitializer + | | +- Name + | | +- PropertyGet + | | +- Name + | | +- Name + | +- VariableInitializer + | | +- Name + | | +- NumberLiteral + | +- VariableInitializer + | | +- Name + | | +- NumberLiteral + | +- VariableInitializer + | | +- Name + | | +- FunctionCall + | | +- Name + | +- VariableInitializer + | | +- Name + | | +- FunctionCall + | | +- Name + | +- VariableInitializer + | | +- Name + | | +- FunctionCall + | | +- Name + | +- VariableInitializer + | | +- Name + | | +- FunctionCall + | | +- Name + | +- VariableInitializer + | | +- Name + | | +- InfixExpression + | | +- StringLiteral + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- InfixExpression + | | +- InfixExpression + | | | +- StringLiteral + | | | +- Name + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- InfixExpression + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- InfixExpression + | | | | | | | | +- InfixExpression + | | | | | | | | | +- InfixExpression + | | | | | | | | | | +- InfixExpression + | | | | | | | | | | | +- InfixExpression + | | | | | | | | | | | | +- InfixExpression + | | | | | | | | | | | | | +- StringLiteral + | | | | | | | | | | | | | +- Name + | | | | | | | | | | | | +- StringLiteral + | | | | | | | | | | | +- Name + | | | | | | | | | | +- StringLiteral + | | | | | | | | | +- Name + | | | | | | | | +- StringLiteral + | | | | | | | +- Name + | | | | | | +- StringLiteral + | | | | | +- Name + | | | | +- StringLiteral + | | | +- Name + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- InfixExpression + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- InfixExpression + | | | | | | | | +- InfixExpression + | | | | | | | | | +- StringLiteral + | | | | | | | | | +- Name + | | | | | | | | +- StringLiteral + | | | | | | | +- StringLiteral + | | | | | | +- StringLiteral + | | | | | +- Name + | | | | +- StringLiteral + | | | +- StringLiteral + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- NewExpression + | | +- Name + | | +- InfixExpression + | | | +- Name + | | | +- StringLiteral + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- NewExpression + | | +- Name + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- StringLiteral + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Name + | | | +- StringLiteral + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- NewExpression + | | +- Name + | | +- InfixExpression + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- StringLiteral + | | | | | +- Name + | | | | +- StringLiteral + | | | +- Name + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- NewExpression + | | +- Name + | | +- InfixExpression + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- StringLiteral + | | | | | | | +- Name + | | | | | | +- StringLiteral + | | | | | +- Name + | | | | +- StringLiteral + | | | +- Name + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- NewExpression + | | +- Name + | | +- InfixExpression + | | +- Name + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- NewExpression + | | +- Name + | | +- Name + | +- VariableInitializer + | | +- Name + | | +- NewExpression + | | +- Name + | | +- InfixExpression + | | +- InfixExpression + | | | +- StringLiteral + | | | +- Name + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- ObjectLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- NewExpression + | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- StringLiteral + | | | | +- Name + | | | +- StringLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- NewExpression + | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- StringLiteral + | | | | +- Name + | | | +- StringLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- NewExpression + | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- StringLiteral + | | | | +- Name + | | | +- StringLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- NewExpression + | | | +- Name + | | | +- InfixExpression + | | | +- StringLiteral + | | | +- Name + | | +- ObjectProperty + | | | +- Name + | | | +- NewExpression + | | | +- Name + | | | +- InfixExpression + | | | +- StringLiteral + | | | +- Name + | | +- ObjectProperty + | | | +- Name + | | | +- NewExpression + | | | +- Name + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- InfixExpression + | | | | | | | | +- InfixExpression + | | | | | | | | | +- InfixExpression + | | | | | | | | | | +- InfixExpression + | | | | | | | | | | | +- StringLiteral + | | | | | | | | | | | +- Name + | | | | | | | | | | +- StringLiteral + | | | | | | | | | +- Name + | | | | | | | | +- StringLiteral + | | | | | | | +- Name + | | | | | | +- StringLiteral + | | | | | +- Name + | | | | +- StringLiteral + | | | +- StringLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- NewExpression + | | | +- Name + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- StringLiteral + | | | | | +- Name + | | | | +- StringLiteral + | | | +- StringLiteral + | | +- ObjectProperty + | | +- Name + | | +- NewExpression + | | +- Name + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- InfixExpression + | | | | | | | | +- StringLiteral + | | | | | | | | +- Name + | | | | | | | +- StringLiteral + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Name + | | | +- StringLiteral + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- RegExpLiteral + | +- VariableInitializer + | | +- Name + | | +- RegExpLiteral + | +- VariableInitializer + | | +- Name + | | +- RegExpLiteral + | +- VariableInitializer + | | +- Name + | | +- RegExpLiteral + | +- VariableInitializer + | | +- Name + | | +- NewExpression + | | +- Name + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- StringLiteral + | | | | +- Name + | | | +- StringLiteral + | | +- StringLiteral + | +- VariableInitializer + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- StringLiteral + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- NumberLiteral + | | | +- NumberLiteral + | | +- IfStatement + | | | +- Name + | | | +- Scope + | | | +- ReturnStatement + | | | +- Name + | | +- ReturnStatement + | | +- ConditionalExpression + | | +- InfixExpression + | | | +- Name + | | | +- NumberLiteral + | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- NumberLiteral + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- NumberLiteral + | | | +- NumberLiteral + | | +- InfixExpression + | | +- InfixExpression + | | | +- Name + | | | +- NumberLiteral + | | +- NumberLiteral + | +- VariableInitializer + | | +- Name + | | +- FunctionNode + | | +- Block + | | +- ExpressionStatement + | | +- FunctionCall + | | +- Name + | +- VariableInitializer + | +- Name + | +- FunctionCall + | +- Name + | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- InfixExpression + | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- KeywordLiteral + | | +- FunctionCall + | | +- Name + | | +- Name + | | +- StringLiteral + | +- ObjectLiteral + | +- ObjectProperty + | | +- Name + | | +- StringLiteral + | +- ObjectProperty + | +- Name + | +- StringLiteral + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ThrowStatement + | +- NewExpression + | +- Name + | +- InfixExpression + | +- StringLiteral + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- VariableInitializer + | | +- Name + | | +- ConditionalExpression + | | +- Name + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- NumberLiteral + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- InfixExpression + | | +- Name + | | +- ArrayLiteral + | +- IfStatement + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- StringLiteral + | | | +- InfixExpression + | | | +- UnaryExpression + | | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- NumberLiteral + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- NumberLiteral + | | | +- InfixExpression + | | | +- Name + | | | +- NumberLiteral + | | +- Scope + | | +- ReturnStatement + | | +- Name + | +- IfStatement + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- IfStatement + | | +- Name + | | +- Scope + | | +- IfStatement + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- Scope + | | | | | +- IfStatement + | | | | | | +- ParenthesizedExpression + | | | | | | | +- Assignment + | | | | | | | +- Name + | | | | | | | +- FunctionCall + | | | | | | | +- PropertyGet + | | | | | | | | +- Name + | | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Scope + | | | | | | +- ExpressionStatement + | | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- ReturnStatement + | | | | | +- Name + | | | | +- Scope + | | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- ParenthesizedExpression + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | | +- Name + | | | | +- ReturnStatement + | | | | +- Name + | | | +- IfStatement + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | | +- ExpressionStatement + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- ReturnStatement + | | | | +- Name + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- ReturnStatement + | | | +- Name + | | +- IfStatement + | | +- InfixExpression + | | | +- UnaryExpression + | | | | +- ElementGet + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- StringLiteral + | | | +- ParenthesizedExpression + | | | +- InfixExpression + | | | +- UnaryExpression + | | | | +- Name + | | | +- UnaryExpression + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- Name + | | +- IfStatement + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- ParenthesizedExpression + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- Name + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- UnaryExpression + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | +- IfStatement + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Scope + | | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- StringLiteral + | | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- WhileLoop + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- ParenthesizedExpression + | | | | | | +- ConditionalExpression + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | | +- StringLiteral + | | | | | | | +- Name + | | | | | | +- StringLiteral + | | | | | +- StringLiteral + | | | | +- FunctionCall + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- StringLiteral + | | +- TryStatement + | | +- Scope + | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- ReturnStatement + | | | +- Name + | | +- CatchClause + | | | +- Name + | | | +- Block + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- KeywordLiteral + | | +- Scope + | | +- IfStatement + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- StringLiteral + | +- ReturnStatement + | +- FunctionCall + | +- Name + | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | | +- StringLiteral + | +- Name + | +- Name + | +- Name + +- FunctionNode + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | +- Name + | | +- ArrayLiteral + | +- FunctionNode + | | +- Name + | | +- Name + | | +- Name + | | +- Block + | | +- IfStatement + | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | +- UnaryExpression + | | | +- ElementGet + | | | +- Name + | | | +- FunctionCall + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- ParenthesizedExpression + | | +- Assignment + | | +- ElementGet + | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- StringLiteral + | | +- Name + | +- ReturnStatement + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ExpressionStatement + | | +- Assignment + | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- KeywordLiteral + | +- ReturnStatement + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ReturnStatement + | +- FunctionNode + | +- Name + | +- Block + | +- ReturnStatement + | +- InfixExpression + | +- FunctionCall + | | +- Name + | | +- Name + | | +- StringLiteral + | +- InfixExpression + | +- PropertyGet + | | +- Name + | | +- Name + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ReturnStatement + | +- FunctionNode + | +- Name + | +- Block + | +- ReturnStatement + | +- InfixExpression + | +- ParenthesizedExpression + | | +- InfixExpression + | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- StringLiteral + | | +- FunctionCall + | | +- Name + | | +- Name + | | +- StringLiteral + | +- InfixExpression + | +- PropertyGet + | | +- Name + | | +- Name + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ReturnStatement + | +- FunctionNode + | +- Name + | +- Block + | +- IfStatement + | | +- InfixExpression + | | | +- StringLiteral + | | | +- Name + | | +- Scope + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- KeywordLiteral + | | | | +- Scope + | | | | +- IfStatement + | | | | | +- InfixExpression + | | | | | | +- StringLiteral + | | | | | | +- Name + | | | | | +- Scope + | | | | | +- IfStatement + | | | | | +- InfixExpression + | | | | | | +- StringLiteral + | | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Scope + | | | | | | +- ReturnStatement + | | | | | | +- InfixExpression + | | | | | | +- PropertyGet + | | | | | | | +- PropertyGet + | | | | | | | | +- Name + | | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Scope + | | | | | +- ReturnStatement + | | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- ReturnStatement + | | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- IfStatement + | | +- InfixExpression + | | | +- StringLiteral + | | | +- Name + | | +- Scope + | | +- ReturnStatement + | | +- InfixExpression + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ReturnStatement + | +- KeywordLiteral + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ReturnStatement + | +- FunctionCall + | +- Name + | +- FunctionNode + | +- Name + | +- Block + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- UnaryExpression + | | +- Name + | +- ReturnStatement + | +- FunctionCall + | +- Name + | +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- ArrayLiteral + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- VariableInitializer + | | +- Name + | | +- PropertyGet + | | +- Name + | | +- Name + | +- WhileLoop + | +- UnaryExpression + | | +- Name + | +- Scope + | +- IfStatement + | +- ElementGet + | | +- Name + | | +- ParenthesizedExpression + | | +- Assignment + | | +- Name + | | +- ElementGet + | | +- Name + | | +- Name + | +- Scope + | +- ExpressionStatement + | +- Assignment + | +- ElementGet + | | +- Name + | | +- Name + | +- UnaryExpression + | +- ParenthesizedExpression + | +- Assignment + | +- ElementGet + | | +- Name + | | +- Name + | +- ElementGet + | +- Name + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ReturnStatement + | +- InfixExpression + | +- Name + | +- InfixExpression + | +- InfixExpression + | | +- UnaryExpression + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- StringLiteral + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | +- Name + | | +- ConditionalExpression + | | +- Name + | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- Name + | +- IfStatement + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | +- Scope + | | +- ReturnStatement + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- Name + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- PropertyGet + | | +- Name + | | +- Name + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- UnaryExpression + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- IfStatement + | +- InfixExpression + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- InfixExpression + | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- InfixExpression + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- Scope + | +- ExpressionStatement + | +- FunctionCall + | +- PropertyGet + | | +- Name + | | +- Name + | +- StringLiteral + | +- Name + +- ExpressionStatement + | +- Assignment + | +- PropertyGet + | | +- Name + | | +- Name + | +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ReturnStatement + | +- FunctionCall + | +- Name + | +- Name + | +- KeywordLiteral + | +- KeywordLiteral + | +- Name + +- ExpressionStatement + | +- Assignment + | +- PropertyGet + | | +- Name + | | +- Name + | +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ExpressionStatement + | | +- FunctionCall + | | +- Name + | | +- Name + | +- IfStatement + | | +- InfixExpression + | | | +- Name + | | | +- InfixExpression + | | | +- UnaryExpression + | | | | +- ElementGet + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- StringLiteral + | | | +- ParenthesizedExpression + | | | +- InfixExpression + | | | +- UnaryExpression + | | | | +- Name + | | | +- UnaryExpression + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- Scope + | | +- TryStatement + | | +- Scope + | | | +- ReturnStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | | +- Name + | | +- CatchClause + | | +- Name + | | +- Block + | | +- ExpressionStatement + | | +- FunctionCall + | | +- Name + | | +- Name + | | +- KeywordLiteral + | +- ReturnStatement + | +- InfixExpression + | +- PropertyGet + | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- KeywordLiteral + | | | +- ArrayLiteral + | | | +- Name + | | +- Name + | +- NumberLiteral + +- ExpressionStatement + | +- Assignment + | +- Name + | +- Assignment + | +- PropertyGet + | | +- Name + | | +- Name + | +- ObjectLiteral + | +- ObjectProperty + | | +- Name + | | +- NumberLiteral + | +- ObjectProperty + | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- ObjectLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Name + | | | +- Block + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- UnaryExpression + | | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Name + | | | +- Scope + | | | +- VariableDeclaration + | | | | +- VariableInitializer + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- ReturnStatement + | | | +- ConditionalExpression + | | | +- Name + | | | +- ArrayLiteral + | | | | +- Name + | | | +- ArrayLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Name + | | | +- Block + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- UnaryExpression + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- StringLiteral + | | | +- Scope + | | | | +- ReturnStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ReturnStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Block + | | +- IfStatement + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- UnaryExpression + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- StringLiteral + | | | +- Name + | | +- Scope + | | +- ReturnStatement + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- ObjectLiteral + | | +- ObjectProperty + | | | +- StringLiteral + | | | +- ObjectLiteral + | | | +- ObjectProperty + | | | | +- Name + | | | | +- StringLiteral + | | | +- ObjectProperty + | | | +- Name + | | | +- KeywordLiteral + | | +- ObjectProperty + | | | +- StringLiteral + | | | +- ObjectLiteral + | | | +- ObjectProperty + | | | +- Name + | | | +- StringLiteral + | | +- ObjectProperty + | | | +- StringLiteral + | | | +- ObjectLiteral + | | | +- ObjectProperty + | | | | +- Name + | | | | +- StringLiteral + | | | +- ObjectProperty + | | | +- Name + | | | +- KeywordLiteral + | | +- ObjectProperty + | | +- StringLiteral + | | +- ObjectLiteral + | | +- ObjectProperty + | | +- Name + | | +- StringLiteral + | +- ObjectProperty + | | +- Name + | | +- ObjectLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- ParenthesizedExpression + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- StringLiteral + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- StringLiteral + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- StringLiteral + | | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- StringLiteral + | | | +- ReturnStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | | +- NumberLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- Name + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- ElementGet + | | | | | | | | +- Name + | | | | | | | | +- NumberLiteral + | | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | | +- NumberLiteral + | | | | | +- StringLiteral + | | | | +- Scope + | | | | | +- IfStatement + | | | | | | +- UnaryExpression + | | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- Scope + | | | | | | +- ExpressionStatement + | | | | | | +- FunctionCall + | | | | | | +- Name + | | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- UnaryExpression + | | | | | | +- ParenthesizedExpression + | | | | | | +- ConditionalExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- InfixExpression + | | | | | | | +- ElementGet + | | | | | | | | +- Name + | | | | | | | | +- NumberLiteral + | | | | | | | +- ParenthesizedExpression + | | | | | | | +- InfixExpression + | | | | | | | +- ElementGet + | | | | | | | | +- Name + | | | | | | | | +- NumberLiteral + | | | | | | | +- NumberLiteral + | | | | | | +- InfixExpression + | | | | | | +- NumberLiteral + | | | | | | +- ParenthesizedExpression + | | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- ElementGet + | | | | | | | | +- Name + | | | | | | | | +- NumberLiteral + | | | | | | | +- StringLiteral + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- StringLiteral + | | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- UnaryExpression + | | | | | +- ParenthesizedExpression + | | | | | +- InfixExpression + | | | | | +- ParenthesizedExpression + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- InfixExpression + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- StringLiteral + | | | | +- IfStatement + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- ReturnStatement + | | | +- Name + | | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | | +- Name + | | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- UnaryExpression + | | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- ElementGet + | | | +- Name + | | | +- NumberLiteral + | | +- IfStatement + | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | +- ReturnStatement + | | | +- KeywordLiteral + | | +- IfStatement + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- InfixExpression + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- InfixExpression + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- StringLiteral + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- Name + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- InfixExpression + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- Name + | | | | | +- Name + | | | | | +- KeywordLiteral + | | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- Name + | | | | +- NumberLiteral + | | | | +- Name + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | | +- Name + | | +- ReturnStatement + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- NumberLiteral + | | +- NumberLiteral + | +- ObjectProperty + | | +- Name + | | +- ObjectLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- VariableDeclaration + | | | | +- VariableInitializer + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- ReturnStatement + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- ReturnStatement + | | | +- InfixExpression + | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- StringLiteral + | | | +- Name + | | +- ObjectProperty + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- VariableDeclaration + | | | | +- VariableInitializer + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- ReturnStatement + | | | +- ConditionalExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- StringLiteral + | | | +- FunctionNode + | | | | +- Block + | | | | +- ReturnStatement + | | | | +- KeywordLiteral + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- ReturnStatement + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | +- ObjectProperty + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- VariableDeclaration + | | | | +- VariableInitializer + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- StringLiteral + | | | +- ReturnStatement + | | | +- InfixExpression + | | | +- Name + | | | +- InfixExpression + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- NewExpression + | | | | +- Name + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- InfixExpression + | | | | | | | | +- InfixExpression + | | | | | | | | | +- StringLiteral + | | | | | | | | | +- Name + | | | | | | | | +- StringLiteral + | | | | | | | +- Name + | | | | | | +- StringLiteral + | | | | | +- Name + | | | | +- StringLiteral + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- ReturnStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- UnaryExpression + | | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- UnaryExpression + | | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- StringLiteral + | | | +- StringLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Block + | | | +- ReturnStatement + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- VariableDeclaration + | | | | +- VariableInitializer + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- KeywordLiteral + | | | | +- Scope + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- Name + | | | | +- StringLiteral + | | | +- IfStatement + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- Scope + | | | | +- ReturnStatement + | | | | +- KeywordLiteral + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- StringLiteral + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Scope + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Scope + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Scope + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- Name + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- NumberLiteral + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Scope + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- Name + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- UnaryExpression + | | | | +- NumberLiteral + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Scope + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- Name + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- UnaryExpression + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Scope + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- ParenthesizedExpression + | | | | | | | +- InfixExpression + | | | | | | | +- InfixExpression + | | | | | | | | +- StringLiteral + | | | | | | | | +- FunctionCall + | | | | | | | | +- PropertyGet + | | | | | | | | | +- Name + | | | | | | | | | +- Name + | | | | | | | | +- Name + | | | | | | | | +- StringLiteral + | | | | | | | +- StringLiteral + | | | | | | +- Name + | | | | | +- Name + | | | | +- UnaryExpression + | | | | +- NumberLiteral + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Scope + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- NumberLiteral + | | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- InfixExpression + | | | | +- Name + | | | | +- StringLiteral + | | | +- ReturnStatement + | | | +- KeywordLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Block + | | | +- VariableDeclaration + | | | | +- VariableInitializer + | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | | +- NumberLiteral + | | | | | +- StringLiteral + | | | | +- VariableInitializer + | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- UnaryExpression + | | | | | | +- NumberLiteral + | | | | | +- StringLiteral + | | | | +- VariableInitializer + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- StringLiteral + | | | +- ReturnStatement + | | | +- ConditionalExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- InfixExpression + | | | | +- Name + | | | | +- NumberLiteral + | | | +- FunctionNode + | | | | +- Name + | | | | +- Block + | | | | +- ReturnStatement + | | | | +- UnaryExpression + | | | | +- UnaryExpression + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Block + | | | +- VariableDeclaration + | | | | +- VariableInitializer + | | | | | +- Name + | | | | +- VariableInitializer + | | | | | +- Name + | | | | +- VariableInitializer + | | | | | +- Name + | | | | +- VariableInitializer + | | | | | +- Name + | | | | +- VariableInitializer + | | | | | +- Name + | | | | +- VariableInitializer + | | | | | +- Name + | | | | | +- ConditionalExpression + | | | | | +- InfixExpression + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | | +- StringLiteral + | | | | +- VariableInitializer + | | | | | +- Name + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- VariableInitializer + | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- VariableInitializer + | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- UnaryExpression + | | | | | | +- Name + | | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- VariableInitializer + | | | | +- Name + | | | | +- KeywordLiteral + | | | +- IfStatement + | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | | +- Name + | | | | +- Scope + | | | | +- WhileLoop + | | | | | +- Name + | | | | | +- Scope + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- WhileLoop + | | | | | | +- ParenthesizedExpression + | | | | | | | +- Assignment + | | | | | | | +- Name + | | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Scope + | | | | | | +- IfStatement + | | | | | | +- ConditionalExpression + | | | | | | | +- Name + | | | | | | | +- FunctionCall + | | | | | | | | +- Name + | | | | | | | | +- Name + | | | | | | | | +- Name + | | | | | | | +- InfixExpression + | | | | | | | +- PropertyGet + | | | | | | | | +- Name + | | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- Scope + | | | | | | +- ReturnStatement + | | | | | | +- KeywordLiteral + | | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- Name + | | | | | | +- StringLiteral + | | | | | +- InfixExpression + | | | | | +- UnaryExpression + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- ReturnStatement + | | | | +- KeywordLiteral + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- ArrayLiteral + | | | | +- ConditionalExpression + | | | | +- Name + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ParenthesizedExpression + | | | | | | +- Assignment + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ObjectLiteral + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ArrayLiteral + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- ElementGet + | | | | | | | | +- Name + | | | | | | | | +- NumberLiteral + | | | | | | | +- Name + | | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- Name + | | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- Name + | | | | | | +- ElementGet + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | +- WhileLoop + | | | | | +- ParenthesizedExpression + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- UnaryExpression + | | | | | | | | +- Name + | | | | | | | +- InfixExpression + | | | | | | | +- Name + | | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- ParenthesizedExpression + | | | | | | | +- Assignment + | | | | | | | +- Name + | | | | | | | +- Assignment + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Scope + | | | | | +- IfStatement + | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- PropertyGet + | | | | | | | | +- Name + | | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- InfixExpression + | | | | | | +- UnaryExpression + | | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Scope + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ArrayLiteral + | | | | | | +- Name + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- BreakStatement + | | | | +- Scope + | | | | +- IfStatement + | | | | | +- Name + | | | | | +- Scope + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ParenthesizedExpression + | | | | | | +- Assignment + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ObjectLiteral + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ArrayLiteral + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- InfixExpression + | | | | | | | +- ElementGet + | | | | | | | | +- Name + | | | | | | | | +- NumberLiteral + | | | | | | | +- Name + | | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- Name + | | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- KeywordLiteral + | | | | +- Scope + | | | | +- WhileLoop + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- InfixExpression + | | | | | | +- UnaryExpression + | | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- Name + | | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- ParenthesizedExpression + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- ParenthesizedExpression + | | | | | | +- ConditionalExpression + | | | | | | +- Name + | | | | | | +- FunctionCall + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- Scope + | | | | +- IfStatement + | | | | | +- Name + | | | | | +- Scope + | | | | | +- ExpressionStatement + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ParenthesizedExpression + | | | | | | +- Assignment + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ObjectLiteral + | | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- ArrayLiteral + | | | | | +- Name + | | | | | +- Name + | | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | +- BreakStatement + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- Name + | | | +- ReturnStatement + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- ParenthesizedExpression + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- NumberLiteral + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | | +- Name + | | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- ElementGet + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- ElementGet + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- InfixExpression + | | | +- StringLiteral + | | | +- Name + | | +- IfStatement + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ReturnStatement + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | +- IfStatement + | | | +- InfixExpression + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- ArrayLiteral + | | | | +- Name + | | | | +- Name + | | | | +- StringLiteral + | | | | +- Name + | | | +- ReturnStatement + | | | +- ConditionalExpression + | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- FunctionCall + | | | | +- Name + | | | | +- FunctionNode + | | | | +- Name + | | | | +- Name + | | | | +- Block + | | | | +- VariableDeclaration + | | | | | +- VariableInitializer + | | | | | | +- Name + | | | | | +- VariableInitializer + | | | | | | +- Name + | | | | | | +- FunctionCall + | | | | | | +- Name + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- VariableInitializer + | | | | | +- Name + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- WhileLoop + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- UnaryExpression + | | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Block + | | | +- ReturnStatement + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- NumberLiteral + | | | +- Name + | | +- ReturnStatement + | | +- Name + | +- ObjectProperty + | +- Name + | +- ObjectLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | | +- Name + | | | | +- ArrayLiteral + | | | +- VariableInitializer + | | | | +- Name + | | | | +- ArrayLiteral + | | | +- VariableInitializer + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | | +- StringLiteral + | | +- ReturnStatement + | | +- ConditionalExpression + | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- FunctionCall + | | | +- Name + | | | +- FunctionNode + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Block + | | | +- VariableDeclaration + | | | | +- VariableInitializer + | | | | | +- Name + | | | | +- VariableInitializer + | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- Name + | | | | | +- Name + | | | | | +- KeywordLiteral + | | | | | +- Name + | | | | | +- ArrayLiteral + | | | | +- VariableInitializer + | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- WhileLoop + | | | +- UnaryExpression + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- UnaryExpression + | | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Name + | | +- Block + | | +- ExpressionStatement + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Name + | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- KeywordLiteral + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- KeywordLiteral + | | +- ReturnStatement + | | +- UnaryExpression + | | +- FunctionCall + | | +- PropertyGet + | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- InfixExpression + | | +- PropertyGet + | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- NumberLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- InfixExpression + | | +- FunctionCall + | | | +- PropertyGet + | | | | +- ParenthesizedExpression + | | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- UnaryExpression + | | +- NumberLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- IfStatement + | | | +- UnaryExpression + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- StringLiteral + | | | +- Scope + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- Name + | | | +- InfixExpression + | | | +- StringLiteral + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- PropertyGet + | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | +- Name + | | +- DoLoop + | | | +- Scope + | | | | +- IfStatement + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- ConditionalExpression + | | | | | +- Name + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- StringLiteral + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- NumberLiteral + | | | +- InfixExpression + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | +- ReturnStatement + | | +- KeywordLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- PropertyGet + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- InfixExpression + | | +- Name + | | +- InfixExpression + | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | +- PropertyGet + | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- InfixExpression + | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- InfixExpression + | | +- InfixExpression + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- InfixExpression + | | +- FunctionCall + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- UnaryExpression + | | +- UnaryExpression + | | +- ParenthesizedExpression + | | +- InfixExpression + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- InfixExpression + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- UnaryExpression + | | +- PropertyGet + | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- KeywordLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- KeywordLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- InfixExpression + | | +- ParenthesizedExpression + | | | +- InfixExpression + | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | | +- StringLiteral + | | | +- UnaryExpression + | | | +- UnaryExpression + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- ParenthesizedExpression + | | +- InfixExpression + | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- StringLiteral + | | +- UnaryExpression + | | +- UnaryExpression + | | +- PropertyGet + | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- IfStatement + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | +- PropertyGet + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- InfixExpression + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- KeywordLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ForLoop + | | | +- Assignment + | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | | +- Assignment + | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | +- ReturnStatement + | | | +- KeywordLiteral + | | +- ReturnStatement + | | +- KeywordLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- UnaryExpression + | | +- FunctionCall + | | +- PropertyGet + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- PropertyGet + | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- PropertyGet + | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- InfixExpression + | | +- InfixExpression + | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | | +- StringLiteral + | | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- StringLiteral + | | +- FunctionCall + | | +- Name + | | +- Name + | | +- StringLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- InfixExpression + | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- StringLiteral + | | +- InfixExpression + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- StringLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Block + | | +- ReturnStatement + | | +- ArrayLiteral + | | +- NumberLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- ArrayLiteral + | | +- InfixExpression + | | +- Name + | | +- NumberLiteral + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Name + | | +- Block + | | +- ReturnStatement + | | +- ArrayLiteral + | | +- ConditionalExpression + | | +- InfixExpression + | | | +- Name + | | | +- NumberLiteral + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | +- Name + | | | +- NumberLiteral + | | +- ForLoop + | | | +- EmptyExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- Assignment + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | +- Name + | | | +- NumberLiteral + | | +- ForLoop + | | | +- EmptyExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- Assignment + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- Name + | +- ObjectProperty + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | +- Name + | | +- IfStatement + | | | +- InfixExpression + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- Name + | | +- ForLoop + | | | +- EmptyExpression + | | | +- InfixExpression + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- NumberLiteral + | | | +- EmptyExpression + | | | +- Scope + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- Name + | +- ObjectProperty + | +- Name + | +- FunctionCall + | +- Name + | +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | +- Name + | | +- ConditionalExpression + | | +- InfixExpression + | | | +- Name + | | | +- NumberLiteral + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- Name + | +- ForLoop + | | +- EmptyExpression + | | +- InfixExpression + | | | +- UnaryExpression + | | | | +- Name + | | | +- Name + | | +- EmptyExpression + | | +- Scope + | | +- ExpressionStatement + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ReturnStatement + | +- Name + +- ExpressionStatement + | +- Assignment + | +- PropertyGet + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- PropertyGet + | +- PropertyGet + | | +- Name + | | +- Name + | +- Name + +- ForInLoop + | +- Name + | +- ObjectLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- KeywordLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- KeywordLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- KeywordLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- KeywordLiteral + | | +- ObjectProperty + | | +- Name + | | +- KeywordLiteral + | +- Scope + | +- ExpressionStatement + | +- Assignment + | +- ElementGet + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- FunctionCall + | +- Name + | +- Name + +- ForInLoop + | +- Name + | +- ObjectLiteral + | | +- ObjectProperty + | | | +- Name + | | | +- KeywordLiteral + | | +- ObjectProperty + | | +- Name + | | +- KeywordLiteral + | +- Scope + | +- ExpressionStatement + | +- Assignment + | +- ElementGet + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- FunctionCall + | +- Name + | +- Name + +- FunctionNode + | +- Name + | +- Block + +- ExpressionStatement + | +- Assignment + | +- PropertyGet + | | +- Name + | | +- Name + | +- Assignment + | +- PropertyGet + | | +- Name + | | +- Name + | +- PropertyGet + | +- Name + | +- Name + +- ExpressionStatement + | +- Assignment + | +- PropertyGet + | | +- Name + | | +- Name + | +- NewExpression + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | +- Name + | | +- ElementGet + | | +- Name + | | +- InfixExpression + | | +- Name + | | +- StringLiteral + | +- IfStatement + | | +- Name + | | +- Scope + | | +- ReturnStatement + | | +- ConditionalExpression + | | +- Name + | | +- NumberLiteral + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- NumberLiteral + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- Name + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- ArrayLiteral + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- PropertyGet + | | +- Name + | | +- Name + | +- WhileLoop + | | +- Name + | | +- Scope + | | +- IfStatement + | | | +- InfixExpression + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- PropertyGet + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- Name + | | | +- ArrayLiteral + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- KeywordLiteral + | | +- IfStatement + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- ObjectLiteral + | | | | +- ObjectProperty + | | | | | +- Name + | | | | | +- Name + | | | | +- ObjectProperty + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- Name + | | | | +- Name + | | | | +- StringLiteral + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- ForInLoop + | | | +- Name + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- ParenthesizedExpression + | | | | +- InfixExpression + | | | | +- UnaryExpression + | | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- ObjectLiteral + | | | | +- ObjectProperty + | | | | | +- Name + | | | | | +- Name + | | | | +- ObjectProperty + | | | | | +- Name + | | | | | +- Name + | | | | +- ObjectProperty + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- IfStatement + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- BreakStatement + | +- IfStatement + | | +- Name + | | +- Scope + | | +- ReturnStatement + | | +- PropertyGet + | | +- Name + | | +- Name + | +- ReturnStatement + | +- ConditionalExpression + | +- Name + | +- FunctionCall + | | +- Name + | | +- Name + | +- FunctionCall + | +- PropertyGet + | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | +- Name + | +- NumberLiteral + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | | +- NumberLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- VariableInitializer + | | +- Name + | | +- StringLiteral + | +- ForLoop + | | +- EmptyExpression + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- PropertyGet + | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ReturnStatement + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- StringLiteral + | | +- VariableInitializer + | | +- Name + | | +- UnaryExpression + | | +- Name + | +- ReturnStatement + | +- ConditionalExpression + | +- PropertyGet + | | +- Name + | | +- Name + | +- FunctionNode + | | +- Name + | | +- Name + | | +- Name + | | +- Block + | | +- WhileLoop + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- Name + | | | +- Scope + | | | +- ReturnStatement + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- KeywordLiteral + | +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | +- Name + | | +- ArrayLiteral + | | +- Name + | | +- Name + | +- IfStatement + | | +- Name + | | +- Scope + | | | +- WhileLoop + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ReturnStatement + | | | +- KeywordLiteral + | | +- Scope + | | +- WhileLoop + | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- Name + | | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- Scope + | | +- IfStatement + | | +- InfixExpression + | | | +- InfixExpression + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- NumberLiteral + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- InfixExpression + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- ObjectLiteral + | | +- IfStatement + | | +- InfixExpression + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | +- Scope + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- InfixExpression + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- IfStatement + | | +- InfixExpression + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- Name + | | | +- InfixExpression + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Name + | | +- Scope + | | | +- ReturnStatement + | | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- ElementGet + | | | +- Name + | | | +- NumberLiteral + | | +- Scope + | | +- ExpressionStatement + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- IfStatement + | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | +- Scope + | | +- ReturnStatement + | | +- KeywordLiteral + | +- ReturnStatement + | +- KeywordLiteral + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- ReturnStatement + | +- ConditionalExpression + | +- InfixExpression + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- NumberLiteral + | +- FunctionNode + | | +- Name + | | +- Name + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- WhileLoop + | | | +- UnaryExpression + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | +- UnaryExpression + | | | | +- FunctionCall + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ReturnStatement + | | | +- KeywordLiteral + | | +- ReturnStatement + | | +- KeywordLiteral + | +- ElementGet + | +- Name + | +- NumberLiteral + +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | | +- NumberLiteral + | | +- VariableInitializer + | | +- Name + | | +- PropertyGet + | | +- Name + | | +- Name + | +- ForLoop + | | +- EmptyExpression + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | +- FunctionCall + | | +- Name + | | +- Name + | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ReturnStatement + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- ArrayLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- NumberLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- VariableInitializer + | | +- Name + | | +- InfixExpression + | | +- Name + | | +- KeywordLiteral + | +- ForLoop + | | +- EmptyExpression + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- IfStatement + | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- Name + | | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- Scope + | | +- IfStatement + | | +- InfixExpression + | | | +- UnaryExpression + | | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- IfStatement + | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ReturnStatement + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Name + | +- Name + | +- Name + | +- Name + | +- Block + | +- IfStatement + | | +- InfixExpression + | | | +- Name + | | | +- UnaryExpression + | | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- Name + | +- IfStatement + | | +- InfixExpression + | | | +- Name + | | | +- UnaryExpression + | | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- Name + | | +- Name + | +- ReturnStatement + | +- FunctionCall + | +- Name + | +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- ArrayLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- ArrayLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- InfixExpression + | | | | +- Name + | | | | +- StringLiteral + | | | +- ConditionalExpression + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- ArrayLiteral + | | | | | +- Name + | | | | +- Name + | | | +- ArrayLiteral + | | +- VariableInitializer + | | +- Name + | | +- ConditionalExpression + | | +- InfixExpression + | | | +- Name + | | | +- ParenthesizedExpression + | | | +- InfixExpression + | | | +- Name + | | | +- UnaryExpression + | | | +- Name + | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | +- Name + | +- IfStatement + | | +- Name + | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- ConditionalExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- ParenthesizedExpression + | | | | | +- ConditionalExpression + | | | | | +- Name + | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- ArrayLiteral + | | | | +- Name + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- Name + | +- IfStatement + | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- ArrayLiteral + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- WhileLoop + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- IfStatement + | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- Name + | | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | +- Assignment + | | +- ElementGet + | | | +- Name + | | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- UnaryExpression + | | +- ParenthesizedExpression + | | +- Assignment + | | +- ElementGet + | | | +- Name + | | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- Name + | +- IfStatement + | +- Name + | +- Scope + | | +- IfStatement + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- Scope + | | +- IfStatement + | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- ArrayLiteral + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- WhileLoop + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- Scope + | | | | +- IfStatement + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- Name + | | | +- KeywordLiteral + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- ArrayLiteral + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- WhileLoop + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- IfStatement + | | +- InfixExpression + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- ConditionalExpression + | | | | +- Name + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- UnaryExpression + | | | +- NumberLiteral + | | +- Scope + | | +- ExpressionStatement + | | +- Assignment + | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- UnaryExpression + | | +- ParenthesizedExpression + | | +- Assignment + | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- Name + | +- Scope + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- FunctionCall + | | +- Name + | | +- ConditionalExpression + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- IfStatement + | +- Name + | +- Scope + | | +- ExpressionStatement + | | +- FunctionCall + | | +- Name + | | +- KeywordLiteral + | | +- Name + | | +- Name + | | +- Name + | +- Scope + | +- ExpressionStatement + | +- FunctionCall + | +- PropertyGet + | | +- Name + | | +- Name + | +- Name + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- ElementGet + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- PropertyGet + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- ElementGet + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- StringLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- ConditionalExpression + | | | +- Name + | | | +- NumberLiteral + | | | +- NumberLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- FunctionNode + | | | | +- Name + | | | | +- Block + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- Name + | | | +- KeywordLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- FunctionNode + | | | | +- Name + | | | | +- Block + | | | | +- ReturnStatement + | | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | | +- Name + | | | | +- UnaryExpression + | | | | +- NumberLiteral + | | | +- Name + | | | +- KeywordLiteral + | | +- VariableInitializer + | | +- Name + | | +- ArrayLiteral + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- ParenthesizedExpression + | | | | +- InfixExpression + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- ParenthesizedExpression + | | | | +- InfixExpression + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- ParenthesizedExpression + | | | +- ConditionalExpression + | | | +- PropertyGet + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- KeywordLiteral + | | +- ReturnStatement + | | +- Name + | +- ForLoop + | | +- EmptyExpression + | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- IfStatement + | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- Name + | | | +- ElementGet + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- PropertyGet + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- Scope + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- ArrayLiteral + | | | +- FunctionCall + | | | +- Name + | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- ElementGet + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- PropertyGet + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- KeywordLiteral + | | | +- PropertyGet + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- IfStatement + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- UnaryExpression + | | | | +- Name + | | | +- ForLoop + | | | | +- EmptyExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- Scope + | | | | +- IfStatement + | | | | +- ElementGet + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- PropertyGet + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | +- BreakStatement + | | | +- ReturnStatement + | | | +- FunctionCall + | | | +- Name + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- FunctionCall + | | | | | | +- Name + | | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- FunctionCall + | | | | | | | | +- PropertyGet + | | | | | | | | | +- Name + | | | | | | | | | +- Name + | | | | | | | | +- NumberLiteral + | | | | | | | | +- InfixExpression + | | | | | | | | +- Name + | | | | | | | | +- NumberLiteral + | | | | | | | +- Name + | | | | | | +- ObjectLiteral + | | | | | | +- ObjectProperty + | | | | | | +- Name + | | | | | | +- ConditionalExpression + | | | | | | +- InfixExpression + | | | | | | | +- PropertyGet + | | | | | | | | +- ElementGet + | | | | | | | | | +- Name + | | | | | | | | | +- InfixExpression + | | | | | | | | | +- Name + | | | | | | | | | +- NumberLiteral + | | | | | | | | +- Name + | | | | | | | +- StringLiteral + | | | | | | +- StringLiteral + | | | | | | +- StringLiteral + | | | | | +- Name + | | | | +- Name + | | | | +- StringLiteral + | | | +- Name + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- FunctionCall + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- FunctionCall + | | | | +- Name + | | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | +- FunctionCall + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ReturnStatement + | +- FunctionCall + | +- Name + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | +- VariableInitializer + | | +- Name + | | +- FunctionNode + | | +- Name + | | +- Name + | | +- Name + | | +- Name + | | +- Name + | | +- Block + | | +- VariableDeclaration + | | | +- VariableInitializer + | | | | +- Name + | | | +- VariableInitializer + | | | | +- Name + | | | +- VariableInitializer + | | | | +- Name + | | | +- VariableInitializer + | | | | +- Name + | | | | +- NumberLiteral + | | | +- VariableInitializer + | | | | +- Name + | | | | +- StringLiteral + | | | +- VariableInitializer + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- ArrayLiteral + | | | +- VariableInitializer + | | | | +- Name + | | | | +- ArrayLiteral + | | | +- VariableInitializer + | | | | +- Name + | | | | +- Name + | | | +- VariableInitializer + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- StringLiteral + | | | | +- Name + | | | +- VariableInitializer + | | | +- Name + | | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- Name + | | | +- ConditionalExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- KeywordLiteral + | | | +- NumberLiteral + | | | +- InfixExpression + | | | +- FunctionCall + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | +- IfStatement + | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- Name + | | | +- Name + | | +- ForLoop + | | | +- EmptyExpression + | | | +- InfixExpression + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- ElementGet + | | | | | +- Name + | | | | | +- Name + | | | | +- KeywordLiteral + | | | +- UnaryExpression + | | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- IfStatement + | | | | | +- InfixExpression + | | | | | | +- UnaryExpression + | | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Scope + | | | | | +- ExpressionStatement + | | | | | | +- FunctionCall + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- WhileLoop + | | | | | +- ParenthesizedExpression + | | | | | | +- Assignment + | | | | | | +- Name + | | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- UnaryExpression + | | | | | | +- Name + | | | | | +- Scope + | | | | | +- IfStatement + | | | | | +- FunctionCall + | | | | | | +- Name + | | | | | | +- Name + | | | | | | +- InfixExpression + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Scope + | | | | | +- ExpressionStatement + | | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- BreakStatement + | | | | +- IfStatement + | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | +- Name + | | | +- Scope + | | | +- IfStatement + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- InfixExpression + | | | | | +- UnaryExpression + | | | | | | +- Name + | | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- UnaryExpression + | | | | +- Name + | | | +- IfStatement + | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- Name + | | +- IfStatement + | | | +- InfixExpression + | | | | +- Name + | | | | +- InfixExpression + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- NumberLiteral + | | | +- WhileLoop + | | | | +- ParenthesizedExpression + | | | | | +- Assignment + | | | | | +- Name + | | | | | +- ElementGet + | | | | | +- Name + | | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | | +- Name + | | | | +- Scope + | | | | +- IfStatement + | | | | | +- InfixExpression + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- Scope + | | | | | +- WhileLoop + | | | | | +- UnaryExpression + | | | | | | +- Name + | | | | | +- Scope + | | | | | +- IfStatement + | | | | | +- UnaryExpression + | | | | | | +- ParenthesizedExpression + | | | | | | +- InfixExpression + | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Scope + | | | | | +- ExpressionStatement + | | | | | +- Assignment + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | +- InfixExpression + | | | | +- Name + | | | | +- InfixExpression + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- InfixExpression + | | | | +- ParenthesizedExpression + | | | | | +- InfixExpression + | | | | | +- Name + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- NumberLiteral + | | | +- Scope + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- IfStatement + | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- Name + | | +- ReturnStatement + | | +- Name + | +- ReturnStatement + | +- ConditionalExpression + | +- Name + | +- FunctionCall + | | +- Name + | | +- Name + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- ArrayLiteral + | | +- VariableInitializer + | | | +- Name + | | | +- ArrayLiteral + | | +- VariableInitializer + | | +- Name + | | +- ElementGet + | | +- Name + | | +- InfixExpression + | | +- Name + | | +- StringLiteral + | +- IfStatement + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- IfStatement + | | | +- UnaryExpression + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- WhileLoop + | | | +- UnaryExpression + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- FunctionCall + | | | | +- Name + | | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- IfStatement + | | | +- ElementGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | +- Assignment + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ReturnStatement + | +- Name + +- FunctionNode + | +- Name + | +- Name + | +- Name + | +- Name + | +- Name + | +- Block + | +- VariableDeclaration + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | +- VariableInitializer + | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- StringLiteral + | | | +- Name + | | +- VariableInitializer + | | +- Name + | | +- InfixExpression + | | +- UnaryExpression + | | | +- Name + | | +- FunctionCall + | | +- Name + | | +- ParenthesizedExpression + | | +- Assignment + | | +- Name + | | +- InfixExpression + | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ExpressionStatement + | | +- Assignment + | | +- Name + | | +- InfixExpression + | | +- Name + | | +- ArrayLiteral + | +- IfStatement + | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | +- Scope + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- Assignment + | | | +- ElementGet + | | | | +- Name + | | | | +- NumberLiteral + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- Name + | | | +- NumberLiteral + | | +- IfStatement + | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- ParenthesizedExpression + | | | | | | | +- Assignment + | | | | | | | +- Name + | | | | | | | +- ElementGet + | | | | | | | +- Name + | | | | | | | +- NumberLiteral + | | | | | | +- Name + | | | | | +- StringLiteral + | | | | +- InfixExpression + | | | | +- InfixExpression + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- InfixExpression + | | | | +- Name + | | | | +- ElementGet + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- PropertyGet + | | | | +- ElementGet + | | | | | +- Name + | | | | | +- NumberLiteral + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- ElementGet + | | | | +- ParenthesizedExpression + | | | | | +- InfixExpression + | | | | | +- FunctionCall + | | | | | | +- PropertyGet + | | | | | | | +- PropertyGet + | | | | | | | | +- Name + | | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- FunctionCall + | | | | | | | +- PropertyGet + | | | | | | | | +- ElementGet + | | | | | | | | | +- PropertyGet + | | | | | | | | | | +- Name + | | | | | | | | | | +- Name + | | | | | | | | | +- NumberLiteral + | | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- Name + | | | | | +- ArrayLiteral + | | | | +- NumberLiteral + | | | +- IfStatement + | | | | +- UnaryExpression + | | | | | +- Name + | | | | +- Scope + | | | | | +- ReturnStatement + | | | | | +- Name + | | | | +- IfStatement + | | | | +- Name + | | | | +- Scope + | | | | +- ExpressionStatement + | | | | +- Assignment + | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- PropertyGet + | | | +- PropertyGet + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- ConditionalExpression + | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- NumberLiteral + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- WhileLoop + | | +- UnaryExpression + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- ElementGet + | | | +- Name + | | | +- Name + | | +- IfStatement + | | | +- ElementGet + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- ParenthesizedExpression + | | | | +- Assignment + | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Scope + | | | +- BreakStatement + | | +- IfStatement + | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- Name + | | | +- ElementGet + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- Scope + | | +- IfStatement + | | +- ParenthesizedExpression + | | | +- Assignment + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- ElementGet + | | | | | | +- PropertyGet + | | | | | | | +- Name + | | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- InfixExpression + | | | +- InfixExpression + | | | | +- FunctionCall + | | | | | +- PropertyGet + | | | | | | +- Name + | | | | | | +- Name + | | | | | +- PropertyGet + | | | | | +- ElementGet + | | | | | | +- Name + | | | | | | +- NumberLiteral + | | | | | +- Name + | | | | +- FunctionCall + | | | | +- Name + | | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | +- Scope + | | +- ExpressionStatement + | | | +- FunctionCall + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- Name + | | | +- NumberLiteral + | | +- ExpressionStatement + | | | +- Assignment + | | | +- Name + | | | +- InfixExpression + | | | +- PropertyGet + | | | | +- Name + | | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | +- IfStatement + | | | +- UnaryExpression + | | | | +- Name + | | | +- Scope + | | | +- ExpressionStatement + | | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | | +- Name + | | | +- ReturnStatement + | | | +- Name + | | +- BreakStatement + | +- ExpressionStatement + | | +- FunctionCall + | | +- ParenthesizedExpression + | | | +- InfixExpression + | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- Name + | | | +- Name + | | +- Name + | | +- Name + | | +- UnaryExpression + | | | +- Name + | | +- Name + | | +- InfixExpression + | | +- UnaryExpression + | | | +- Name + | | +- InfixExpression + | | +- InfixExpression + | | | +- FunctionCall + | | | | +- PropertyGet + | | | | | +- Name + | | | | | +- Name + | | | | +- Name + | | | +- FunctionCall + | | | +- Name + | | | +- PropertyGet + | | | +- Name + | | | +- Name + | | +- Name + | +- ReturnStatement + | +- Name + +- ExpressionStatement + | +- FunctionCall + | +- Name + +- ExpressionStatement + +- Assignment + +- PropertyGet + | +- Name + | +- Name + +- Name 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 index 9a56d46876..c2584110f9 100644 --- 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 @@ -6,12 +6,18 @@ package net.sourceforge.pmd.lang.jsp.ast; import net.sourceforge.pmd.lang.ast.impl.javacc.AbstractJjtreeNode; -abstract class AbstractJspNode extends AbstractJjtreeNode implements JspNode { +abstract class AbstractJspNode extends AbstractJjtreeNode implements JspNode { protected AbstractJspNode(int id) { super(id); } + + @Override // override to make protected member accessible to parser + protected void setImage(String image) { + super.setImage(image); + } + @Override public String getXPathNodeName() { return JspParserImplTreeConstants.jjtNodeName[id]; 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 index a036186852..8412c2a2de 100644 --- 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 @@ -4,25 +4,13 @@ package net.sourceforge.pmd.lang.jsp.ast; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeNode; -public interface JspNode extends Node { +public interface JspNode extends JjtreeNode { /** * Accept the visitor. */ Object jjtAccept(JspParserVisitor visitor, Object data); - - @Override - JspNode getChild(int index); - - - @Override - JspNode getParent(); - - - @Override - NodeStream children(); } diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt index 2bb150f8e2..41ef8181f4 100644 --- a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt +++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt @@ -103,9 +103,7 @@ abstract class BaseParsingHelper, T : RootNode @JvmOverloads fun getNodes(target: Class, source: String, version: String? = null): List = - ArrayList().also { - parse(source, version).findDescendantsOfType(target, it, true) - } + parse(source, version).descendants(target).crossFindBoundaries(true).toList() /** * Parses the [sourceCode] with the given [version]. This may execute diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodeExtensions.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodeExtensions.kt index 84677df759..8f85343d01 100644 --- a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodeExtensions.kt +++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodeExtensions.kt @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.ast.test import io.kotlintest.matchers.string.shouldContain -import net.sourceforge.pmd.lang.ast.AbstractNode +import net.sourceforge.pmd.lang.ast.impl.AbstractNode import net.sourceforge.pmd.lang.ast.GenericToken import net.sourceforge.pmd.lang.ast.Node import net.sourceforge.pmd.lang.ast.TextAvailableNode @@ -19,13 +19,6 @@ import java.util.* // kotlin converts getters of java types into property accessors // but it doesn't recognise jjtGet* methods as getters - -val AbstractJjtreeNode<*>.firstToken: JavaccToken - get() = jjtGetFirstToken() - -val AbstractJjtreeNode<*>.lastToken: JavaccToken - get() = jjtGetLastToken() - fun Node.safeGetChild(i: Int): Node? = when { i < numChildren -> getChild(i) else -> null diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/AbstractModelicaNode.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/AbstractModelicaNode.java index fab9f05dfb..326340d672 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/AbstractModelicaNode.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/AbstractModelicaNode.java @@ -16,7 +16,7 @@ import net.sourceforge.pmd.lang.modelica.resolver.ModelicaScope; * * @see ModelicaNode for public API. */ -abstract class AbstractModelicaNode extends AbstractJjtreeNode implements ModelicaNode { +abstract class AbstractModelicaNode extends AbstractJjtreeNode implements ModelicaNode { private ModelicaScope ownScope; @@ -24,6 +24,12 @@ abstract class AbstractModelicaNode extends AbstractJjtreeNode imp super(id); } + + @Override // override to make protected member accessible to parser + protected void setImage(String image) { + super.setImage(image); + } + @Override public abstract Object jjtAccept(ModelicaParserVisitor visitor, Object data); @@ -34,7 +40,7 @@ abstract class AbstractModelicaNode extends AbstractJjtreeNode imp @Override public ModelicaScope getContainingScope() { - return ((AbstractModelicaNode) parent).getMostSpecificScope(); + return getParent().getMostSpecificScope(); } @Override diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/ModelicaNode.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/ModelicaNode.java index 387bece368..102da32f88 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/ModelicaNode.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/ModelicaNode.java @@ -4,14 +4,13 @@ package net.sourceforge.pmd.lang.modelica.ast; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeNode; import net.sourceforge.pmd.lang.modelica.resolver.ModelicaScope; /** * Public interface for all Modelica AST nodes. */ -public interface ModelicaNode extends Node { +public interface ModelicaNode extends JjtreeNode { /** * Returns the lexical scope this node is contained in. @@ -26,15 +25,4 @@ public interface ModelicaNode extends Node { ModelicaScope getMostSpecificScope(); Object jjtAccept(ModelicaParserVisitor visitor, Object data); - - @Override - ModelicaNode getParent(); - - - @Override - ModelicaNode getChild(int index); - - - @Override - NodeStream children(); } diff --git a/pmd-modelica/src/test/kotlin/net/sourceforge/pmd/lang/modelica/ast/ModelicaCoordsTest.kt b/pmd-modelica/src/test/kotlin/net/sourceforge/pmd/lang/modelica/ast/ModelicaCoordsTest.kt index 4b8ee9f7c0..09bebd3a46 100644 --- a/pmd-modelica/src/test/kotlin/net/sourceforge/pmd/lang/modelica/ast/ModelicaCoordsTest.kt +++ b/pmd-modelica/src/test/kotlin/net/sourceforge/pmd/lang/modelica/ast/ModelicaCoordsTest.kt @@ -8,8 +8,6 @@ import io.kotlintest.should import io.kotlintest.shouldBe import io.kotlintest.specs.FunSpec import net.sourceforge.pmd.lang.ast.Node -import net.sourceforge.pmd.lang.ast.test.firstToken -import net.sourceforge.pmd.lang.ast.test.lastToken import net.sourceforge.pmd.lang.ast.test.matchNode import net.sourceforge.pmd.lang.ast.test.shouldBe import net.sourceforge.pmd.lang.modelica.ModelicaParsingHelper diff --git a/pmd-plsql/etc/grammar/PLSQL.jjt b/pmd-plsql/etc/grammar/PLSQL.jjt index 2f6c6a7dd1..877384caf4 100644 --- a/pmd-plsql/etc/grammar/PLSQL.jjt +++ b/pmd-plsql/etc/grammar/PLSQL.jjt @@ -573,17 +573,22 @@ ASTObjectNameDeclaration ObjectNameDeclaration() : } +/* + * See https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/formal-parameter-declaration.html#GUID-5BA8E033-96B9-439A-A4FC-4844FEC14AD8 + */ ASTFormalParameter FormalParameter() : { PLSQLNode simpleNode = null ; } { ( - simpleNode = ID() - // the order of outer "|" is important ! - ( LOOKAHEAD(2) ( LOOKAHEAD(2) ( (|( )) (LOOKAHEAD(2) )? ) | ) )? - ("..." | Datatype()) - ( (":" "="|<_DEFAULT>) Expression() )? + simpleNode = ID() + [ {jjtThis.setIn(true); } ] + [ {jjtThis.setOut(true); } ] + [ {jjtThis.setNoCopy(true); } ] + + ("..." | Datatype()) + ( (":" "="|<_DEFAULT>) Expression() )? ) { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } } @@ -1131,8 +1136,18 @@ ASTReadPastNextOccurrence ReadPastNextOccurrence(String target) : ASTSqlStatement SqlStatement(String initiator, String terminator) : {} { - ( + | + | + | + |{jjtThis.setType(ASTSqlStatement.Type.COMMIT); } + |{jjtThis.setType(ASTSqlStatement.Type.ROLLBACK); } + |{jjtThis.setType(ASTSqlStatement.Type.SAVEPOINT); } + |{jjtThis.setType(ASTSqlStatement.Type.SET_TRANSACTION); } + |{jjtThis.setType(ASTSqlStatement.Type.LOCK_TABLE); } + |{jjtThis.setType(ASTSqlStatement.Type.MERGE); } + |) + Skip2NextTerminator(initiator, terminator) { return jjtThis ; } @@ -2629,12 +2644,15 @@ ASTOpenStatement OpenStatement() : { return jjtThis ; } } +/* + * See https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/FETCH-statement.html#GUID-75BC6E63-841A-4103-9B96-8AC97F5C28BB + */ ASTFetchStatement FetchStatement() : {} { - QualifiedName() [ ] + QualifiedName() [ {jjtThis.setBulkCollect(true); }] //MMUE 04/08/2005 (LOOKAHEAD(functionCall()) functionCall() | QualifiedName()) ("," (LOOKAHEAD(functionCall()) functionCall() | QualifiedName()))* ";" - Expression() ("," Expression())* [ Expression()] + Expression() ("," Expression())* [ Expression(){jjtThis.setLimit(true);}] // { return jjtThis ; } } diff --git a/pmd-plsql/src/main/ant/alljavacc.xml b/pmd-plsql/src/main/ant/alljavacc.xml index 355b172aa0..180c2bbedf 100644 --- a/pmd-plsql/src/main/ant/alljavacc.xml +++ b/pmd-plsql/src/main/ant/alljavacc.xml @@ -47,6 +47,7 @@ + @@ -67,6 +68,7 @@ + diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFetchStatement.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFetchStatement.java new file mode 100644 index 0000000000..af8a000777 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFetchStatement.java @@ -0,0 +1,36 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public final class ASTFetchStatement extends AbstractPLSQLNode { + private boolean bulkcollect; + private boolean limit; + + + ASTFetchStatement(int id) { + super(id); + } + + void setBulkCollect(boolean bulkcollect) { + this.bulkcollect = bulkcollect; + } + + public boolean isBulkCollect() { + return this.bulkcollect; + } + + void setLimit(boolean limit) { + this.limit = limit; + } + + public boolean isLimit() { + return this.limit; + } + + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFormalParameter.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFormalParameter.java index e0f0bbd63c..becefff4fc 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFormalParameter.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFormalParameter.java @@ -6,10 +6,38 @@ package net.sourceforge.pmd.lang.plsql.ast; public final class ASTFormalParameter extends AbstractPLSQLNode { + private boolean in; + private boolean out; + private boolean nocopy; + ASTFormalParameter(int id) { super(id); } + public boolean isIn() { + return this.in; + } + + void setIn(boolean in) { + this.in = in; + } + + public boolean isOut() { + return this.out; + } + + void setOut(boolean out) { + this.out = out; + } + + public boolean isNoCopy() { + return this.nocopy; + } + + void setNoCopy(boolean nocopy) { + this.nocopy = nocopy; + } + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSqlStatement.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSqlStatement.java new file mode 100644 index 0000000000..d39ee7d55b --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSqlStatement.java @@ -0,0 +1,29 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public final class ASTSqlStatement extends AbstractPLSQLNode { + + private Type type; + + public enum Type { COMMIT, ROLLBACK, SAVEPOINT, SET_TRANSACTION, LOCK_TABLE, MERGE } + + ASTSqlStatement(int id) { + super(id); + } + + void setType(Type type) { + this.type = type; + } + + public Type getType() { + return this.type; + } + + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractPLSQLNode.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractPLSQLNode.java index 2cbc5fd9ee..4f3acc19d5 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractPLSQLNode.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractPLSQLNode.java @@ -4,13 +4,11 @@ package net.sourceforge.pmd.lang.plsql.ast; -import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.impl.javacc.AbstractJjtreeNode; import net.sourceforge.pmd.lang.symboltable.Scope; -@InternalApi -public abstract class AbstractPLSQLNode extends AbstractJjtreeNode implements PLSQLNode { +abstract class AbstractPLSQLNode extends AbstractJjtreeNode implements PLSQLNode { + protected Object value; protected PLSQLParser parser; protected Scope scope; @@ -19,6 +17,11 @@ public abstract class AbstractPLSQLNode extends AbstractJjtreeNode im super(i); } + @Override // override to make protected member accessible to parser + protected void setImage(String image) { + super.setImage(image); + } + protected void jjtSetValue(Object value) { this.value = value; } @@ -53,48 +56,6 @@ public abstract class AbstractPLSQLNode extends AbstractJjtreeNode im return getXPathNodeName(); } - /* - * Override this method if you want to customize how the node dumps out its - * children. - */ - - public void dump(String prefix) { - System.out.println(toString(prefix)); - for (Node child : children) { - AbstractPLSQLNode n = (AbstractPLSQLNode) child; - if (n != null) { - n.dump(prefix + " "); - } - } - } - - /** - * Return node image converted to the normal Oracle form. - * - *

    - * Normally this is uppercase, unless the names is quoted ("name"). - *

    - */ - public String getCanonicalImage() { - return PLSQLParserImpl.canonicalName(this.getImage()); - } - - /** - * Convert arbitrary String to normal Oracle format, under assumption that - * the passed image is an Oracle name. - * - *

    - * This a helper method for PLSQL classes dependent on SimpleNode, that - * would otherwise have to import PLSQParser. - *

    - * - * @param image - * @return - */ - public static String getCanonicalImage(String image) { - return PLSQLParserImpl.canonicalName(image); - } - @Override public Scope getScope() { if (scope == null) { @@ -103,8 +64,7 @@ public abstract class AbstractPLSQLNode extends AbstractJjtreeNode im return scope; } - @Override - public void setScope(Scope scope) { + void setScope(Scope scope) { this.scope = scope; } } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/InternalApiBridge.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/InternalApiBridge.java index ae2e383cc5..91387c7722 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/InternalApiBridge.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/InternalApiBridge.java @@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.plsql.ast; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; +import net.sourceforge.pmd.lang.symboltable.Scope; /** * Acts as a bridge between outer parts (e.g. symbol table) and the restricted @@ -21,6 +22,10 @@ public final class InternalApiBridge { } + public static void setScope(PLSQLNode node, Scope decl) { + ((AbstractPLSQLNode) node).setScope(decl); + } + public static void setNameDeclaration(ASTName node, NameDeclaration decl) { node.setNameDeclaration(decl); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLNode.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLNode.java index 74c303f7c5..a91fb3dfd2 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLNode.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLNode.java @@ -4,12 +4,11 @@ package net.sourceforge.pmd.lang.plsql.ast; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeNode; import net.sourceforge.pmd.lang.symboltable.Scope; import net.sourceforge.pmd.lang.symboltable.ScopedNode; -public interface PLSQLNode extends Node, ScopedNode { +public interface PLSQLNode extends ScopedNode, JjtreeNode { /** Accept the visitor. **/ Object jjtAccept(PLSQLParserVisitor visitor, Object data); @@ -17,14 +16,31 @@ public interface PLSQLNode extends Node, ScopedNode { @Override Scope getScope(); - void setScope(Scope scope); + /** + * Return node image converted to the normal Oracle form. + * + *

    + * Normally this is uppercase, unless the names is quoted ("name"). + *

    + */ + default String getCanonicalImage() { + return PLSQLParserImpl.canonicalName(this.getImage()); + } - @Override - PLSQLNode getChild(int index); - @Override - PLSQLNode getParent(); - - @Override - NodeStream children(); + /** + * Convert arbitrary String to normal Oracle format, under assumption that + * the passed image is an Oracle name. + * + *

    + * This a helper method for PLSQL classes dependent on SimpleNode, that + * would otherwise have to import PLSQParser. + *

    + * + * @param image + * @return + */ + static String getCanonicalImage(String image) { + return PLSQLParserImpl.canonicalName(image); + } } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/VariableAccessVisitor.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/VariableAccessVisitor.java index 6c42815732..459be495d4 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/VariableAccessVisitor.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/VariableAccessVisitor.java @@ -71,7 +71,7 @@ public class VariableAccessVisitor extends PLSQLParserVisitorAdapter { */ private void computeNow(Node node) { - DataFlowNode inode = node.getDataFlowNode(); + DataFlowNode inode = DataFlowNode.get(node); List undefinitions = markUsages(inode); diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/ClassScope.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/ClassScope.java index 76fb598854..57c804d731 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/ClassScope.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/ClassScope.java @@ -14,8 +14,8 @@ import java.util.logging.Logger; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.plsql.ast.ASTName; -import net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode; import net.sourceforge.pmd.lang.plsql.ast.InternalApiBridge; +import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; import net.sourceforge.pmd.lang.symboltable.AbstractScope; import net.sourceforge.pmd.lang.symboltable.Applier; import net.sourceforge.pmd.lang.symboltable.ImageFinderFunction; @@ -36,7 +36,7 @@ public class ClassScope extends AbstractScope { private String className; public ClassScope(String className) { - this.className = AbstractPLSQLNode.getCanonicalImage(className); + this.className = PLSQLNode.getCanonicalImage(className); anonymousInnerClassCounter.set(Integer.valueOf(1)); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodNameDeclaration.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodNameDeclaration.java index 50046504f9..14e6d41873 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodNameDeclaration.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodNameDeclaration.java @@ -12,7 +12,6 @@ import net.sourceforge.pmd.lang.plsql.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.plsql.ast.ASTFormalParameters; import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclarator; import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection; -import net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode; import net.sourceforge.pmd.lang.symboltable.AbstractNameDeclaration; public class MethodNameDeclaration extends AbstractNameDeclaration { @@ -123,8 +122,8 @@ public class MethodNameDeclaration extends AbstractNameDeclaration { // myTypeImg = myTypeNode.getImage(); // otherTypeImg = otherTypeNode.getImage(); // } else { - myTypeImg = ((AbstractPLSQLNode) myTypeNode.getChild(0)).getImage(); - otherTypeImg = ((AbstractPLSQLNode) otherTypeNode.getChild(0)).getImage(); + myTypeImg = myTypeNode.getChild(0).getImage(); + otherTypeImg = otherTypeNode.getChild(0).getImage(); // } if (!myTypeImg.equals(otherTypeImg)) { diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodScope.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodScope.java index cebd7ed47a..a6107c91a3 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodScope.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodScope.java @@ -11,8 +11,8 @@ import java.util.Set; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.plsql.ast.ASTName; -import net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode; import net.sourceforge.pmd.lang.plsql.ast.InternalApiBridge; +import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; import net.sourceforge.pmd.lang.symboltable.AbstractScope; import net.sourceforge.pmd.lang.symboltable.Applier; import net.sourceforge.pmd.lang.symboltable.ImageFinderFunction; @@ -21,9 +21,9 @@ import net.sourceforge.pmd.lang.symboltable.NameOccurrence; public class MethodScope extends AbstractScope { - private Node node; + private final PLSQLNode node; - public MethodScope(Node node) { + public MethodScope(PLSQLNode node) { this.node = node; } @@ -73,7 +73,7 @@ public class MethodScope extends AbstractScope { } public String getName() { - return ((AbstractPLSQLNode) node.getChild(1)).getCanonicalImage(); + return node.getChild(1).getCanonicalImage(); } @Override diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/ScopeAndDeclarationFinder.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/ScopeAndDeclarationFinder.java index 238eb12d3e..315621cb5f 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/ScopeAndDeclarationFinder.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/ScopeAndDeclarationFinder.java @@ -64,7 +64,7 @@ public class ScopeAndDeclarationFinder extends PLSQLParserVisitorAdapter { private void addScope(Scope newScope, PLSQLNode node) { newScope.setParent(scopes.peek()); scopes.push(newScope); - node.setScope(newScope); + InternalApiBridge.setScope(node, newScope); } /** @@ -133,7 +133,7 @@ public class ScopeAndDeclarationFinder extends PLSQLParserVisitorAdapter { scope = new SourceFileScope(); } scopes.push(scope); - node.setScope(scope); + InternalApiBridge.setScope(node, scope); } @Override diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ASTFetchStatementTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ASTFetchStatementTest.java new file mode 100644 index 0000000000..53ad53d938 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ASTFetchStatementTest.java @@ -0,0 +1,35 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class ASTFetchStatementTest extends AbstractPLSQLParserTst { + + @Test + public void testBulkCollectLimit() { + ASTInput input = plsql.parseResource("FetchStatementBulkCollectLimit.pls"); + List fetchStatements = input.findDescendantsOfType(ASTFetchStatement.class); + Assert.assertEquals(1, fetchStatements.size()); + ASTFetchStatement fetch = fetchStatements.get(0); + Assert.assertTrue(fetch.isBulkCollect()); + Assert.assertTrue(fetch.isLimit()); + } + + @Test + public void testFetch() { + ASTInput input = plsql.parseResource("FetchStatement.pls"); + List fetchStatements = input.findDescendantsOfType(ASTFetchStatement.class); + Assert.assertEquals(1, fetchStatements.size()); + ASTFetchStatement fetch = fetchStatements.get(0); + Assert.assertFalse(fetch.isBulkCollect()); + Assert.assertFalse(fetch.isLimit()); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ASTSqlStatementTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ASTSqlStatementTest.java new file mode 100644 index 0000000000..f395018baf --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ASTSqlStatementTest.java @@ -0,0 +1,54 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class ASTSqlStatementTest extends AbstractPLSQLParserTst { + + @Test + public void testCommit() { + ASTInput input = plsql.parseResource("CommitStatement.pls"); + List sqlStatements = input.findDescendantsOfType(ASTSqlStatement.class); + Assert.assertEquals(1, sqlStatements.size()); + assertType(sqlStatements, 0, ASTSqlStatement.Type.COMMIT); + } + + @Test + public void testRollback() { + ASTInput input = plsql.parseResource("RollbackStatement.pls"); + List sqlStatements = input.findDescendantsOfType(ASTSqlStatement.class); + Assert.assertEquals(1, sqlStatements.size()); + assertType(sqlStatements, 0, ASTSqlStatement.Type.ROLLBACK); + } + + @Test + public void testSavepoint() { + ASTInput input = plsql.parseResource("SavepointStatement.pls"); + List sqlStatements = input.findDescendantsOfType(ASTSqlStatement.class); + Assert.assertEquals(2, sqlStatements.size()); + assertType(sqlStatements, 0, ASTSqlStatement.Type.SAVEPOINT); + assertType(sqlStatements, 1, ASTSqlStatement.Type.ROLLBACK); + } + + @Test + public void testSetTransaction() { + ASTInput input = plsql.parseResource("SetTransactionStatement.pls"); + List sqlStatements = input.findDescendantsOfType(ASTSqlStatement.class); + Assert.assertEquals(3, sqlStatements.size()); + assertType(sqlStatements, 0, ASTSqlStatement.Type.COMMIT); + assertType(sqlStatements, 1, ASTSqlStatement.Type.SET_TRANSACTION); + assertType(sqlStatements, 2, ASTSqlStatement.Type.COMMIT); + } + + private void assertType(List sqlStatements, int index, ASTSqlStatement.Type expectedType) { + Assert.assertEquals(expectedType, sqlStatements.get(index).getType()); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/InOutNoCopyTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/InOutNoCopyTest.java new file mode 100644 index 0000000000..c8915329c6 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/InOutNoCopyTest.java @@ -0,0 +1,50 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class InOutNoCopyTest extends AbstractPLSQLParserTst { + + @Test + public void parseInOutNoCopy() { + ASTInput input = plsql.parseResource("InOutNoCopy.pls"); + Assert.assertNotNull(input); + List params = input.findDescendantsOfType(ASTFormalParameter.class); + Assert.assertEquals(18, params.size()); + //detailed check of first 6 test cases + Assert.assertFalse(params.get(0).isIn()); + Assert.assertFalse(params.get(0).isOut()); + Assert.assertFalse(params.get(0).isNoCopy()); + Assert.assertTrue(params.get(1).isIn()); + Assert.assertFalse(params.get(1).isOut()); + Assert.assertFalse(params.get(1).isNoCopy()); + Assert.assertFalse(params.get(2).isIn()); + Assert.assertTrue(params.get(2).isOut()); + Assert.assertFalse(params.get(2).isNoCopy()); + Assert.assertTrue(params.get(3).isIn()); + Assert.assertTrue(params.get(3).isOut()); + Assert.assertFalse(params.get(3).isNoCopy()); + Assert.assertTrue(params.get(4).isIn()); + Assert.assertTrue(params.get(4).isOut()); + Assert.assertTrue(params.get(4).isNoCopy()); + Assert.assertFalse(params.get(5).isIn()); + Assert.assertTrue(params.get(5).isOut()); + Assert.assertTrue(params.get(5).isNoCopy()); + //piecemeal test of other test cases + Assert.assertFalse(params.get(11).isIn()); + Assert.assertTrue(params.get(11).isOut()); + Assert.assertTrue(params.get(11).isNoCopy()); + Assert.assertTrue(params.get(16).isIn()); + Assert.assertTrue(params.get(16).isOut()); + Assert.assertTrue(params.get(16).isNoCopy()); + } + +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/dfa/StatementAndBraceFinderTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/dfa/StatementAndBraceFinderTest.java index e6966eac2f..95f3285850 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/dfa/StatementAndBraceFinderTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/dfa/StatementAndBraceFinderTest.java @@ -36,7 +36,7 @@ public class StatementAndBraceFinderTest extends AbstractPLSQLParserTst { @Test public void testExpressionParentChildLinks() { ASTExpression ex = getExpr(TEST1); - DataFlowNode dfn = ex.getDataFlowNode(); + DataFlowNode dfn = DataFlowNode.get(ex); assertEquals(3, dfn.getLine()); assertTrue(dfn.getNode() instanceof ASTExpression); List dfns = dfn.getParents(); @@ -46,8 +46,8 @@ public class StatementAndBraceFinderTest extends AbstractPLSQLParserTst { assertTrue(parentDfn.getNode() instanceof ASTProgramUnit); ASTProgramUnit exParent = (ASTProgramUnit) parentDfn.getNode(); // Validate the two-way link between Program Unit and Statement - assertEquals(ex, exParent.getDataFlowNode().getChildren().get(0).getNode()); - assertEquals(exParent, ex.getDataFlowNode().getParents().get(0).getNode()); + assertEquals(ex, DataFlowNode.get(exParent).getChildren().get(0).getNode()); + assertEquals(exParent, DataFlowNode.get(ex).getParents().get(0).getNode()); } @@ -55,21 +55,21 @@ public class StatementAndBraceFinderTest extends AbstractPLSQLParserTst { public void testVariableOrConstantDeclaratorParentChildLinks() { ASTVariableOrConstantDeclarator vd = plsql.getNodes(ASTVariableOrConstantDeclarator.class, TEST2).get(0); // ASTMethodDeclaration vdParent = (ASTMethodDeclaration) - // ((DataFlowNode) vd.getDataFlowNode().getParents().get(0)).getNode(); - ASTProgramUnit vdParent = (ASTProgramUnit) vd.getDataFlowNode().getParents().get(0).getNode(); + // ((DataFlowNode) DataFlowNode.get(vd).getParents().get(0)).getNode(); + ASTProgramUnit vdParent = (ASTProgramUnit) DataFlowNode.get(vd).getParents().get(0).getNode(); // Validate the two-way link between Program Unit and Variable - assertEquals(vd, vdParent.getDataFlowNode().getChildren().get(0).getNode()); - assertEquals(vdParent, vd.getDataFlowNode().getParents().get(0).getNode()); + assertEquals(vd, DataFlowNode.get(vdParent).getChildren().get(0).getNode()); + assertEquals(vdParent, DataFlowNode.get(vd).getParents().get(0).getNode()); } @Test public void testIfStmtHasCorrectTypes() { ASTExpression exp = getExpr(TEST3); - assertEquals(5, exp.getDataFlowNode().getFlow().size()); - DataFlowNode dfn = exp.getDataFlowNode().getFlow().get(2); + assertEquals(5, DataFlowNode.get(exp).getFlow().size()); + DataFlowNode dfn = DataFlowNode.get(exp).getFlow().get(2); assertTrue(dfn.isType(NodeType.IF_EXPR)); assertEquals(3, dfn.getLine()); - dfn = exp.getDataFlowNode().getFlow().get(3); + dfn = DataFlowNode.get(exp).getFlow().get(3); assertTrue(dfn.isType(NodeType.IF_LAST_STATEMENT_WITHOUT_ELSE)); assertEquals(3, dfn.getLine()); } @@ -77,9 +77,9 @@ public class StatementAndBraceFinderTest extends AbstractPLSQLParserTst { @Test public void testWhileStmtHasCorrectTypes() { ASTExpression exp = getExpr(TEST4); - DataFlowNode dfn = exp.getDataFlowNode().getFlow().get(2); + DataFlowNode dfn = DataFlowNode.get(exp).getFlow().get(2); assertTrue(dfn.isType(NodeType.WHILE_EXPR)); - dfn = exp.getDataFlowNode().getFlow().get(3); + dfn = DataFlowNode.get(exp).getFlow().get(3); assertTrue(dfn.isType(NodeType.WHILE_LAST_STATEMENT)); } @@ -87,16 +87,16 @@ public class StatementAndBraceFinderTest extends AbstractPLSQLParserTst { public void testForStmtHasCorrectTypes() { ASTExpression exp = getExpr(TEST5); DataFlowNode dfn = null; - dfn = exp.getDataFlowNode().getFlow().get(0); + dfn = DataFlowNode.get(exp).getFlow().get(0); assertTrue(dfn instanceof StartOrEndDataFlowNode); - dfn = exp.getDataFlowNode().getFlow().get(1); + dfn = DataFlowNode.get(exp).getFlow().get(1); assertTrue(dfn.getNode() instanceof ASTProgramUnit); assertEquals(2, dfn.getLine()); - dfn = exp.getDataFlowNode().getFlow().get(2); + dfn = DataFlowNode.get(exp).getFlow().get(2); assertEquals(3, dfn.getLine()); assertTrue(dfn.isType(NodeType.FOR_EXPR)); assertTrue(dfn.isType(NodeType.FOR_BEFORE_FIRST_STATEMENT)); - dfn = exp.getDataFlowNode().getFlow().get(3); + dfn = DataFlowNode.get(exp).getFlow().get(3); assertEquals(3, dfn.getLine()); assertTrue(dfn.isType(NodeType.FOR_END)); } @@ -105,24 +105,24 @@ public class StatementAndBraceFinderTest extends AbstractPLSQLParserTst { public void testSimpleCaseStmtHasCorrectTypes() { ASTExpression exp = getExpr(TEST6); DataFlowNode dfn = null; - dfn = exp.getDataFlowNode().getFlow().get(0); + dfn = DataFlowNode.get(exp).getFlow().get(0); assertTrue(dfn instanceof StartOrEndDataFlowNode); - dfn = exp.getDataFlowNode().getFlow().get(1); + dfn = DataFlowNode.get(exp).getFlow().get(1); assertEquals(2, dfn.getLine()); assertTrue(dfn.getNode() instanceof ASTProgramUnit); - dfn = exp.getDataFlowNode().getFlow().get(2); + dfn = DataFlowNode.get(exp).getFlow().get(2); assertEquals(4, dfn.getLine()); assertTrue(dfn.isType(NodeType.SWITCH_START)); assertTrue(dfn.isType(NodeType.CASE_LAST_STATEMENT)); - dfn = exp.getDataFlowNode().getFlow().get(3); + dfn = DataFlowNode.get(exp).getFlow().get(3); assertEquals(5, dfn.getLine()); assertTrue(dfn.isType(NodeType.CASE_LAST_STATEMENT)); assertTrue(dfn.isType(NodeType.BREAK_STATEMENT)); - dfn = exp.getDataFlowNode().getFlow().get(4); + dfn = DataFlowNode.get(exp).getFlow().get(4); assertEquals(6, dfn.getLine()); assertTrue(dfn.isType(NodeType.SWITCH_LAST_DEFAULT_STATEMENT)); assertTrue(dfn.isType(NodeType.BREAK_STATEMENT)); - dfn = exp.getDataFlowNode().getFlow().get(5); + dfn = DataFlowNode.get(exp).getFlow().get(5); assertEquals(7, dfn.getLine()); assertTrue(dfn.isType(NodeType.SWITCH_END)); } @@ -182,7 +182,7 @@ public class StatementAndBraceFinderTest extends AbstractPLSQLParserTst { @Test public void testLabelledStmtHasCorrectTypes() { ASTExpression exp = getExpr(TEST8); - DataFlowNode dfn = exp.getDataFlowNode().getFlow().get(2); + DataFlowNode dfn = DataFlowNode.get(exp).getFlow().get(2); assertEquals(3, dfn.getLine()); assertTrue(dfn.isType(NodeType.LABEL_STATEMENT)); } diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CommitStatement.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CommitStatement.pls new file mode 100644 index 0000000000..b8cfa86efc --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CommitStatement.pls @@ -0,0 +1,34 @@ +-- +-- Example 6-36 COMMIT Statement with COMMENT and WRITE Clauses +-- from https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/static-sql.html#GUID-56EC1B31-CA06-4460-A098-49ABD4706B9C +-- + +DROP TABLE accounts; +CREATE TABLE accounts ( + account_id NUMBER(6), + balance NUMBER (10,2) +); + +INSERT INTO accounts (account_id, balance) +VALUES (7715, 6350.00); + +INSERT INTO accounts (account_id, balance) +VALUES (7720, 5100.50); + +CREATE OR REPLACE PROCEDURE transfer ( + from_acct NUMBER, + to_acct NUMBER, + amount NUMBER +) AUTHID CURRENT_USER AS +BEGIN + UPDATE accounts + SET balance = balance - amount + WHERE account_id = from_acct; + + UPDATE accounts + SET balance = balance + amount + WHERE account_id = to_acct; + + COMMIT WRITE IMMEDIATE NOWAIT; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/FetchStatement.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/FetchStatement.pls new file mode 100644 index 0000000000..e9c05e1422 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/FetchStatement.pls @@ -0,0 +1,45 @@ +-- +-- Example 5-51 FETCH Assigns Values to Record that Function Returns +-- from https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-collections-and-records.html#GUID-CC1DA893-4087-4DA4-8B13-052AB76DAC4F +-- +DECLARE + TYPE EmpRecTyp IS RECORD ( + emp_id employees.employee_id%TYPE, + salary employees.salary%TYPE + ); + + CURSOR desc_salary RETURN EmpRecTyp IS + SELECT employee_id, salary + FROM employees + ORDER BY salary DESC; + + highest_paid_emp EmpRecTyp; + next_highest_paid_emp EmpRecTyp; + + FUNCTION nth_highest_salary (n INTEGER) RETURN EmpRecTyp IS + emp_rec EmpRecTyp; + BEGIN + OPEN desc_salary; + FOR i IN 1..n LOOP + FETCH desc_salary INTO emp_rec; + END LOOP; + CLOSE desc_salary; + RETURN emp_rec; + END nth_highest_salary; + +BEGIN + highest_paid_emp := nth_highest_salary(1); + next_highest_paid_emp := nth_highest_salary(2); + + DBMS_OUTPUT.PUT_LINE( + 'Highest Paid: #' || + highest_paid_emp.emp_id || ', $' || + highest_paid_emp.salary + ); + DBMS_OUTPUT.PUT_LINE( + 'Next Highest Paid: #' || + next_highest_paid_emp.emp_id || ', $' || + next_highest_paid_emp.salary + ); +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/FetchStatementBulkCollectLimit.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/FetchStatementBulkCollectLimit.pls new file mode 100644 index 0000000000..fa0701d849 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/FetchStatementBulkCollectLimit.pls @@ -0,0 +1,27 @@ +-- +-- Example Example 12-24 Limiting Bulk FETCH with LIMIT +-- From https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-optimization-and-tuning.html#GUID-2AD6C621-3B71-4D27-8E94-574A54BB93A6 +-- +DECLARE + TYPE numtab IS TABLE OF NUMBER INDEX BY PLS_INTEGER; + + CURSOR c1 IS + SELECT employee_id + FROM employees + WHERE department_id = 80 + ORDER BY employee_id; + + empids numtab; +BEGIN + OPEN c1; + LOOP -- Fetch 10 rows or fewer in each iteration + FETCH c1 BULK COLLECT INTO empids LIMIT 10; + DBMS_OUTPUT.PUT_LINE ('------- Results from One Bulk Fetch --------'); + FOR i IN 1..empids.COUNT LOOP + DBMS_OUTPUT.PUT_LINE ('Employee Id: ' || empids(i)); + END LOOP; + EXIT WHEN c1%NOTFOUND; + END LOOP; + CLOSE c1; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InOutNoCopy.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InOutNoCopy.pls new file mode 100644 index 0000000000..012e4b97bb --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InOutNoCopy.pls @@ -0,0 +1,38 @@ +create or replace procedure InOutNoCopyTest(blankParam varchar2, + inParam in integer, + outParam out varchar2, + inOutParam in out date, + inOutNoCopyParam in out nocopy clob, + outNoCpyParam out nocopy blob) +is +begin + null; +end InOutNoCopyTest; +/ + +create or replace package InOutNoCopyTestPck is + + procedure InOutNoCopyTest(blankParam varchar2, + inParam in integer, + outParam out varchar2, + inOutParam in out date, + inOutNoCopyParam in out nocopy clob, + outNoCpyParam out nocopy blob); + +end InOutNoCopyTestPck; +/ + +create or replace package body InOutNoCopyTestPck is + + procedure InOutNoCopyTest(blankParam varchar2, + inParam in integer, + outParam out varchar2, + inOutParam in out date, + inOutNoCopyParam in out nocopy clob, + outNoCpyParam out nocopy blob) is + begin + null; + end; + +end InOutNoCopyTestPck; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/RollbackStatement.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/RollbackStatement.pls new file mode 100644 index 0000000000..39415455ea --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/RollbackStatement.pls @@ -0,0 +1,58 @@ +-- +-- Example 6-37 ROLLBACK Statement +-- from https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/static-sql.html#GUID-B1B20D2F-C421-446C-9171-1D03E2D77BF8 +-- + +DROP TABLE emp_name; +CREATE TABLE emp_name AS + SELECT employee_id, last_name + FROM employees; + +CREATE UNIQUE INDEX empname_ix +ON emp_name (employee_id); + + +DROP TABLE emp_sal; +CREATE TABLE emp_sal AS + SELECT employee_id, salary + FROM employees; + +CREATE UNIQUE INDEX empsal_ix +ON emp_sal (employee_id); + + +DROP TABLE emp_job; +CREATE TABLE emp_job AS + SELECT employee_id, job_id + FROM employees; + +CREATE UNIQUE INDEX empjobid_ix +ON emp_job (employee_id); + + +DECLARE + emp_id NUMBER(6); + emp_lastname VARCHAR2(25); + emp_salary NUMBER(8,2); + emp_jobid VARCHAR2(10); +BEGIN + SELECT employee_id, last_name, salary, job_id + INTO emp_id, emp_lastname, emp_salary, emp_jobid + FROM employees + WHERE employee_id = 120; + + INSERT INTO emp_name (employee_id, last_name) + VALUES (emp_id, emp_lastname); + + INSERT INTO emp_sal (employee_id, salary) + VALUES (emp_id, emp_salary); + + INSERT INTO emp_job (employee_id, job_id) + VALUES (emp_id, emp_jobid); + +EXCEPTION + WHEN DUP_VAL_ON_INDEX THEN + ROLLBACK; + DBMS_OUTPUT.PUT_LINE('Inserts were rolled back'); +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SavepointStatement.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SavepointStatement.pls new file mode 100644 index 0000000000..02a22364b1 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SavepointStatement.pls @@ -0,0 +1,42 @@ +-- +-- Example 6-38 SAVEPOINT and ROLLBACK Statements +-- from: https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/static-sql.html#GUID-68924CF6-130E-497B-9DA8-7B3E4D510FA6 +-- + +DROP TABLE emp_name; +CREATE TABLE emp_name AS + SELECT employee_id, last_name, salary + FROM employees; + +CREATE UNIQUE INDEX empname_ix +ON emp_name (employee_id); + +DECLARE + emp_id employees.employee_id%TYPE; + emp_lastname employees.last_name%TYPE; + emp_salary employees.salary%TYPE; + +BEGIN + SELECT employee_id, last_name, salary + INTO emp_id, emp_lastname, emp_salary + FROM employees + WHERE employee_id = 120; + + UPDATE emp_name + SET salary = salary * 1.1 + WHERE employee_id = emp_id; + + DELETE FROM emp_name + WHERE employee_id = 130; + + SAVEPOINT do_insert; + + INSERT INTO emp_name (employee_id, last_name, salary) + VALUES (emp_id, emp_lastname, emp_salary); + +EXCEPTION + WHEN DUP_VAL_ON_INDEX THEN + ROLLBACK TO do_insert; + DBMS_OUTPUT.PUT_LINE('Insert was rolled back'); +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SetTransactionStatement.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SetTransactionStatement.pls new file mode 100644 index 0000000000..d59880a21e --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SetTransactionStatement.pls @@ -0,0 +1,30 @@ +-- +-- Example 6-40 SET TRANSACTION Statement in Read-Only Transaction +-- from https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/static-sql.html#GUID-EC9CC8B7-5DDD-4B60-83BF-686A9FD43B3D +-- +DECLARE + daily_order_total NUMBER(12,2); + weekly_order_total NUMBER(12,2); + monthly_order_total NUMBER(12,2); +BEGIN + COMMIT; -- end previous transaction + SET TRANSACTION READ ONLY NAME 'Calculate Order Totals'; + + SELECT SUM (order_total) + INTO daily_order_total + FROM orders + WHERE order_date = SYSDATE; + + SELECT SUM (order_total) + INTO weekly_order_total + FROM orders + WHERE order_date = SYSDATE - 7; + + SELECT SUM (order_total) + INTO monthly_order_total + FROM orders + WHERE order_date = SYSDATE - 30; + + COMMIT; -- ends read-only transaction +END; +/ \ No newline at end of file diff --git a/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/AbstractScalaNode.java b/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/AbstractScalaNode.java index 6772031ae6..03b426ffaa 100644 --- a/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/AbstractScalaNode.java +++ b/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/AbstractScalaNode.java @@ -4,8 +4,7 @@ package net.sourceforge.pmd.lang.scala.ast; -import net.sourceforge.pmd.lang.ast.AbstractNode; -import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.AbstractNode; import scala.meta.Tree; import scala.meta.inputs.Position; @@ -14,10 +13,10 @@ import scala.meta.inputs.Position; * A Wrapper for translating the Scala Tree Nodes to PMD-compatible Java-base * Nodes. * - * @param - * the type of the Scala tree node + * @param the type of the Scala tree node */ -abstract class AbstractScalaNode extends AbstractNode implements ScalaNode { +abstract class AbstractScalaNode extends AbstractNode, ScalaNode> implements ScalaNode { + protected final T node; private final Position pos; @@ -28,15 +27,15 @@ abstract class AbstractScalaNode extends AbstractNode implements * the scala tree node this node wraps */ AbstractScalaNode(T treeNode) { - super(0); + super(); node = treeNode; pos = node.pos(); } + // overridden to make it visible @Override - @SuppressWarnings("unchecked") - public NodeStream> children() { - return (NodeStream>) super.children(); + protected void addChild(AbstractScalaNode child, int index) { + super.addChild(child, index); } @Override @@ -64,49 +63,12 @@ abstract class AbstractScalaNode extends AbstractNode implements return pos.endColumn(); // no +1 } - @Override - @Deprecated - public void testingOnlySetBeginColumn(int i) { - throw new UnsupportedOperationException(); - } - - @Override - @Deprecated - public void testingOnlySetBeginLine(int i) { - throw new UnsupportedOperationException(); - } - - @Override - @Deprecated - public void testingOnlySetEndColumn(int i) { - throw new UnsupportedOperationException(); - } - - @Override - @Deprecated - public void testingOnlySetEndLine(int i) { - throw new UnsupportedOperationException(); - } - - @Override - public abstract R accept(ScalaParserVisitor visitor, D data); - @Override @Deprecated public T getNode() { return node; } - @Override - public ScalaNode getChild(int index) { - return (ScalaNode) super.getChild(index); - } - - @Override - public ScalaNode getParent() { - return (ScalaNode) super.getParent(); - } - @Override public String getXPathNodeName() { return node.productPrefix().replace(".", ""); diff --git a/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/ScalaNode.java b/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/ScalaNode.java index 7cd0a0c389..2852382aaa 100644 --- a/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/ScalaNode.java +++ b/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/ScalaNode.java @@ -4,8 +4,7 @@ package net.sourceforge.pmd.lang.scala.ast; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.GenericNode; import scala.meta.Tree; @@ -16,7 +15,7 @@ import scala.meta.Tree; * @param * The Scala node type that extends Scala's Tree trait */ -public interface ScalaNode extends Node { +public interface ScalaNode extends GenericNode> { /** * Accept a visitor and traverse this node. * @@ -54,16 +53,4 @@ public interface ScalaNode extends Node { // we could filter them out from violations transparently // Apex has the same problem boolean isImplicit(); - - - @Override - ScalaNode getChild(int idx); - - - @Override - ScalaNode getParent(); - - - @Override - NodeStream> children(); } diff --git a/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/ScalaTreeBuilder.java b/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/ScalaTreeBuilder.java index 6a664d19a6..32a704d91f 100644 --- a/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/ScalaTreeBuilder.java +++ b/pmd-scala/src/main/java/net/sourceforge/pmd/lang/scala/ast/ScalaTreeBuilder.java @@ -10,8 +10,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Stack; -import net.sourceforge.pmd.lang.ast.Node; - import scala.meta.Case; import scala.meta.Ctor; import scala.meta.Decl; @@ -173,7 +171,7 @@ class ScalaTreeBuilder { } // The nodes having children built. - private final Stack nodes = new Stack<>(); + private final Stack> nodes = new Stack<>(); private static void register(Class nodeType, Class> nodeAdapterType) { @@ -185,10 +183,10 @@ class ScalaTreeBuilder { } @SuppressWarnings("unchecked") - private static ScalaNode createNodeAdapter(T node) { + private static AbstractScalaNode createNodeAdapter(T node) { try { - Constructor> constructor = null; + Constructor> constructor = null; // This isInstance is unfortunately necessary as Scala gives us // access to the Interface (Trait) of classes at compile time, but @@ -197,7 +195,7 @@ class ScalaTreeBuilder { // translation between Scala Traits and Java Classes for (Class treeClass : NODE_TYPE_TO_NODE_ADAPTER_TYPE.keySet()) { if (treeClass.isInstance(node)) { - constructor = (Constructor>) NODE_TYPE_TO_NODE_ADAPTER_TYPE.get(treeClass); + constructor = (Constructor>) NODE_TYPE_TO_NODE_ADAPTER_TYPE.get(treeClass); } } @@ -229,12 +227,11 @@ class ScalaTreeBuilder { private ScalaNode buildInternal(T astNode) { // Create a Node - ScalaNode node = createNodeAdapter(astNode); + AbstractScalaNode node = createNodeAdapter(astNode); // Append to parent - Node parent = nodes.isEmpty() ? null : nodes.peek(); + AbstractScalaNode parent = nodes.isEmpty() ? null : nodes.peek(); if (parent != null) { - parent.jjtAddChild(node, parent.getNumChildren()); - node.jjtSetParent(parent); + parent.addChild(node, parent.getNumChildren()); } // Build the children... diff --git a/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/ast/Swift.g4 b/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/ast/Swift.g4 index 62747999f4..a39c2a80c6 100644 --- a/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/ast/Swift.g4 +++ b/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/ast/Swift.g4 @@ -1,5 +1,5 @@ // Downloaded on 2016/03/02 from https://github.com/sleekbyte/tailor/blob/master/src/main/antlr/com/sleekbyte/tailor/antlr/Swift.g4 - +// https://github.com/apple/swift/blob/master/CHANGELOG.md /* * [The "BSD license"] * Copyright (c) 2014 Terence Parr @@ -890,7 +890,7 @@ classRequirement: 'class' ; // GRAMMAR OF A COMPILER CONTROL STATEMENT -compilerControlStatement: conditionalCompilationBlock | lineControlStatement ; +compilerControlStatement: conditionalCompilationBlock | lineControlStatement | warningCompilationStatement ; // GRAMMAR OF A CONDITIONAL COMPILATION BLOCK @@ -927,6 +927,8 @@ lineControlStatement: '#sourceLocation' '(' 'file' ':' fileName ',' 'line' ':' l lineNumber: integerLiteral ; fileName: SingleStringLiteral ; +warningCompilationStatement: '#warning' | '#error' '(' SingleStringLiteral ')' ; + // ---------- Lexical Structure ----------- BooleanLiteral: 'true' | 'false' ; @@ -967,7 +969,7 @@ grammarString: 'red' | 'blue' | 'green' | 'alpha' | 'resourceName' | 'of' | 'type' ; OperatorHead - : '/' | '=' | '-' | '+' | '!' | '*' | '%' | '<' | '>' | '&' | '|' | '^' | '~' | '?' + : '/' | '=' | '-' | '+' | '!' | '*' | '%' | '<' | '>' | '&' | '|' | '^' | '~' | '?' | '$' | [\u00A1-\u00A7] | [\u00A9\u00AB\u00AC\u00AE] | [\u00B0-\u00B1\u00B6\u00BB\u00BF\u00D7\u00F7] diff --git a/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftParserAdapter.java b/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftParserAdapter.java index aa053859e2..495ca21c6e 100644 --- a/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftParserAdapter.java +++ b/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftParserAdapter.java @@ -12,7 +12,7 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Lexer; import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.ast.impl.antlr4.AntlrBaseNode; +import net.sourceforge.pmd.lang.ast.RootNode; import net.sourceforge.pmd.lang.ast.impl.antlr4.AntlrBaseParser; import net.sourceforge.pmd.lang.swift.ast.SwiftLexer; import net.sourceforge.pmd.lang.swift.ast.SwiftParser; @@ -27,7 +27,7 @@ public class SwiftParserAdapter extends AntlrBaseParser { } @Override - protected AntlrBaseNode getRootNode(final SwiftParser parser) { + protected RootNode getRootNode(final SwiftParser parser) { return parser.topLevel(); } diff --git a/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java b/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java index e2945ad0f3..ad9c2dbda0 100644 --- a/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java +++ b/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java @@ -6,32 +6,54 @@ package net.sourceforge.pmd.cpd; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import net.sourceforge.pmd.testframework.AbstractTokenizerTest; +@RunWith(Parameterized.class) public class SwiftTokenizerTest extends AbstractTokenizerTest { - private static final String FILENAME = "BTree.swift"; + private final String filename; + private final int nExpectedTokens; + + public SwiftTokenizerTest(String filename, int nExpectedTokens) { + this.filename = filename; + this.nExpectedTokens = nExpectedTokens; + } + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[] { "Swift5.2.swift", 90 }, + new Object[] { "Swift5.1.swift", 242 }, + new Object[] { "Swift5.0.swift", 172 }, + new Object[] { "Swift4.2.swift", 91 }, + new Object[] { "BTree.swift", 4239 } + ); + } @Before @Override public void buildTokenizer() throws IOException { this.tokenizer = new SwiftTokenizer(); - this.sourceCode = new SourceCode(new SourceCode.StringCodeLoader(this.getSampleCode(), FILENAME)); + this.sourceCode = new SourceCode(new SourceCode.StringCodeLoader(this.getSampleCode(), this.filename)); } @Override public String getSampleCode() throws IOException { - return IOUtils.toString(SwiftTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); + return IOUtils.toString(SwiftTokenizer.class.getResourceAsStream(this.filename), StandardCharsets.UTF_8); } @Test public void tokenizeTest() throws IOException { - this.expectedTokenCount = 4239; + this.expectedTokenCount = nExpectedTokens; super.tokenizeTest(); } } diff --git a/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift4.2.swift b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift4.2.swift new file mode 100644 index 0000000000..069787e16f --- /dev/null +++ b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift4.2.swift @@ -0,0 +1,26 @@ +// file for supporting swift 4.2 changes : https://github.com/apple/swift/blob/master/CHANGELOG.md#swift-42 +// can be compiled with: swift pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift4.2.swift + +let diceRoll = Int.random(in: 1 ... 6) +let randomUnit = Double.random(in: 0 ..< 1) +let randomBool = Bool.random() + +// https://github.com/apple/swift-evolution/blob/master/proposals/0193-cross-module-inlining-and-specialization.md +public class C { + public func f() {} +} + +public class Cbis { + @usableFromInline internal class D { + @usableFromInline internal func f() {} + + @inlinable internal func g() {} + } +} + +// https://github.com/apple/swift-evolution/blob/master/proposals/0196-diagnostic-directives.md +#warning("this is incomplete") + +#if MY_BUILD_CONFIG && MY_OTHER_BUILD_CONFIG + #error("MY_BUILD_CONFIG and MY_OTHER_BUILD_CONFIG cannot both be set") +#endif diff --git a/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift5.0.swift b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift5.0.swift new file mode 100644 index 0000000000..6e77f1dd29 --- /dev/null +++ b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift5.0.swift @@ -0,0 +1,40 @@ +// file for supporting swift 5.0 changes : https://github.com/apple/swift/blob/master/CHANGELOG.md#swift-5 + +// https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md +enum Result { + case success(Success) + case failure(Failure) +} + +// https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md +struct MyType {} +#if compiler(<5.0) +extension MyType : _ExpressibleByStringInterpolation { } +#else +extension MyType : ExpressibleByStringInterpolation { } +#endif + +// +func foo(_ fn: @autoclosure () -> Int) {} +func bar(_ fn: @autoclosure () -> Int) { + //foo(fn) // Incorrect, `fn` can't be forwarded and has to be called + foo(fn()) // Ok +} + +// https://github.com/apple/swift-evolution/blob/master/proposals/0216-dynamic-callable.md +@dynamicCallable +struct ToyCallable { + func dynamicallyCall(withArguments: [Int]) {} + func dynamicallyCall(withKeywordArguments: KeyValuePairs) {} +} +let toy = ToyCallable() +toy(1, 2, 3) // desugars to `x.dynamicallyCall(withArguments: [1, 2, 3])` +toy(label: 1, 2) // desugars to `x.dynamicallyCall(withKeywordArguments: ["label": 1, "": 2])` + +// https://github.com/apple/swift-evolution/blob/master/proposals/0227-identity-keypath.md +let id = \Int.self + +var x = 2 +print(x[keyPath: id]) // prints 2 +x[keyPath: id] = 3 +print(x[keyPath: id]) // prints 3 diff --git a/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift5.1.swift b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift5.1.swift new file mode 100644 index 0000000000..969ce2c5de --- /dev/null +++ b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift5.1.swift @@ -0,0 +1,73 @@ +// file for supporting swift 5.1 changes : https://github.com/apple/swift/blob/master/CHANGELOG.md#swift-5 + +// https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#property-wrapper-types-in-the-wild +// https://developer.apple.com/documentation/combine/published +// Publishing a property with the @Published attribute creates a publisher of this type. You access the publisher with the $ operator, as shown here: +import Combine +class Weather { + @Published var temperature: Double + init(temperature: Double) { + self.temperature = temperature + } +} + +let weather = Weather(temperature: 20) +let cancellable = weather.$temperature + .sink() { + print ("Temperature now: \($0)") +} +weather.temperature = 25 + +// Prints: +// Temperature now: 20.0 +// Temperature now: 25.0 + +// https://github.com/apple/swift-evolution/blob/master/proposals/0244-opaque-result-types.md +func makeMeACollection() -> some Collection { + return [1, 2, 3] +} + +// https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md +@dynamicMemberLookup +struct Lens { + let getter: () -> T + let setter: (T) -> Void + + var value: T { + get { + return getter() + } + set { + setter(newValue) + } + } + + subscript(dynamicMember keyPath: WritableKeyPath) -> Lens { + return Lens( + getter: { self.value[keyPath: keyPath] }, + setter: { self.value[keyPath: keyPath] = $0 }) + } +} + +// https://github.com/apple/swift-evolution/blob/master/proposals/0242-default-values-memberwise.md +struct Dog { + var name = "Generic dog name" + var age = 0 + + // The synthesized memberwise initializer + init(name: String = "Generic dog name", age: Int = 0) {} +} + +let sparky = Dog(name: "Sparky") // Dog(name: "Sparky", age: 0) + + +// https://bugs.swift.org/browse/SR-7799 +enum Foo { case zero, one } + +let foo: Foo? = .zero + +switch foo { + case .zero: break + case .one: break + case .none: break +} diff --git a/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift5.2.swift b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift5.2.swift new file mode 100644 index 0000000000..d249be58db --- /dev/null +++ b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/Swift5.2.swift @@ -0,0 +1,29 @@ +// file for supporting swift 5.2 changes : https://github.com/apple/swift/blob/master/CHANGELOG.md#swift-52 + +// https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md +struct Adder { + var base: Int + func callAsFunction(_ x: Int) -> Int { + return x + base + } +} +var adder = Adder(base: 3) +adder(10) // returns 13, same as `adder.callAsFunction(10)` + +// https://github.com/apple/swift-evolution/blob/master/proposals/0249-key-path-literal-function-expressions.md +struct User { + let email: String + let isAdmin: Bool +} + +users.map(\.email) // this is equivalent to: users.map { $0[keyPath: \User.email] } + +// https://bugs.swift.org/browse/SR-6118 +struct Subscriptable { + subscript(x: Int, y: Int = 0) { + ... + } +} + +let s = Subscriptable() +print(s[0]) diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java index daa198c747..2b94b5f74a 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java @@ -75,10 +75,9 @@ public class DummyLanguageModule extends BaseLanguageModule { public Parser getParser(ParserOptions parserOptions) { return new AbstractParser(parserOptions) { @Override - public Node parse(String fileName, Reader source) throws ParseException { - DummyNode node = new DummyRootNode(1); - node.testingOnlySetBeginLine(1); - node.testingOnlySetBeginColumn(1); + public DummyRootNode parse(String fileName, Reader source) throws ParseException { + DummyRootNode node = new DummyRootNode(); + node.setCoords(1, 1, 1, 2); node.setImage("Foo"); return node; } @@ -89,10 +88,6 @@ public class DummyLanguageModule extends BaseLanguageModule { private static class DummyRootNode extends DummyNode implements RootNode { - DummyRootNode(int id) { - super(id); - } - } diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/ast/DummyNode.java b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/ast/DummyNode.java index 1f7a0d4e82..d9c20a1696 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/ast/DummyNode.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/ast/DummyNode.java @@ -4,11 +4,15 @@ package net.sourceforge.pmd.test.lang.ast; -import net.sourceforge.pmd.lang.ast.AbstractNode; +import net.sourceforge.pmd.lang.ast.impl.AbstractNodeWithTextCoordinates; -public class DummyNode extends AbstractNode { - public DummyNode(int id) { - super(id); +public class DummyNode extends AbstractNodeWithTextCoordinates { + + private String image; + + @Override + public void setCoords(int bline, int bcol, int eline, int ecol) { + super.setCoords(bline, bcol, eline, ecol); } @Deprecated @@ -17,9 +21,17 @@ public class DummyNode extends AbstractNode { return "dummyNode"; } - @Override public String getXPathNodeName() { return "dummyNode"; } + + @Override + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } } diff --git a/pmd-test/src/test/java/net/sourceforge/pmd/testframework/RuleTstTest.java b/pmd-test/src/test/java/net/sourceforge/pmd/testframework/RuleTstTest.java index 533f9d4244..815d3562ce 100644 --- a/pmd-test/src/test/java/net/sourceforge/pmd/testframework/RuleTstTest.java +++ b/pmd-test/src/test/java/net/sourceforge/pmd/testframework/RuleTstTest.java @@ -64,11 +64,9 @@ public class RuleTstTest { when(rule.getName()).thenReturn("test rule"); Mockito.doAnswer(new Answer() { private RuleViolation createViolation(RuleContext context, int beginLine, String message) { - DummyNode node = new DummyNode(1); - node.testingOnlySetBeginLine(beginLine); - node.testingOnlySetBeginColumn(1); - ParametricRuleViolation violation = new ParametricRuleViolation(rule, context, node, message); - return violation; + DummyNode node = new DummyNode(); + node.setCoords(beginLine, 1, beginLine + 1, 2); + return new ParametricRuleViolation(rule, context, node, message); } @Override diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/AbstractVfNode.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/AbstractVfNode.java index e50bf2c10b..05ef601efa 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/AbstractVfNode.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/AbstractVfNode.java @@ -6,12 +6,17 @@ package net.sourceforge.pmd.lang.vf.ast; import net.sourceforge.pmd.lang.ast.impl.javacc.AbstractJjtreeNode; -abstract class AbstractVfNode extends AbstractJjtreeNode implements VfNode { +abstract class AbstractVfNode extends AbstractJjtreeNode implements VfNode { protected AbstractVfNode(int id) { super(id); } + @Override // override to make protected member accessible to parser + protected void setImage(String image) { + super.setImage(image); + } + @Override public String getXPathNodeName() { return VfParserImplTreeConstants.jjtNodeName[id]; diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/VfNode.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/VfNode.java index b6ca6ff812..e4ea42ad4e 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/VfNode.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/VfNode.java @@ -4,26 +4,13 @@ package net.sourceforge.pmd.lang.vf.ast; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeNode; -public interface VfNode extends Node { +public interface VfNode extends JjtreeNode { /** * Accept the visitor. */ Object jjtAccept(VfParserVisitor visitor, Object data); - - @Override - VfNode getParent(); - - - @Override - VfNode getChild(int i); - - - - @Override - NodeStream children(); } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/AbstractVmNode.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/AbstractVmNode.java index 9c1d69ada7..07795dc818 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/AbstractVmNode.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/AbstractVmNode.java @@ -23,18 +23,16 @@ package net.sourceforge.pmd.lang.vm.ast; import net.sourceforge.pmd.lang.ast.impl.javacc.AbstractJjtreeNode; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; -abstract class AbstractVmNode extends AbstractJjtreeNode implements VmNode { +abstract class AbstractVmNode extends AbstractJjtreeNode implements VmNode { protected AbstractVmNode(final int i) { super(i); } - public JavaccToken getFirstToken() { - return jjtGetFirstToken(); - } - public JavaccToken getLastToken() { - return jjtGetLastToken(); + @Override // override to make protected member accessible to parser + protected void setImage(String image) { + super.setImage(image); } @Override diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/VmNode.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/VmNode.java index f6c7709217..b215b0ef93 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/VmNode.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/VmNode.java @@ -4,23 +4,12 @@ package net.sourceforge.pmd.lang.vm.ast; -import net.sourceforge.pmd.lang.ast.NodeStream; -import net.sourceforge.pmd.lang.ast.TextAvailableNode; +import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeNode; -public interface VmNode extends TextAvailableNode { +public interface VmNode extends JjtreeNode { /** * Accept the visitor. */ Object jjtAccept(VmParserVisitor visitor, Object data); - - @Override - VmNode getChild(int index); - - @Override - VmNode getParent(); - - @Override - NodeStream children(); - } diff --git a/pmd-xml/pom.xml b/pmd-xml/pom.xml index c588a93bed..ff7815e99d 100644 --- a/pmd-xml/pom.xml +++ b/pmd-xml/pom.xml @@ -18,6 +18,10 @@ + + org.antlr + antlr4-maven-plugin + maven-resources-plugin @@ -30,6 +34,10 @@ + + org.antlr + antlr4-runtime + net.sourceforge.pmd pmd-core diff --git a/pmd-xml/src/main/antlr4/net/sourceforge/pmd/lang/xml/antlr4/XMLLexer.g4 b/pmd-xml/src/main/antlr4/net/sourceforge/pmd/lang/xml/antlr4/XMLLexer.g4 new file mode 100644 index 0000000000..6d4cbe8116 --- /dev/null +++ b/pmd-xml/src/main/antlr4/net/sourceforge/pmd/lang/xml/antlr4/XMLLexer.g4 @@ -0,0 +1,103 @@ +/* + [The "BSD licence"] + Copyright (c) 2013 Terence Parr + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR 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. +*/ + +/** XML lexer derived from ANTLR v4 ref guide book example */ +lexer grammar XMLLexer; + +// Default "mode": Everything OUTSIDE of a tag +COMMENT : '' ; +CDATA : '' ; +/** Scarf all DTD stuff, Entity Declarations like , + * and Notation Declarations + */ +DTD : '' -> skip ; +EntityRef : '&' Name ';' ; +CharRef : '&#' DIGIT+ ';' + | '&#x' HEXDIGIT+ ';' + ; +SEA_WS : (' '|'\t'|'\r'? '\n')+ ; + +OPEN : '<' -> pushMode(INSIDE) ; +XMLDeclOpen : ' pushMode(INSIDE) ; +SPECIAL_OPEN: ' more, pushMode(PROC_INSTR) ; + +TEXT : ~[<&]+ ; // match any 16 bit char other than < and & + +// ----------------- Everything INSIDE of a tag --------------------- +mode INSIDE; + +CLOSE : '>' -> popMode ; +SPECIAL_CLOSE: '?>' -> popMode ; // close +SLASH_CLOSE : '/>' -> popMode ; +SLASH : '/' ; +EQUALS : '=' ; +STRING : '"' ~[<"]* '"' + | '\'' ~[<']* '\'' + ; +Name : NameStartChar NameChar* ; +S : [ \t\r\n] -> skip ; + +fragment +HEXDIGIT : [a-fA-F0-9] ; + +fragment +DIGIT : [0-9] ; + +fragment +NameChar : NameStartChar + | '-' | '.' | DIGIT + | '\u00B7' + | '\u0300'..'\u036F' + | '\u203F'..'\u2040' + ; + +fragment +NameStartChar + : ':' + | [A-Z] + | '_' + | [a-z] + | [\u{C0}-\u{D6}] + | [\u{D8}-\u{F6}] + | [\u{F8}-\u{2FF}] + | [\u{370}-\u{37D}] + | [\u{37F}-\u{1FFF}] + | [\u{200C}-\u{200D}] + | [\u{2070}-\u{218F}] + | [\u{2C00}-\u{2FEF}] + | [\u{3001}-\u{D7FF}] + | [\u{F900}-\u{FDCF}] + | [\u{FDF0}-\u{FFFD}] + | [\u{10000}-\u{EFFFF}] + ; + +// ----------------- Handle --------------------- +mode PROC_INSTR; + +PI : '?>' -> popMode ; // close +IGNORE : . -> more ; diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParser.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParser.java index 1303adbb7b..7b3a9a2935 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParser.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParser.java @@ -8,9 +8,9 @@ import java.io.Reader; import net.sourceforge.pmd.lang.AbstractParser; import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.ParseException; import net.sourceforge.pmd.lang.xml.ast.internal.XmlParserImpl; +import net.sourceforge.pmd.lang.xml.ast.internal.XmlParserImpl.RootXmlNode; /** * Adapter for the XmlParser. @@ -22,7 +22,7 @@ public class XmlParser extends AbstractParser { } @Override - public Node parse(String fileName, Reader source) throws ParseException { + public RootXmlNode parse(String fileName, Reader source) throws ParseException { return new XmlParserImpl((XmlParserOptions) parserOptions).parse(source); } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNode.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNode.java index 451bf39c2a..ade3d69ba8 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNode.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNode.java @@ -4,13 +4,13 @@ package net.sourceforge.pmd.lang.xml.ast; -import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.impl.GenericNode; /** * This interface represents all XML AST nodes. They are essentially thin * wrappers around the underlying DOM nodes. */ -public interface XmlNode extends Node { +public interface XmlNode extends GenericNode { /** * Provide access to the underlying DOM node. diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/internal/XmlNodeWrapper.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/internal/XmlNodeWrapper.java index 5b84bda4fd..218c0ded6c 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/internal/XmlNodeWrapper.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/internal/XmlNodeWrapper.java @@ -11,16 +11,15 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.NodeList; import org.w3c.dom.Text; -import net.sourceforge.pmd.lang.ast.AbstractNode; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.xpath.Attribute; import net.sourceforge.pmd.lang.xml.ast.XmlNode; import net.sourceforge.pmd.util.CompoundIterator; +import net.sourceforge.pmd.util.DataMap; +import net.sourceforge.pmd.util.DataMap.DataKey; /** @@ -29,15 +28,20 @@ import net.sourceforge.pmd.util.CompoundIterator; * @author Clément Fournier * @since 6.1.0 */ -class XmlNodeWrapper extends AbstractNode implements XmlNode { +class XmlNodeWrapper implements XmlNode { + int beginLine = -1; + int endLine = -1; + int beginColumn = -1; + int endColumn = -1; + + private DataMap> dataMap; private final XmlParserImpl parser; - private Object userData; private final org.w3c.dom.Node node; XmlNodeWrapper(XmlParserImpl parser, org.w3c.dom.Node domNode) { - super(0); + super(); this.node = domNode; this.parser = parser; } @@ -63,7 +67,7 @@ class XmlNodeWrapper extends AbstractNode implements XmlNode { @Override - public Node getChild(int index) { + public XmlNode getChild(int index) { return parser.wrapDomNode(node.getChildNodes().item(index)); } @@ -89,20 +93,11 @@ class XmlNodeWrapper extends AbstractNode implements XmlNode { } @Override - public Document getAsDocument() { - throw new UnsupportedOperationException(); - } - - - @Override - public Object getUserData() { - return userData; - } - - - @Override - public void setUserData(Object userData) { - this.userData = userData; + public DataMap> getUserMap() { + if (dataMap == null) { + dataMap = DataMap.newDataMap(); + } + return dataMap; } @@ -169,6 +164,25 @@ class XmlNodeWrapper extends AbstractNode implements XmlNode { return node; } + @Override + public int getBeginLine() { + return beginLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } + + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getEndColumn() { + return endColumn; + } // package private, open only to DOMLineNumbers diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/internal/XmlParserImpl.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/internal/XmlParserImpl.java index aaa17c9fd3..ddc433af6f 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/internal/XmlParserImpl.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/internal/XmlParserImpl.java @@ -59,7 +59,7 @@ public class XmlParserImpl { } - public XmlNode parse(Reader reader) { + public RootXmlNode parse(Reader reader) { String xmlData; try { xmlData = IOUtils.toString(reader); diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/xml/cpd/XmlLanguage.java b/pmd-xml/src/main/java/net/sourceforge/pmd/xml/cpd/XmlLanguage.java new file mode 100644 index 0000000000..38b38c8eb3 --- /dev/null +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/xml/cpd/XmlLanguage.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.xml.cpd; + +import net.sourceforge.pmd.cpd.AbstractLanguage; + +public class XmlLanguage extends AbstractLanguage { + + public XmlLanguage() { + super("Xml", "xml", new XmlTokenizer(), ".xml"); + } +} diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/xml/cpd/XmlTokenizer.java b/pmd-xml/src/main/java/net/sourceforge/pmd/xml/cpd/XmlTokenizer.java new file mode 100644 index 0000000000..72c7a8d5b3 --- /dev/null +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/xml/cpd/XmlTokenizer.java @@ -0,0 +1,21 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.xml.cpd; + +import org.antlr.v4.runtime.CharStream; + +import net.sourceforge.pmd.cpd.SourceCode; +import net.sourceforge.pmd.cpd.internal.AntlrTokenizer; +import net.sourceforge.pmd.lang.ast.impl.antlr4.AntlrTokenManager; +import net.sourceforge.pmd.lang.xml.antlr4.XMLLexer; + +public class XmlTokenizer extends AntlrTokenizer { + + @Override + protected AntlrTokenManager getLexerForSource(SourceCode sourceCode) { + CharStream charStream = AntlrTokenizer.getCharStreamFromSourceCode(sourceCode); + return new AntlrTokenManager(new XMLLexer(charStream), sourceCode.getFileName()); + } +} diff --git a/pmd-xml/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-xml/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..fad9c021ea --- /dev/null +++ b/pmd-xml/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.xml.cpd.XmlLanguage diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/xml/cpd/XmlCPDTokenizerTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/xml/cpd/XmlCPDTokenizerTest.java new file mode 100644 index 0000000000..6c31bc1aac --- /dev/null +++ b/pmd-xml/src/test/java/net/sourceforge/pmd/xml/cpd/XmlCPDTokenizerTest.java @@ -0,0 +1,38 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.xml.cpd; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; + +import net.sourceforge.pmd.cpd.SourceCode; +import net.sourceforge.pmd.testframework.AbstractTokenizerTest; + +public class XmlCPDTokenizerTest extends AbstractTokenizerTest { + + private static final String FILENAME = "hello.xml"; + + @Before + @Override + public void buildTokenizer() throws IOException { + this.tokenizer = new XmlTokenizer(); + this.sourceCode = new SourceCode(new SourceCode.StringCodeLoader(this.getSampleCode(), FILENAME)); + } + + @Override + public String getSampleCode() throws IOException { + return IOUtils.toString(XmlTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); + } + + @Test + public void tokenizeTest() throws IOException { + this.expectedTokenCount = 37; + super.tokenizeTest(); + } +} diff --git a/pmd-xml/src/test/resources/net/sourceforge/pmd/xml/cpd/hello.xml b/pmd-xml/src/test/resources/net/sourceforge/pmd/xml/cpd/hello.xml new file mode 100644 index 0000000000..3b86c4c08c --- /dev/null +++ b/pmd-xml/src/test/resources/net/sourceforge/pmd/xml/cpd/hello.xml @@ -0,0 +1,5 @@ + + + Somehow we would like to improve this xml so we are not repeating the same content in this file or other files + Somehow we would like to improve this xml so we are not repeating the same content in this file or other files + \ No newline at end of file diff --git a/pom.xml b/pom.xml index cc2cf4f683..eddc28589a 100644 --- a/pom.xml +++ b/pom.xml @@ -77,9 +77,6 @@ 8 - - 1.${java.version} - 1.${java.version} 1.8 1.8 @@ -194,7 +191,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.8.1 ${java.version} @@ -450,7 +447,7 @@ - [10,) + [11,)