diff --git a/docs/pages/pmd/userdocs/cpd.md b/docs/pages/pmd/userdocs/cpd.md index 65639bd1c0..492776d5df 100644 --- a/docs/pages/pmd/userdocs/cpd.md +++ b/docs/pages/pmd/userdocs/cpd.md @@ -11,7 +11,7 @@ author: Tom Copeland Duplicate code can be hard to find, especially in a large project. But PMD's **Copy/Paste Detector (CPD)** can find it for you! -CPD works with Java, JSP, C, C++, C#, Fortran and PHP code and [some more languages](#supported-languages). +CPD works with Java, JSP, C/C++, C#, Go, Kotlin, Ruby, Swift and [many more languages](#supported-languages). It can be used via [command-line](#cli-usage), or via an [Ant task](#ant-task). It can also be run with Maven by using the `cpd-check` goal on the [Maven PMD Plugin](pmd_userdocs_tools_maven.html). @@ -210,12 +210,14 @@ This behavior has been introduced to ease CPD integration into scripts or hooks, * Apex * C# * C/C++ +* Dart * EcmaScript (JavaScript) * Fortran * Go * Groovy * Java * Jsp +* Kotlin * Matlab * Objective-C * Perl @@ -363,7 +365,7 @@ Here's a screenshot of CPD after running on the JDK 8 java.lang package: ## Suppression -Arbitrary blocks of code can be ignored through comments on **Java**, **C/C++**, **Go**, **Javascript**, +Arbitrary blocks of code can be ignored through comments on **Java**, **C/C++**, **Dart**, **Go**, **Javascript**, **Kotlin**, **Matlab**, **Objective-C**, **PL/SQL**, **Python** and **Swift** by including the keywords `CPD-OFF` and `CPD-ON`. ```java diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 8704bba206..dcec3b34b5 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -14,6 +14,16 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy +#### Dart support + +Thanks to the contribution from [Maikel Steneker](https://github.com/maikelsteneker), and built on top of the ongoing efforts to fully support Antlr-based languages, +PMD now has CPD support for [Dart](https://www.dartlang.org/). + +Being based on a proper Antlr grammar, CPD can: +* ignore comments +* ignore imports / libraries +* honor [comment-based suppressions](pmd_userdocs_cpd.html#suppression) + ### Fixed Issues * go @@ -32,6 +42,7 @@ This is a {{ site.pmd.release_type }} release. * [#1745](https://github.com/pmd/pmd/pull/1745): \[doc] Fixed some errors in docs - [0xflotus](https://github.com/0xflotus) * [#1746](https://github.com/pmd/pmd/pull/1746): \[java] Update rule to prevent UnusedImport when using JavaDoc with array type - [itaigilo](https://github.com/itaigilo) * [#1752](https://github.com/pmd/pmd/pull/1752): \[java] UseObjectForClearerAPI Only For Public - [Björn Kautler](https://github.com/Vampire) +* [#1761](https://github.com/pmd/pmd/pull/1761): \[dart] \[cpd] Added CPD support for Dart - [Maikel Steneker](https://github.com/maikelsteneker) {% endtocmaker %} diff --git a/pmd-dart/pom.xml b/pmd-dart/pom.xml new file mode 100644 index 0000000000..b0295ebf93 --- /dev/null +++ b/pmd-dart/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + pmd-dart + PMD Dart + + + net.sourceforge.pmd + pmd + 6.14.0-SNAPSHOT + + + + + + org.antlr + antlr4-maven-plugin + + + + maven-resources-plugin + + false + + ${*} + + + + + + + + + org.antlr + antlr4-runtime + + + net.sourceforge.pmd + pmd-core + + + commons-io + commons-io + + + + junit + junit + test + + + net.sourceforge.pmd + pmd-test + test + + + diff --git a/pmd-dart/src/main/antlr4/net/sourceforge/pmd/lang/dart/antlr4/Dart2.g4 b/pmd-dart/src/main/antlr4/net/sourceforge/pmd/lang/dart/antlr4/Dart2.g4 new file mode 100644 index 0000000000..1c3dffa8c6 --- /dev/null +++ b/pmd-dart/src/main/antlr4/net/sourceforge/pmd/lang/dart/antlr4/Dart2.g4 @@ -0,0 +1,1019 @@ +/* + [The "BSD licence"] + Copyright (c) 2019 Wener + 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. +*/ + +grammar Dart2; + +compilationUnit: libraryDefinition | partDeclaration; + +WHITESPACE +// : ('\t' | ' ' | NEWLINE)+ -> skip + : [ \t\r\n\u000C]+ -> skip + ; + +SEMICOLON: ';' ; + +// 8 Variables +variableDeclaration + : declaredIdentifier (',' identifier)* + ; + +declaredIdentifier + : metadata finalConstVarOrType identifier + ; +finalConstVarOrType + : 'final' type? + | 'const' type? + | varOrType + ; +varOrType + : 'var' + | type + ; + +initializedVariableDeclaration + : declaredIdentifier ('=' expression)? (','initializedIdentifier)* + ; +initializedIdentifier + : identifier ('=' expression)? + ; +initializedIdentifierList + : initializedIdentifier (',' initializedIdentifier)* + ; + + + + +// 9 Functions +functionSignature + : metadata returnType? identifier formalParameterPart + ; +formalParameterPart + : typeParameters? formalParameterList + ; +returnType + : 'void' + | type + ; + +functionBody + : 'async'? '=>' expression SEMICOLON + | ('async' | 'async*' | 'sync*')? block + ; +block + : '{' statements '}' + ; + +// 9.2 Formal Parameters +formalParameterList + : '(' ')' + | '(' normalFormalParameters ')' + | '(' normalFormalParameters (',' optionalFormalParameters)? ')' + | '(' optionalFormalParameters ')' + ; +normalFormalParameters + : normalFormalParameter (',' normalFormalParameter)* + ; +optionalFormalParameters + : optionalPositionalFormalParameters + | namedFormalParameters + ; +optionalPositionalFormalParameters + : '[' defaultFormalParameter (',' defaultFormalParameter)* ','? ']' + ; +namedFormalParameters + : '{' defaultNamedParameter (',' defaultNamedParameter)* ','? '}' + ; + +// 9.2.1 Required Formals +normalFormalParameter + : functionFormalParameter + | fieldFormalParameter + | simpleFormalParameter + ; +functionFormalParameter + : metadata COVARIANT? returnType? identifier formalParameterPart + ; +simpleFormalParameter + : declaredIdentifier + | metadata COVARIANT? identifier + ; +fieldFormalParameter + : metadata finalConstVarOrType? 'this' '.' identifier formalParameterPart? + ; + +// 9.2.2 Optional Formals +defaultFormalParameter + : normalFormalParameter ('=' expression)? + ; +defaultNamedParameter + : normalFormalParameter ('=' expression)? + | normalFormalParameter (':' expression)? + ; + +// 10 Classes +classDefinition + : metadata ABSTRACT? 'class' identifier typeParameters? + superclass? mixins? interfaces? + '{' (metadata classMemberDefinition)* '}' + | metadata ABSTRACT? 'class' mixinApplicationClass +; +mixins + : 'with' typeList + ; +classMemberDefinition + : declaration SEMICOLON + | methodSignature functionBody + ; +methodSignature + : constructorSignature initializers? + | factoryConstructorSignature + | STATIC? functionSignature + | STATIC? getterSignature + | STATIC? setterSignature + | operatorSignature + ; + + +declaration + : constantConstructorSignature (redirection | initializers)? + | constructorSignature (redirection | initializers)? + | EXTERNAL constantConstructorSignature + | EXTERNAL constructorSignature + | (EXTERNAL STATIC?)? getterSignature + | (EXTERNAL STATIC?)? setterSignature + | EXTERNAL? operatorSignature + | (EXTERNAL STATIC?)? functionSignature + | STATIC ('final' | 'const') type? staticFinalDeclarationList + | 'final' type? initializedIdentifierList + | (STATIC | COVARIANT)? ('var' | type) initializedIdentifierList + ; + +staticFinalDeclarationList + : staticFinalDeclaration (',' staticFinalDeclaration)* + ; +staticFinalDeclaration + : identifier '=' expression + ; + +// 10.1.1 Operators +operatorSignature + : returnType? OPERATOR operator formalParameterList + ; +operator + : '~' | binaryOperator | '[]' | '[]=' + ; + +binaryOperator + : multiplicativeOperator + | additiveOperator + | shiftOperator + | relationalOperator + | '==' + | bitwiseOperator + ; +// 10.2 Getters +getterSignature + : returnType? GET identifier + ; +// 10.2 Setters +setterSignature + : returnType? SET identifier formalParameterList + ; + +// 10.6 Constructors +constructorSignature + : identifier ('.' identifier)? formalParameterList + ; +redirection + : ':' 'this' ('.' identifier)? arguments + ; + +initializers + : ':' initializerListEntry (',' initializerListEntry)* + ; +initializerListEntry + : 'super' arguments + | 'super' '.' identifier arguments + | fieldInitializer + | assertion + ; +fieldInitializer + : ('this' '.')? identifier '=' conditionalExpression cascadeSection* + ; + +// 10.6.2 Factories +factoryConstructorSignature + : FACTORY identifier ('.' identifier)? formalParameterList + ; +redirectingFactoryConstructorSignature + : 'const'? FACTORY identifier ('.' identifier)? formalParameterList '=' + type ('.' identifier)? + ; +// 10.6.3 Constant Constructors +constantConstructorSignature: 'const' qualified formalParameterList; + +// 10.9 Supperclasses +superclass: 'extends' type; + +// 10.10 SUperinterfaces +interfaces: IMPLEMENTS typeList; + +// 12.1 Mixin Application +mixinApplicationClass + : identifier typeParameters? '=' mixinApplication SEMICOLON; +mixinApplication + : type mixins interfaces? + ; + +// 13 Enums +enumType + : metadata 'enum' identifier + '{' enumEntry (',' enumEntry)* ','? '}' + ; + +enumEntry + : metadata identifier + ; + +// 14 Generics +typeParameter + : metadata identifier ('extends' type)? + ; +typeParameters + : '<' typeParameter (',' typeParameter)* '>' + ; + +// 15 Metadata +metadata + : ('@' qualified ('.' identifier)? arguments?)* + ; + +// 16 Expressions +expression + : assignableExpression assignmentOperator expression + | conditionalExpression cascadeSection* + | throwExpression + ; +expressionWithoutCascade + : assignableExpression assignmentOperator expressionWithoutCascade + | conditionalExpression + | throwExpressionWithoutCascade + ; +expressionList + : expression (',' expression)* + ; +primary + : thisExpression + | 'super' unconditionalAssignableSelector + | functionExpression + | literal + | identifier + | newExpression + | constObjectExpression + | '(' expression ')' + ; + +// 16.1 Constants + +literal + : nullLiteral + | booleanLiteral + | numericLiteral + | stringLiteral + | symbolLiteral + | mapLiteral + | listLiteral + ; +nullLiteral: 'null'; + +numericLiteral + : NUMBER + | HEX_NUMBER + ; + +NUMBER + : DIGIT+ ('.' DIGIT+)? EXPONENT? + | '.' DIGIT+ EXPONENT? + ; +fragment +EXPONENT + : ('e' | 'E') ('+' | '-')? DIGIT+ + ; +HEX_NUMBER + : '0x' HEX_DIGIT+ + | '0X' HEX_DIGIT+ + ; +fragment +HEX_DIGIT + : [a-f] + | [A-F] + | DIGIT + ; + +booleanLiteral + : 'true' + | 'false' + ; + +//stringLiteral: (MultilineString | SingleLineString)+; +stringLiteral: SingleLineString; +//stringLiteral: SingleLineString; +SingleLineString + : '"' ~["]* '"' + | '\'' ~[']* '\'' +// | 'r\'' (~('\'' | NEWLINE))* '\'' // TODO +// | 'r"' (~('\'' | NEWLINE))* '"' + ; + +//MultilineString +// : '"""' StringContentTDQ* '"""' +// | '\'\'\'' StringContentTDQ* '\'\'\'' +// | 'r"""' (~'"""')* '"""' // TODO +// | 'r\'\'\'' (~'\'\'\'')* '\'\'\'' +// ; +//StringContentSQ: .;// TODO +//StringContentTDQ: .;// TODO +fragment +ESCAPE_SEQUENCE + : '\\n' + | '\\r' + | '\\f' + | '\\b' + | '\\t' + | '\\v' + | '\\x' HEX_DIGIT HEX_DIGIT + | '\\u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT + | '\\u{' HEX_DIGIT_SEQUENCE '}' + ; +fragment +HEX_DIGIT_SEQUENCE + : HEX_DIGIT HEX_DIGIT? HEX_DIGIT? + HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? + ; +/*TODO + ::= \~{}( `\\' | `"' | `$' | ) + \alt `\\' \~{}( ) + \alt + + ::= \~{}( `\\' | `\'' | `$' | ) + \alt `\\' \~{}( ) + \alt + ::= \~{}( `\\' | `"""' | `$') + \alt `\\' \~{}( ) + \alt + + ::= \~{}( `\\' | `\'\'\'' | `$') + \alt `\\' \~{}( ) + \alt +*/ +NEWLINE + : '\n' + | '\r' + | '\r\n' + ; + +// 16.5.1 String Interpolation +stringInterpolation +// : '$' IDENTIFIER_NO_DOLLAR + : '$' identifier// FIXME + | '${' expression '}' + ; + +// 16.6 Symbols +symbolLiteral + : '#' (operator | (identifier (',' identifier)*)) + ; +// 16.7 Lists +listLiteral + : 'const'? typeArguments? '[' (expressionList ','?)? ']' + ; + +// 16.8 Maps +mapLiteral + : 'const'? typeArguments? + '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}' +; +mapLiteralEntry + : expression ':' expression + ; + +// 16.9 Throw +throwExpression + : 'throw' expression + ; +throwExpressionWithoutCascade + : 'throw' expressionWithoutCascade + ; + +// 16.10 Function Expressions +functionExpression + : formalParameterPart functionBody + ; + +// 16.11 This +thisExpression: 'this'; + +// 16.12.1 New +newExpression + : 'new' type ('.' identifier)? arguments + ; + +// 16.12.2 Const +constObjectExpression + : 'const' type ('.' identifier)? arguments + ; + +// 16.14.1 Actual Argument List Evaluation +arguments + : '(' (argumentList ','?)? ')' + ; +argumentList + : namedArgument (',' namedArgument)* + | expressionList (',' namedArgument)* + ; +namedArgument + : label expression + ; + +// 16.18.2 Cascaded Invocations +cascadeSection + : '..' (cascadeSelector argumentPart*) + (assignableSelector argumentPart*)* + (assignmentOperator expressionWithoutCascade)? + ; +cascadeSelector + : '[' expression ']' + | identifier + ; +argumentPart + : typeArguments? arguments + ; + +// 16.20 Assignment +assignmentOperator + : '=' + | compoundAssignmentOperator + ; + +// 16.20.1 Compound Assignment +compoundAssignmentOperator + : '*=' + | '/=' + | '~/=' + | '%=' + | '+=' + | '<<=' + | '>>=' + | '>>>=' + | '&=' + | '^=' + | '|=' + | '??=' + ; + +// 16.21 Conditional +conditionalExpression + : ifNullExpression + ('?' expressionWithoutCascade ':' expressionWithoutCascade)? + ; +// 16.22 If-null Expression +ifNullExpression + : logicalOrExpression ('??' logicalOrExpression)* + ; + +// 16.23 Logical Boolean Expressions +logicalOrExpression + : logicalAndExpression ('||' logicalAndExpression)* + ; +logicalAndExpression + : equalityExpression ('&&' equalityExpression)* + ; + +// 16.24 Equality +equalityExpression + : relationalExpression (equalityOperator relationalExpression)? + | 'super' equalityOperator relationalExpression + ; +equalityOperator + : '==' + | '!=' + ; + +// 16.25 Relational Expressions +relationalExpression + : bitwiseOrExpression + ( + typeTest + | typeCast + | relationalOperator bitwiseOrExpression + )? + | 'super' relationalOperator bitwiseOrExpression + ; +relationalOperator + : '>=' + | '>' + | '<=' + | '<' + ; + +// 16.26 Bitwize Expression +bitwiseOrExpression + : bitwiseXorExpression ('|' bitwiseXorExpression)* + | 'super' ('|' bitwiseOrExpression)+ + ; +bitwiseXorExpression + : bitwiseAndExpression ('^' bitwiseAndExpression)* + | 'super' ('^' bitwiseAndExpression)+ + ; +bitwiseAndExpression + : shiftExpression ('&' shiftExpression)* + | 'super' ('&' shiftExpression)+ + ; +bitwiseOperator + : '&' + | '^' + | '|' + ; + +// 16.27 Shift +shiftExpression + : additiveExpression (shiftOperator additiveExpression)* + | 'super' (shiftOperator additiveExpression)+ + ; +shiftOperator + : '<<' + | '>>' + | '>>>' + ; + +// 16.28 Additive Expression +additiveExpression + : multiplicativeExpression (additiveOperator multiplicativeExpression)* + | 'super' (additiveOperator multiplicativeExpression)+ + ; +additiveOperator + : '+' + | '-' + ; +// 16.29 Multiplicative Expression +multiplicativeExpression + : unaryExpression (multiplicativeOperator unaryExpression)* + | 'super' (multiplicativeOperator unaryExpression)+ + ; +multiplicativeOperator + : '*' + | '/' + | '%' + | '~/' + ; + +// 16.30 Unary Expression +unaryExpression + : prefixOperator unaryExpression + | awaitExpression + | postfixExpression + | (minusOperator | tildeOperator) 'super' + | incrementOperator assignableExpression + ; +prefixOperator + : minusOperator + | negationOperator + | tildeOperator + ; +minusOperator: '-'; +negationOperator: '!'; +tildeOperator: '~'; + +// 16.31 Await Expressions +awaitExpression + : 'await' unaryExpression + ; + +// 16.32 Postfix Expressions +postfixExpression + : assignableExpression postfixOperator + | primary selector* + ; +postfixOperator + : incrementOperator + ; +selector + : assignableSelector + | argumentPart + ; + +incrementOperator + : '++' + | '--' + ; +// 16.33 Assignable Expressions +// NOTE +// primary (argumentPart* assignableSelector)+ -> primary (argumentPart* assignableSelector)? +assignableExpression + : primary (argumentPart* assignableSelector)? + | 'super' unconditionalAssignableSelector identifier + ; +unconditionalAssignableSelector + : '[' expression ']' + | '.' identifier + ; +assignableSelector + : unconditionalAssignableSelector + | '?.' identifier + ; + +identifier + : IDENTIFIER + ; +qualified + : identifier ('.' identifier)? + ; +// 16.35 Type Test +typeTest + : isOperator type + ; +isOperator + : 'is' '!'? + ; + +// 16.36 Type Cast +typeCast + : asOperator type + ; +asOperator + : AS + ; +// 17 Statements +statements + : statement* + ; +statement + : label* nonLabledStatment + ; +nonLabledStatment + : block + | localVariableDeclaration + | forStatement + | whileStatement + | doStatement + | switchStatement + | ifStatement + | rethrowStatment + | tryStatement + | breakStatement + | continueStatement + | returnStatement + | yieldStatement + | yieldEachStatement + | expressionStatement + | assertStatement + | localFunctionDeclaration + ; + +// 17.2 Expression Statements +expressionStatement + : expression? SEMICOLON + ; + +// 17.3 Local Variable Declaration +localVariableDeclaration + : initializedVariableDeclaration SEMICOLON + ; +// 17.4 Local Function Declaration +localFunctionDeclaration + : functionSignature functionBody + ; +// 17.5 If +ifStatement + : 'if' '(' expression ')' statement ('else' statement)? + ; + +// 17.6 For for +forStatement + : 'await'? 'for' '(' forLoopParts ')' statement + ; +forLoopParts + : forInitializerStatement expression? SEMICOLON expressionList? + | declaredIdentifier 'in' expression + | identifier 'in' expression + ; +forInitializerStatement + : localVariableDeclaration + | expression? SEMICOLON + ; + +// 17.7 While + +whileStatement + : 'while' '(' expression ')' statement + ; +// 17.8 Do +doStatement + : 'do' statement 'while' '(' expression ')' SEMICOLON + ; +// 17.9 Switch +switchStatement + : 'switch' '(' expression ')' '{' switchCase* defaultCase? '}' + ; +switchCase + : label* 'case' expression ':' statements + ; +defaultCase + : label* 'default' ':' statements + ; + +// 17.10 Rethrow +rethrowStatment + : 'rethrow' SEMICOLON + ; + +// 17.11 Try +tryStatement + : 'try' block (onPart+ finallyPart? | finallyPart) + ; +onPart + : catchPart block + | 'on' type catchPart? block + ; +catchPart + : 'catch' '(' identifier (',' identifier)? ')' + ; +finallyPart + : 'finally' block + ; + +// 17.12 Return + +returnStatement + : 'return' expression? SEMICOLON + ; + +// 17.13 Labels +label + : identifier ':' + ; + +// 17.13 Break +breakStatement + : 'break' identifier? SEMICOLON + ; + +// 17.13 Continue +continueStatement + : 'continue' identifier? SEMICOLON + ; + +// 17.16.1 Yield +yieldStatement + : 'yield' expression SEMICOLON + ; +// 17.16.1 Yield-Each +yieldEachStatement + : 'yield*' expression SEMICOLON + ; + +// 17.17 Assert +assertStatement + : assertion SEMICOLON + ; +assertion + : 'assert' '(' expression (',' expression )? ','? ')' + ; + +// 18 Libraries and Scripts +topLevelDefinition + : classDefinition + | enumType + | typeAlias + | EXTERNAL? functionSignature SEMICOLON + | EXTERNAL? getterSignature SEMICOLON + | EXTERNAL? setterSignature SEMICOLON + | functionSignature functionBody + | returnType? GET identifier functionBody + | returnType? SET identifier formalParameterList functionBody + | ('final' | 'const') type? staticFinalDeclarationList SEMICOLON + | variableDeclaration SEMICOLON + ; +getOrSet + : GET + | SET + ; +libraryDefinition + : scriptTag? libraryName? importOrExport* partDirective* + topLevelDefinition* + ; +scriptTag + : '#!' (~NEWLINE)* NEWLINE + ; + +libraryName + : metadata LIBRARY dottedIdentifierList SEMICOLON + ; +importOrExport + : libraryimport + | libraryExport + ; +dottedIdentifierList + : identifier (',' identifier)* + ; + +libraryimport + : metadata importSpecification + ; + +importSpecification + : IMPORT configurableUri (AS identifier)? combinator* SEMICOLON +// | IMPORT uri DEFERRED AS identifier combinator* SEMICOLON + ; + +combinator + : 'show' identifierList + | 'hide' identifierList + ; +identifierList + : identifier (',' identifier)* + ; + +// 18.2 Exports +libraryExport + : metadata EXPORT configurableUri combinator* SEMICOLON + ; + +// 18.3 Parts +partDirective + : metadata PART uri SEMICOLON + ; +partHeader + : metadata PART 'of' identifier ('.' identifier)* SEMICOLON + ; +partDeclaration + : partHeader topLevelDefinition* EOF + ; + +// 18.5 URIs +uri + : stringLiteral + ; +configurableUri + : uri configurationUri* + ; +configurationUri + : 'if' '(' uriTest ')' uri + ; +uriTest + : dottedIdentifierList ('==' stringLiteral)? + ; + +// 19.1 Static Types +type + : typeName typeArguments? + ; +typeName + : qualified + | 'void' // SyntaxFix + ; +typeArguments + : '<' typeList '>' + ; +typeList + : type (',' type)* + ; + +// 19.3.1 Typedef +typeAlias + : metadata TYPEDEF typeAliasBody + ; +typeAliasBody + : functionTypeAlias + ; +functionTypeAlias + : functionPrefix typeParameters? formalParameterList SEMICOLON + ; +functionPrefix + : returnType? identifier + ; + +// 20.2 Lexical Rules +// 20.1.1 Reserved Words +//assert, break, case, catch, class, const, continue, default, do, else, +//enum, extends, false, final, finally, for, if, in, is, new, null, rethrow, +//return, super, switch, this, throw, true, try, var, void, while, with. +ABSTRACT: 'abstract' ; +AS: 'as' ; +COVARIANT: 'covariant' ; +DEFERRED: 'deferred' ; +DYNAMIC: 'dynamic' ; +EXPORT: 'export' ; +EXTERNAL: 'external' ; +FACTORY: 'factory' ; +FUNCTION: 'Function' ; +GET: 'get' ; +IMPLEMENTS: 'implements' ; +IMPORT: 'import' ; +INTERFACE: 'interface' ; +LIBRARY: 'library' ; +OPERATOR: 'operator' ; +MIXIN: 'mixin' ; +PART: 'part' ; +SET: 'set' ; +STATIC: 'static' ; +TYPEDEF: 'typedef' ; + +//BUILT_IN_IDENTIFIER +// : ABSTRACT +// | AS +// | COVARIANT +// | DEFERRED +// | DYNAMIC +// | EXPORT +// | EXTERNAL +// | FACTORY +// | FUNCTION +// | GET +// | IMPLEMENTS +// | IMPORT +// | INTERFACE +// | LIBRARY +// | OPERATOR +// | MIXIN +// | PART +// | SET +// | STATIC +// | TYPEDEF +// ; + +fragment +IDENTIFIER_NO_DOLLAR + : IDENTIFIER_START_NO_DOLLAR + IDENTIFIER_PART_NO_DOLLAR* + ; +IDENTIFIER + : IDENTIFIER_START IDENTIFIER_PART* + ; + +fragment +IDENTIFIER_START + : IDENTIFIER_START_NO_DOLLAR + | '$' + ; +fragment +IDENTIFIER_START_NO_DOLLAR + : LETTER + | '_' + ; +fragment +IDENTIFIER_PART_NO_DOLLAR + : IDENTIFIER_START_NO_DOLLAR + | DIGIT + ; +fragment +IDENTIFIER_PART + : IDENTIFIER_START + | DIGIT + ; + +// 20.1.1 Reserved Words +fragment +LETTER + : [a-z] + | [A-Z] + ; +fragment +DIGIT + : [0-9] + ; +// 20.1.2 Comments +SINGLE_LINE_COMMENT +// : '//' ~(NEWLINE)* (NEWLINE)? // Origin Syntax + : '//' ~[\r\n]* -> skip + ; +MULTI_LINE_COMMENT +// : '/*' (MULTI_LINE_COMMENT | ~'*/')* '*/' // Origin Syntax + : '/*' .*? '*/' -> skip + ; + diff --git a/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartLanguage.java b/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartLanguage.java new file mode 100644 index 0000000000..1f4e8d4748 --- /dev/null +++ b/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartLanguage.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +/** + * Language implementation for Dart + */ +public class DartLanguage extends AbstractLanguage { + + /** + * Creates a new Dart Language instance. + */ + public DartLanguage() { + super("Dart", "dart", new DartTokenizer(), ".dart"); + } +} diff --git a/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartTokenizer.java b/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartTokenizer.java new file mode 100644 index 0000000000..d8a53823d6 --- /dev/null +++ b/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartTokenizer.java @@ -0,0 +1,76 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import org.antlr.v4.runtime.CharStream; + +import net.sourceforge.pmd.cpd.token.AntlrToken; +import net.sourceforge.pmd.cpd.token.AntlrTokenFilter; +import net.sourceforge.pmd.lang.antlr.AntlrTokenManager; +import net.sourceforge.pmd.lang.dart.antlr4.Dart2Lexer; + +/** + * The Dart Tokenizer + */ +public class DartTokenizer extends AntlrTokenizer { + + @Override + protected AntlrTokenManager getLexerForSource(SourceCode sourceCode) { + CharStream charStream = AntlrTokenizer.getCharStreamFromSourceCode(sourceCode); + return new AntlrTokenManager(new Dart2Lexer(charStream), sourceCode.getFileName()); + } + + @Override + protected AntlrTokenFilter getTokenFilter(final AntlrTokenManager tokenManager) { + return new DartTokenFilter(tokenManager); + } + + /** + * The {@link DartTokenFilter} extends the {@link AntlrTokenFilter} to discard + * Dart-specific tokens. + *

