1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
<?php
/**
* Uses the clang format to format C/C++/Obj-C code
*/
final class FnClangFormatLinter extends ArcanistExternalLinter {
private $style;
public function getInfoName() {
return 'clang-format';
}
public function getInfoURI() {
return 'http://clang.llvm.org/docs/ClangFormat.html';
}
public function getInfoDescription() {
return pht(
'A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf code.');
}
public function getLinterName() {
return 'CLANGFORMAT';
}
public function getLinterConfigurationName() {
return 'clang-format';
}
public function getDefaultBinary() {
return 'clang-format';
}
public function getVersion() {
list($stdout) = execx('%C --version', $this->getExecutableCommand());
$matches = array();
$regex = '/^clang-format version (?P<version>\S+)/';
if (preg_match($regex, $stdout, $matches)) {
return $matches['version'];
} else {
return false;
}
}
public function getInstallInstructions() {
return pht('See http://llvm.org/releases/download.html');
}
public function getUpgradeInstructions() {
return pht('See http://llvm.org/releases/download.html. If you have the ' .
'right version installed, but it is not symlinked to ' .
'$PATH/%s, you can override the path by setting ' .
'lint.%s.bin in ~/.arcrc.',
$this->getDefaultBinary(), $this->getLinterConfigurationName());
}
public function shouldExpectCommandErrors() {
return false;
}
protected function getMandatoryFlags() {
$options = array();
$options[] = sprintf('--style=%s', coalesce($this->style, 'file'));
return $options;
}
public function getLinterConfigurationOptions() {
$options = array(
'clang-format.style' => array(
'type' => 'optional string',
'help' => pht(
'Either "file" (to use a .clang-format file in a parent directory '.
'of the file being checked), a clang-format predefined style, or a '.
'JSON dictionary of style options. See the docs.'),
),
);
return $options + parent::getLinterConfigurationOptions();
}
public function setLinterConfigurationValue($key, $value) {
switch ($key) {
case 'clang-format.style':
$this->style = $value;
return $this;
}
return parent::setLinterConfigurationValue($key, $value);
}
protected function getPathArgumentForLinterFuture($path) {
$full_path = Filesystem::resolvePath($path);
$ret = array($full_path);
// The |path| we get fed needs to be made relative to the project_root,
// otherwise the |engine| won't recognise it.
$relative_path = Filesystem::readablePath(
$full_path, $this->getProjectRoot());
$changed = $this->getEngine()->getPathChangedLines($relative_path);
if ($changed !== null && count(array_filter($changed)) > 0) {
// Convert the ordered set of changed lines to a list of ranges.
$changed_lines = array_keys(array_filter($changed));
$ranges = array(
array($changed_lines[0], $changed_lines[0]),
);
foreach (array_slice($changed_lines, 1) as $line) {
$range = last($ranges);
if ($range[1] + 1 === $line) {
++$range[1];
$ranges[last_key($ranges)] = $range;
} else {
$ranges[] = array($line, $line);
}
}
foreach ($ranges as $range) {
$ret[] = sprintf('-lines=%d:%d', $range[0], $range[1]);
}
}
return csprintf('%Ls', $ret);
}
protected function parseLinterOutput($path, $err, $stdout, $stderr) {
/* clang-format only returns a non-zero exit code on bad arguments. */
if ($err != 0)
return false;
$old_lines = phutil_split_lines($this->getData($path));
$new_lines = phutil_split_lines($stdout);
$op_codes = id(new FnSequenceMatcher($old_lines, $new_lines))->getOpCodes();
$messages = array();
foreach ($op_codes as $op_code) {
list($_op, $i1, $i2, $j1, $j2) = $op_code;
$messages[] = id(new ArcanistLintMessage())
->setBypassChangedLineFiltering(true)
->setPath($path)
->setCode($this->getLinterName())
->setSeverity(ArcanistLintSeverity::SEVERITY_AUTOFIX)
->setName('Formatting suggestion')
->setDescription(pht('%s suggests an alternative formatting.',
$this->getInfoName()))
->setLine($i1 + 1)
->setChar(1)
->setOriginalText(
implode('', array_slice($old_lines, $i1, $i2 - $i1)))
->setReplacementText(
implode('', array_slice($new_lines, $j1, $j2 - $j1)));
}
return $messages;
}
}
|