File: FnClangFormatLinter.php

package info (click to toggle)
arcanist-clang-format-linter 0.git20161021-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 112 kB
  • sloc: php: 438; makefile: 2
file content (157 lines) | stat: -rw-r--r-- 4,736 bytes parent folder | download | duplicates (2)
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;
  }
}