Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15396941
D14632.id35532.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
D14632.id35532.diff
View Options
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
@@ -266,6 +266,7 @@
'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',
'ArcanistParentMemberReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistParentMemberReferenceXHPASTLinterRule.php',
'ArcanistParentMemberReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistParentMemberReferenceXHPASTLinterRuleTestCase.php',
'ArcanistParenthesesSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php',
@@ -666,6 +667,7 @@
'ArcanistPHPOpenTagXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistPHPShortTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistPHPShortTagXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
+ 'ArcanistPMDLinter' => 'ArcanistExternalLinter',
'ArcanistParentMemberReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistParentMemberReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistParenthesesSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
diff --git a/src/lint/linter/ArcanistCheckstyleLinter.php b/src/lint/linter/ArcanistCheckstyleLinter.php
--- a/src/lint/linter/ArcanistCheckstyleLinter.php
+++ b/src/lint/linter/ArcanistCheckstyleLinter.php
@@ -75,8 +75,9 @@
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` field. Use the `flags` field to configure checkstyle '.
- 'arguments, including the `-c my_styles.xml` for the styles to verify.');
+ '`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() {
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,264 @@
+<?php
+
+/**
+ * This linter invokes PMD for linting java code.
+ * The PMD report is given over stdout as a simple XML document
+ * which maps fairly easily to ArcanistLintMessage.
+ *
+ * The manner of executing PMD is a little round-about in order
+ * to work around the internals of ArcanistExternalLinter.
+ *
+ * The binary is passed as a classpath argument to `java` then
+ * the binary arguments override it with a full classpath.
+ * This also enables the binary being used to build the full
+ * classpath since the PMD distribution uses multiple jars.
+ */
+class ArcanistPMDLinter extends ArcanistExternalLinter {
+
+ private $subCommand = 'pmd';
+
+ public function getInfoName() {
+ return 'Java PMD linter';
+ }
+
+ public function getLinterName() {
+ return 'PMD';
+ }
+
+ public function getInfoURI() {
+ return 'https://pmd.github.io/';
+ }
+
+ public function getInfoDescription() {
+ return pht('Use `%s` to perform static analysis on Java code.',
+ 'PMD');
+ }
+
+ public function getLinterConfigurationName() {
+ return 'pmd';
+ }
+
+ public function getLinterConfigurationOptions() {
+ $options = array(
+ 'pmd.command' => 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: '.
+ 'https://pmd.github.io/pmd-5.4.1/usage/running.html'),
+ ),
+ );
+
+ 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<version>\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` field. Use the `flags` field to configure pmd '.
+ 'arguments, including the `-rulesets my_rulesets.xml`. '.
+ 'You must pass the `-dir` argument as the last flag.');
+ }
+
+ protected function getMandatoryFlags() {
+ $classpath = $this->buildClasspath();
+ $subcommand_mainclass = $this->getSubCommandMainClass();
+ return array(
+ '-cp',
+ $classpath,
+ $subcommand_mainclass,
+ '-failOnViolation',
+ 'false',
+ '-format',
+ '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;
+ }
+
+ $files = $dom->getElementsByTagName('file');
+ $messages = array();
+ 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($this->getLinterName());
+
+ // 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<hexcode>[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 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;
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Mar 17, 5:07 PM (5 d, 9 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7708627
Default Alt Text
D14632.id35532.diff (10 KB)
Attached To
Mode
D14632: Add Java linters, checkstyle and PMD
Attached
Detach File
Event Timeline
Log In to Comment