+ * By default, it discards package and import statements, and + * enables comment-based CPD suppression. + *

+ */ + private static class DartTokenFilter extends AntlrTokenFilter { + private boolean discardingLibraryAndImport = false; + private boolean discardingNL = false; + private boolean discardingSemicolon = false; + + /* default */ DartTokenFilter(final AntlrTokenManager tokenManager) { + super(tokenManager); + } + + @Override + protected void analyzeToken(final AntlrToken currentToken) { + skipLibraryAndImport(currentToken); + skipNewLines(currentToken); + skipSemicolons(currentToken); + } + + private void skipLibraryAndImport(final AntlrToken currentToken) { + final int type = currentToken.getType(); + if (type == Dart2Lexer.LIBRARY || type == Dart2Lexer.IMPORT) { + discardingLibraryAndImport = true; + } else if (discardingLibraryAndImport && (type == Dart2Lexer.SEMICOLON || type == Dart2Lexer.NEWLINE)) { + discardingLibraryAndImport = false; + } + } + + private void skipNewLines(final AntlrToken currentToken) { + discardingNL = currentToken.getType() == Dart2Lexer.NEWLINE; + } + + private void skipSemicolons(final AntlrToken currentToken) { + discardingSemicolon = currentToken.getType() == Dart2Lexer.SEMICOLON; + } + + @Override + protected boolean isLanguageSpecificDiscarding() { + return discardingLibraryAndImport || discardingNL || discardingSemicolon; + } + } +} diff --git a/pmd-dart/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-dart/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..9c2eb85ed5 --- /dev/null +++ b/pmd-dart/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.cpd.DartLanguage diff --git a/pmd-dart/src/test/java/net/sourceforge/pmd/cpd/DartTokenizerTest.java b/pmd-dart/src/test/java/net/sourceforge/pmd/cpd/DartTokenizerTest.java new file mode 100644 index 0000000000..397613f1fd --- /dev/null +++ b/pmd-dart/src/test/java/net/sourceforge/pmd/cpd/DartTokenizerTest.java @@ -0,0 +1,56 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.io.IOException; +import java.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 DartTokenizerTest extends AbstractTokenizerTest { + + private final String filename; + private final int nExpectedTokens; + + public DartTokenizerTest(String filename, int nExpectedTokens) { + this.filename = filename; + this.nExpectedTokens = nExpectedTokens; + } + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[] { "comment.dart", 5 }, + new Object[] { "increment.dart", 185 }, + new Object[] { "imports.dart", 1 } + ); + } + + @Before + @Override + public void buildTokenizer() throws IOException { + this.tokenizer = new DartTokenizer(); + this.sourceCode = new SourceCode(new SourceCode.StringCodeLoader(this.getSampleCode(), this.filename)); + } + + @Override + public String getSampleCode() throws IOException { + return IOUtils.toString(DartTokenizer.class.getResourceAsStream(this.filename)); + } + + @Test + public void tokenizeTest() throws IOException { + this.expectedTokenCount = nExpectedTokens; + super.tokenizeTest(); + } +} diff --git a/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/comment.dart b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/comment.dart new file mode 100644 index 0000000000..d5081bc3e5 --- /dev/null +++ b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/comment.dart @@ -0,0 +1,26 @@ +library mylibrary; + +var x = 0; + +/* +void increment1() { x += 1; } +void increment2() { x += 1; } +void increment3() { x += 1; } +void increment4() { x += 1; } +void increment5() { x += 1; } +void increment6() { x += 1; } +void increment7() { x += 1; } +void increment8() { x += 1; } +void increment9() { x += 1; } +void increment10() { x += 1; } +void increment11() { x += 1; } +void increment12() { x += 1; } +void increment13() { x += 1; } +void increment14() { x += 1; } +void increment15() { x += 1; } +void increment16() { x += 1; } +void increment17() { x += 1; } +void increment18() { x += 1; } +void increment19() { x += 1; } +void increment20() { x += 1; } +*/ \ No newline at end of file diff --git a/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/imports.dart b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/imports.dart new file mode 100644 index 0000000000..cc8aefbf2f --- /dev/null +++ b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/imports.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:english_words/english_words.dart' as EnglishWords; +import 'dart:io' deferred as io; +import 'dart:async' show AsyncError hide Completer; +import 'dart:async' hide Completer show AsyncError; +import 'dart:async' show AsyncError; +import 'dart:async' hide Completer; +import 'dart:async' show AsyncError, Completer hide ControllerCallback, ControllerCancelCallback; +import 'dart:async' show AsyncError, Completer; +import 'dart:async' hide ControllerCallback, ControllerCancelCallback; +import "package:boolean_selector/boolean_selector.dart"; +import '''package:charcode/ascii.dart''' as ascii; +import """package:collection/algorithms.dart""" as algo; +import 'dart:collection' show HashMap; +import 'dart:math' hide ln10 show ln2, cos, sin; diff --git a/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/increment.dart b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/increment.dart new file mode 100644 index 0000000000..d12e6d400f --- /dev/null +++ b/pmd-dart/src/test/resources/net/sourceforge/pmd/cpd/increment.dart @@ -0,0 +1,24 @@ +library mylibrary; + +var x = 0; + +void increment1() { x += 1; } +void increment2() { x += 1; } +void increment3() { x += 1; } +void increment4() { x += 1; } +void increment5() { x += 1; } +void increment6() { x += 1; } +void increment7() { x += 1; } +void increment8() { x += 1; } +void increment9() { x += 1; } +void increment10() { x += 1; } +void increment11() { x += 1; } +void increment12() { x += 1; } +void increment13() { x += 1; } +void increment14() { x += 1; } +void increment15() { x += 1; } +void increment16() { x += 1; } +void increment17() { x += 1; } +void increment18() { x += 1; } +void increment19() { x += 1; } +void increment20() { x += 1; } \ No newline at end of file diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index 18282ae0b5..00cc4a1a0c 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -107,6 +107,11 @@ pmd-cs ${project.version} + + net.sourceforge.pmd + pmd-dart + ${project.version} + net.sourceforge.pmd pmd-fortran 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 b37093098f..5da0b5f5a1 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -112,7 +112,7 @@ public class BinaryDistributionIT { result = CpdExecutor.runCpd(tempDir, "-h"); - result.assertExecutionResult(1, "Supported languages: [apex, cpp, cs, ecmascript, fortran, go, groovy, java, jsp, kotlin, matlab, objectivec, perl, php, plsql, python, ruby, scala, swift, vf]"); + result.assertExecutionResult(1, "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, go, groovy, java, jsp, kotlin, matlab, objectivec, perl, php, plsql, python, ruby, scala, swift, vf]"); result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "10", "--format", "text", "--files", srcDir); result.assertExecutionResult(4, "Found a 10 line (55 tokens) duplication in the following files:"); diff --git a/pom.xml b/pom.xml index 2efb4559fe..8ef49eaf73 100644 --- a/pom.xml +++ b/pom.xml @@ -1130,6 +1130,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code pmd-core pmd-cpp pmd-cs + pmd-dart pmd-dist pmd-fortran pmd-go