diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -53,6 +53,8 @@ 'ArcanistCapabilityNotSupportedException' => 'workflow/exception/ArcanistCapabilityNotSupportedException.php', 'ArcanistCastSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCastSpacingXHPASTLinterRule.php', 'ArcanistCastSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistCastSpacingXHPASTLinterRuleTestCase.php', + 'ArcanistCheckstyleLinter' => 'lint/linter/ArcanistCheckstyleLinter.php', + 'ArcanistCheckstyleLinterTestCase' => 'lint/linter/__tests__/ArcanistCheckstyleLinterTestCase.php', 'ArcanistCheckstyleXMLLintRenderer' => 'lint/renderer/ArcanistCheckstyleXMLLintRenderer.php', 'ArcanistChmodLinter' => 'lint/linter/ArcanistChmodLinter.php', 'ArcanistChmodLinterTestCase' => 'lint/linter/__tests__/ArcanistChmodLinterTestCase.php', @@ -264,6 +266,8 @@ 'ArcanistPHPOpenTagXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPHPOpenTagXHPASTLinterRuleTestCase.php', 'ArcanistPHPShortTagXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPHPShortTagXHPASTLinterRule.php', 'ArcanistPHPShortTagXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPHPShortTagXHPASTLinterRuleTestCase.php', + 'ArcanistPMDLinter' => 'lint/linter/ArcanistPMDLinter.php', + 'ArcanistPMDLinterTestCase' => 'lint/linter/__tests__/ArcanistPMDLinterTestCase.php', 'ArcanistParentMemberReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistParentMemberReferenceXHPASTLinterRule.php', 'ArcanistParentMemberReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistParentMemberReferenceXHPASTLinterRuleTestCase.php', 'ArcanistParenthesesSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php', @@ -451,6 +455,8 @@ 'ArcanistCapabilityNotSupportedException' => 'Exception', 'ArcanistCastSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistCastSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistCheckstyleLinter' => 'ArcanistExternalLinter', + 'ArcanistCheckstyleLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistCheckstyleXMLLintRenderer' => 'ArcanistLintRenderer', 'ArcanistChmodLinter' => 'ArcanistLinter', 'ArcanistChmodLinterTestCase' => 'ArcanistLinterTestCase', @@ -662,6 +668,8 @@ 'ArcanistPHPOpenTagXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistPHPShortTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistPHPShortTagXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistPMDLinter' => 'ArcanistExternalLinter', + 'ArcanistPMDLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistParentMemberReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistParentMemberReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistParenthesesSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', diff --git a/src/lint/linter/ArcanistCheckstyleLinter.php b/src/lint/linter/ArcanistCheckstyleLinter.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/ArcanistCheckstyleLinter.php @@ -0,0 +1,177 @@ + array( + 'type' => 'optional bool', + 'help' => pht( + 'Lint messages reported by checkstyle indicate their source '. + 'as the fully-qualified classname of the respective module. '. + 'Set this value to true to simplify the classname, such as: '. + '`com.company.pkg.IndentationCheck` => `IndentationCheck`.'), + ), + ); + + return $options + parent::getLinterConfigurationOptions(); + } + + public function setLinterConfigurationValue($key, $value) { + switch ($key) { + case 'checkstyle.simplify-source-classname': + $this->setSimplifySourceClassname($value); + return; + } + + return parent::setLinterConfigurationValue($key, $value); + } + + public function setSimplifySourceClassname($simplify_source_classname) { + $this->simplifySourceClassname = $simplify_source_classname; + return $this; + } + + public function getVersion() { + list($stdout) = execx('%C -v', $this->getExecutableCommand()); + + $matches = array(); + $regex = '/^Checkstyle version: (?P\d+\.\d+)$/'; + if (preg_match($regex, $stdout, $matches)) { + return $matches['version']; + } + return false; + } + + public function getInstallInstructions() { + return pht('Ensure java is configured as interpreter and '. + 'the checkstyle jar is configured as the binary. If you need to pass '. + 'additional additional JVM arguments include them with the '. + '`interpreter.flags` field. Use the `flags` field to configure '. + 'checkstyle arguments, including the `-c my_styles.xml` for '. + 'the styles to verify.'); + } + + protected function getMandatoryFlags() { + return array('-f', 'xml'); + } + + public function shouldExpectCommandErrors() { + return false; + } + + public function shouldUseInterpreter() { + return true; + } + + public function getDefaultBinary() { + return 'checkstyle.jar'; + } + + public function getDefaultInterpreter() { + return 'java'; + } + + protected function getMandatoryInterpreterFlags() { + // since the binary is the checkstyle jar, this flag must + // always be passed to the interpreter, and is guaranteed to be provided + // as the first flag before the binary jar on the command line + return array('-jar'); + } + + protected function parseLinterOutput($path, $err, $stdout, $stderr) { + $dom = new DOMDocument(); + $ok = @$dom->loadXML($stdout); + + if (!$ok) { + return false; + } + + $files = $dom->getElementsByTagName('file'); + $messages = array(); + foreach ($files as $file) { + $errors = $file->getElementsByTagName('error'); + foreach ($errors as $error) { + $message = new ArcanistLintMessage(); + $message->setPath($file->getAttribute('name')); + $message->setLine($error->getAttribute('line')); + $message->setCode($this->getLinterName()); + + // source is the module's fully-qualified classname + // attempt to simplify it for readability + $source = $error->getAttribute('source'); + if ($this->simplifySourceClassname) { + $source = idx(array_slice(explode('.', $source), -1), 0); + } + $message->setName($source); + + // checkstyle's XMLLogger escapes these five characters + $description = $error->getAttribute('message'); + $description = str_replace( + ['<', '>', ''', '"', '&'], + ['<', '>', '\'', '"', '&'], + $description); + $message->setDescription($description); + + $column = $error->getAttribute('column'); + if ($column) { + $message->setChar($column); + } + + $severity = $error->getAttribute('severity'); + switch ($severity) { + case 'error': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ERROR); + break; + case 'warning': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); + break; + case 'info': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ADVICE); + break; + case 'ignore': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_DISABLED); + break; + + // The above four are the only valid checkstyle severities, + // this is for completion as well as preparing for future severities + default: + $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); + break; + } + + $messages[] = $message; + } + } + + return $messages; + } +} diff --git a/src/lint/linter/ArcanistExternalLinter.php b/src/lint/linter/ArcanistExternalLinter.php --- a/src/lint/linter/ArcanistExternalLinter.php +++ b/src/lint/linter/ArcanistExternalLinter.php @@ -14,6 +14,7 @@ private $interpreter; private $flags; private $versionRequirement; + private $interpreterFlags; /* -( Interpreters, Binaries and Flags )----------------------------------- */ @@ -197,6 +198,52 @@ return $this; } + /** + * Return array of flags needed by the interpreter, such as "-jar" when + * using java as the interpreter. This method is only invoked if + * @{method:shouldUseInterpreter} has been overridden to return 'true'. + * + * @return array Flags to pass to interpreter + * @task bin + */ + protected function getDefaultInterpreterFlags() { + return array(); + } + + /** + * Provide mandatory, non-overridable flags to the linter interpreter. + * Generally these are arguments to configure invocation of the interpreter + * such as (assuming Java interpreter) `-Xmx1024m`, `-Dproperty=value`, or + * in the case of the binary being a jar file, the last one should be `-jar`. + * + * Due to the specific case of using java as interpreter and a jarfile as + * the binary, the `-jar` argument must appear last in the list of all + * flags passed to interpreter. For this, manadatory flags are ensured to + * always be listed before any default or user overridden flags. + * + * Flags which are not mandatory should be provided in + * @{method:getDefaultInterpreterFlags} instead. + * + * @return list Mandatory flags, like `"-jar"`. + * @task bin + */ + protected function getMandatoryInterpreterFlags() { + return array(); + } + + /** + * Override default interpreter flags with custom flags. If not overridden, + * flags provided by @{method:getDefaultInterpreterFlags} are used. + * + * @param list New flags for the interpreter. + * @return this + * @task bin + */ + final public function setInterpreterFlags(array $interpreter_flags) { + $this->interpreterFlags = $interpreter_flags; + return $this; + } + /* -( Parsing Linter Output )---------------------------------------------- */ @@ -351,14 +398,20 @@ $this->checkBinaryConfiguration(); $interpreter = null; + $interpreter_flags = array(); if ($this->shouldUseInterpreter()) { $interpreter = $this->getInterpreter(); + $interpreter_flags = $this->getInterpreterFlags(); } $binary = $this->getBinary(); if ($interpreter) { - $bin = csprintf('%s %s', $interpreter, $binary); + if (!empty($interpreter_flags)) { + $bin = csprintf('%s %Ls %s', $interpreter, $interpreter_flags, $binary); + } else { + $bin = csprintf('%s %s', $interpreter, $binary); + } } else { $bin = csprintf('%s', $binary); } @@ -379,6 +432,23 @@ nonempty($this->flags, $this->getDefaultFlags())); } + /** + * Get the composed flags for the interpreter, including both mandatory and + * configured flags. + * + * @return list Composed interpreter flags. + * @task exec + */ + final protected function getInterpreterFlags() { + // Note that this forces the mandatory flags to appear before any other + // flags provided through default or overridden. This is to handle the + // the case of java interpreter needing to combine the `-jar` flag followed + // immediately by the binary which would be the jar file. + return array_merge( + nonempty($this->interpreterFlags, $this->getDefaultInterpreterFlags()), + $this->getMandatoryInterpreterFlags()); + } + public function getCacheVersion() { try { $this->checkBinaryConfiguration(); @@ -495,6 +565,13 @@ 'a list of possible interpreters, the first one that exists '. 'will be used.'), ); + + $options['interpreter.flags'] = array( + 'type' => 'optional list', + 'help' => pht( + 'Provide a list of additional flags to pass to the interpreter on '. + 'the command line.'), + ); } return $options + parent::getLinterConfigurationOptions(); @@ -545,6 +622,9 @@ case 'flags': $this->setFlags($value); return; + case 'interpreter.flags': + $this->setInterpreterFlags($value); + return; case 'version': $this->setVersionRequirement($value); return; diff --git a/src/lint/linter/ArcanistPMDLinter.php b/src/lint/linter/ArcanistPMDLinter.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/ArcanistPMDLinter.php @@ -0,0 +1,324 @@ + array( + 'type' => 'optional string', + 'help' => pht( + 'Specify the subcommand to run, defaults to `pmd`. '. + 'Available subcommands are `pmd` or `cpd`. See the PMD '. + 'documentation for more details.'), + ), + ); + + return $options + parent::getLinterConfigurationOptions(); + } + + public function setLinterConfigurationValue($key, $value) { + switch ($key) { + case 'pmd.command': + $this->setSubCommand($value); + return; + } + + return parent::setLinterConfigurationValue($key, $value); + } + + public function setSubCommand($sub_command) { + $this->subCommand = $sub_command; + return $this; + } + + public function getVersion() { + // The pmd executable doesn't appear to actually be able to print a version + // However if you run the binary, the help output gives examples that + // appear to display the version number in paths... + $interpreter = $this->getInterpreter(); + $classpath = $this->buildClasspath(); + $mainclass = $this->getSubCommandMainClass(); + + // having the help printout for PMD returns with error code 4. + list($code, $stdout, $stderr) = exec_manual('%s -cp %s %s', + $interpreter, $classpath, $mainclass); + + $matches = array(); + $regex = '/^\$ pmd-bin-(?P\d+\.\d+\.\d+)/m'; + if (preg_match($regex, $stdout, $matches)) { + return $matches['version']; + } + return false; + } + + public function getInstallInstructions() { + return pht('Ensure java is configured as interpreter and '. + 'the pmd jar is configured as the binary. If you need to pass '. + 'additional additional JVM arguments include them with the '. + '`interpreter.flags` field. Use the `flags` field to configure pmd '. + 'arguments, including the `-rulesets my_rulesets.xml`. '. + 'You must pass the `-dir` or `--files` argument as the last flag.'); + } + + protected function getMandatoryFlags() { + $classpath = $this->buildClasspath(); + $subcommand_mainclass = $this->getSubCommandMainClass(); + $format_option = '-format'; + if ($this->subCommand == 'cpd') { + $format_option = '--format'; + } + + return array( + '-cp', + $classpath, + $subcommand_mainclass, + '-failOnViolation', + 'false', + $format_option, + 'xml', + ); + } + + public function shouldExpectCommandErrors() { + return false; + } + + public function shouldUseInterpreter() { + return true; + } + + public function getDefaultBinary() { + return 'pmd-core.jar'; + } + + public function getDefaultInterpreter() { + return 'java'; + } + + protected function getMandatoryInterpreterFlags() { + // this causes the jar binary to be interpreted as classpath + // later to be overwritten with a full classpath during execution + return array('-cp'); + } + + protected function parseLinterOutput($path, $err, $stdout, $stderr) { + $dom = new DOMDocument(); + $ok = @$dom->loadXML($stdout); + + if (!$ok) { + return false; + } + + $messages = array(); + $pmd = $dom->getElementsByTagName('pmd'); + if ($pmd) { + foreach ($pmd as $pmd_node) { + $messages = array_merge($messages, + $this->parsePmdNodeToLintMessages($pmd_node)); + } + } + + $cpd = $dom->getElementsByTagName('pmd-cpd'); + if ($cpd) { + foreach ($cpd as $cpd_node) { + $messages = array_merge($messages, + $this->parseCpdNodeToLintMessages($cpd_node, $messages)); + } + } + + return $messages; + } + + private function parsePmdNodeToLintMessages($pmd) { + $messages = array(); + + $files = $pmd->getElementsByTagName('file'); + foreach ($files as $file) { + $violations = $file->getElementsByTagName('violation'); + foreach ($violations as $violation) { + $message = new ArcanistLintMessage(); + $message->setPath($file->getAttribute('name')); + $message->setLine($violation->getAttribute('beginline')); + $message->setCode('PMD'); + + // include the ruleset and the rule + $message->setName($violation->getAttribute('ruleset'). + ': '.$violation->getAttribute('rule')); + + $description = ''; + if (property_exists($violation, 'firstChild')) { + $first_child = $violation->firstChild; + if (property_exists($first_child, 'wholeText')) { + $description = $first_child->wholeText; + } + } + + // unescape the XML written out by pmd's XMLRenderer + if ($description) { + // these 4 characters use specific XML-escape codes + $description = str_replace( + ['&', '"', '<', '>'], + ['&', '"', '<', '>'], + $description); + + // everything else is hex-code escaped + $escaped_chars = array(); + preg_replace_callback( + '/&#x(?P[a-f|A-F|0-9]+);/', + array($this, 'callbackReplaceMatchesWithHexcode'), + $description); + + $message->setDescription($description); + } + + $column = $violation->getAttribute('begincolumn'); + if ($column) { + $message->setChar($column); + } + + $severity = $violation->getAttribute('priority'); + switch ($severity) { + case '1': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ERROR); + break; + case '2': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); + break; + case '3': + case '4': + case '5': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ADVICE); + break; + + // The above five are the only valid PMD priorities, + // this is for completion as well as preparing for future severities + default: + $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); + break; + } + + $messages[] = $message; + } + } + + return $messages; + } + + private function parseCpdNodeToLintMessages($cpd_node, array $messages) { + $dups = $cpd_node->getElementsByTagName('duplication'); + foreach ($dups as $dup) { + $files = $dup->getElementsByTagName('file'); + $code_nodes = $dup->getElementsByTagName('codefragment'); + + $description = pht('Duplicated code locations:'); + foreach ($files as $file) { + $description .= + pht('\n - '). + $file->getAttribute('path'). + pht(', Line: '). + $file->getAttribute('line'); + } + + reset($files); + foreach ($files as $file) { + $message = new ArcanistLintMessage(); + $message->setPath($file->getAttribute('path')); + $message->setLine($file->getAttribute('line')); + $message->setCode('CPD'); + $message->setName('Copy/Paste Detector'); + $message->setDescription($description); + $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); + + $messages[] = $message; + } + } + + return $messages; + } + + private function buildClasspath() { + $jar_files = array(); + $lib_path = Filesystem::resolvePath($this->getBinary()); + $lib_path = substr($lib_path, 0, strrpos($lib_path, DIRECTORY_SEPARATOR)); + + $lib_path_contents = Filesystem::listDirectory($lib_path); + foreach ($lib_path_contents as $lib_path_file) { + if (preg_match('/\.jar$/', $lib_path_file)) { + $jar_files[] = $lib_path.DIRECTORY_SEPARATOR.$lib_path_file; + } + } + return implode(PATH_SEPARATOR, $jar_files); + } + + private function getSubCommandMainClass() { + $command_mainclass = ''; + switch ($this->subCommand) { + case 'pmd': + $command_mainclass = 'net.sourceforge.pmd.PMD'; + break; + case 'cpd': + $command_mainclass = 'net.sourceforge.pmd.cpd.CPD'; + break; + } + return $command_mainclass; + } + + private function callbackReplaceMatchesWithHexcode($matches) { + return $this->convertHexToBin($matches['hexcode']); + } + + /** + * This is a replacement for hex2bin() which is only available in PHP 5.4+. + * Returns the ascii interpretation of a given hexadecimal string. + * + * @param $str string The hexadecimal string to interpret + * + * @return string The string of characters represented by the given hex codes + */ + private function convertHexToBin($str) { + $sbin = ''; + $len = strlen($str); + for ($i = 0; $i < $len; $i += 2) { + $sbin .= pack('H*', substr($str, $i, 2)); + } + return $sbin; + } +} diff --git a/src/lint/linter/__tests__/ArcanistCheckstyleLinterTestCase.php b/src/lint/linter/__tests__/ArcanistCheckstyleLinterTestCase.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/ArcanistCheckstyleLinterTestCase.php @@ -0,0 +1,17 @@ +setBinary('/usr/local/bin/checkstyle-6.13-all.jar'); + $linter->setFlags( + array('-c', dirname(__FILE__).'/checkstyle/google_checks.xml')); + return $linter; + } + + public function testLinter() { + $this->executeTestsInDirectory(dirname(__FILE__).'/checkstyle/'); + } +} diff --git a/src/lint/linter/__tests__/ArcanistLinterTestCase.php b/src/lint/linter/__tests__/ArcanistLinterTestCase.php --- a/src/lint/linter/__tests__/ArcanistLinterTestCase.php +++ b/src/lint/linter/__tests__/ArcanistLinterTestCase.php @@ -60,9 +60,9 @@ '~~~~~~~~~~')); } - list($data, $expect, $xform, $config) = array_merge( + list($data, $expect, $xform, $config, $tmp_filename) = array_merge( $contents, - array(null, null)); + array(null, null, null)); $basename = basename($file); @@ -87,7 +87,10 @@ $caught_exception = false; try { - $tmp = new TempFile($basename); + if (!$tmp_filename) { + $tmp_filename = $basename; + } + $tmp = new TempFile($tmp_filename); Filesystem::writeFile($tmp, $data); $full_path = (string)$tmp; diff --git a/src/lint/linter/__tests__/ArcanistPMDLinterTestCase.php b/src/lint/linter/__tests__/ArcanistPMDLinterTestCase.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/ArcanistPMDLinterTestCase.php @@ -0,0 +1,16 @@ +setBinary('/usr/local/pmd/pmd-bin-5.4.1/lib/pmd-core-5.4.1.jar'); + return $linter; + } + + public function testLinter() { + $this->executeTestsInDirectory(dirname(__FILE__).'/pmd/'); + } + +} diff --git a/src/lint/linter/__tests__/checkstyle/google_checks.lint-test b/src/lint/linter/__tests__/checkstyle/google_checks.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/checkstyle/google_checks.lint-test @@ -0,0 +1,28 @@ +public class TestingCheckstyle { + public static void main(String[] args) + { + System.out.println() + } + + private class A{ + + @Override public String m() { return null; } + } +} +~~~~~~~~~~ +warning:1: +warning:3:3 +warning:4: +warning:7:18 +warning:9: +warning:9:31 +warning:9:46 +~~~~~~~~~~ +~~~~~~~~~~ +{ + "config": { + "checkstyle.simplify-source-classname": true + } +} +~~~~~~~~~~ +google_checks.lint-test.java \ No newline at end of file diff --git a/src/lint/linter/__tests__/checkstyle/google_checks.xml b/src/lint/linter/__tests__/checkstyle/google_checks.xml new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/checkstyle/google_checks.xml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lint/linter/__tests__/pmd/cpd.lint-test b/src/lint/linter/__tests__/pmd/cpd.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/pmd/cpd.lint-test @@ -0,0 +1,60 @@ +public class TestingCPD { + public static void main(String[] args) + { + boolean b = true; + if (b && b || !b && !b) { + System.out.println(); + } + + boolean b = true; + if (b && b || !b && !b) { + System.out.println(); + } + + boolean b = true; + if (b && b || !b && !b) { + System.out.println(); + } + + boolean b = true; + if (b && b || !b && !b) { + System.out.println(); + } + + boolean b = true; + if (b && b || !b && !b) { + System.out.println(); + } + + boolean b = true; + if (b && b || !b && !b) { + System.out.println(); + } + + boolean b = true; + if (b && b || !b && !b) { + System.out.println(); + } + } +} +~~~~~~~~~~ +warning:4: +warning:24: +warning:4: +warning:29: +warning:4: +warning:34: +~~~~~~~~~~ +~~~~~~~~~~ +{ + "config": { + "pmd.command": "cpd", + "flags": [ + "--minimum-tokens", + "25", + "--files" + ] + } +} +~~~~~~~~~~ +cpd.lint-test.java \ No newline at end of file diff --git a/src/lint/linter/__tests__/pmd/pmd.lint-test b/src/lint/linter/__tests__/pmd/pmd.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/pmd/pmd.lint-test @@ -0,0 +1,30 @@ +public class TestingPMD { + public static void main(String[] args) + { + System.out.println(); + } + + private class A{ + + @Override public String m() { return null; } + } +} +~~~~~~~~~~ +advice:1:8 +advice:7:11 +advice:7:11 +advice:9:27 +~~~~~~~~~~ +~~~~~~~~~~ +{ + "config": { + "pmd.command": "pmd", + "flags": [ + "-rulesets", + "rulesets/java/naming.xml", + "-dir" + ] + } +} +~~~~~~~~~~ +pmd.lint-test.java \ No newline at end of file