4
* Shows lint messages to the user.
6
final class ArcanistConsoleLintRenderer extends ArcanistLintRenderer {
8
private $showAutofixPatches = false;
10
public function setShowAutofixPatches($show_autofix_patches) {
11
$this->showAutofixPatches = $show_autofix_patches;
15
public function renderLintResult(ArcanistLintResult $result) {
16
$messages = $result->getMessages();
17
$path = $result->getPath();
19
$lines = explode("\n", $result->getData());
23
foreach ($messages as $message) {
24
if (!$this->showAutofixPatches && $message->isAutofix()) {
28
if ($message->isError()) {
34
$severity = ArcanistLintSeverity::getStringForSeverity(
35
$message->getSeverity());
36
$code = $message->getCode();
37
$name = $message->getName();
38
$description = $message->getDescription();
40
if ($message->getOtherLocations()) {
42
foreach ($message->getOtherLocations() as $location) {
44
idx($location, 'path', $path).
45
(!empty($location['line']) ? ":{$location['line']}" : '');
47
$description .= "\nOther locations: ".implode(', ', $locations);
50
$text[] = phutil_console_format(
51
" **<bg:{$color}> %s </bg>** (%s) __%s__\n%s\n",
55
phutil_console_wrap($description, 4));
57
if ($message->hasFileContext()) {
58
$text[] = $this->renderContext($message, $lines);
63
$prefix = phutil_console_format("**>>>** Lint for __%s__:\n\n\n", $path);
64
return $prefix.implode("\n", $text);
70
protected function renderContext(
71
ArcanistLintMessage $message,
74
$lines_of_context = 3;
77
$num_lines = count($line_data);
78
// make line numbers line up with array indexes
79
array_unshift($line_data, '');
81
$line_num = min($message->getLine(), $num_lines);
82
$line_num = max(1, $line_num);
84
// Print out preceding context before the impacted region.
85
$cursor = max(1, $line_num - $lines_of_context);
86
for (; $cursor < $line_num; $cursor++) {
87
$out[] = $this->renderLine($cursor, $line_data[$cursor]);
90
$text = $message->getOriginalText();
91
$start = $message->getChar() - 1;
93
// Refine original and replacement text to eliminate start and end in common
94
if ($message->isPatchable()) {
95
$patch = $message->getReplacementText();
96
$text_strlen = strlen($text);
97
$patch_strlen = strlen($patch);
98
$min_length = min($text_strlen, $patch_strlen);
101
for ($ii = 0; $ii < $min_length; $ii++) {
102
if ($text[$ii] !== $patch[$ii]) {
107
if ($text[$ii] == "\n") {
108
$out[] = $this->renderLine($cursor, $line_data[$cursor]);
114
// deal with shorter string ' ' longer string ' a '
115
$min_length -= $same_at_front;
117
// And check the end of the string
119
for ($ii = 1; $ii <= $min_length; $ii++) {
120
if ($text[$text_strlen - $ii] !== $patch[$patch_strlen - $ii]) {
129
$text_strlen - $same_at_end - $same_at_front);
133
$patch_strlen - $same_at_end - $same_at_front);
135
// Print out the impacted region itself.
136
$diff = $message->isPatchable() ? '-' : null;
138
$text_lines = explode("\n", $text);
139
$text_length = count($text_lines);
141
$intraline = ($text != '' || $start || !preg_match('/\n$/', $patch));
144
for (; $cursor < $line_num + $text_length; $cursor++) {
145
$chevron = ($cursor == $line_num);
146
// We may not have any data if, e.g., the old file does not exist.
147
$data = idx($line_data, $cursor, null);
149
// Highlight the problem substring.
150
$text_line = $text_lines[$cursor - $line_num];
151
if (strlen($text_line)) {
152
$data = substr_replace(
154
phutil_console_format('##%s##', $text_line),
155
($cursor == $line_num ? $start : 0),
159
$out[] = $this->renderLine($cursor, $data, $chevron, $diff);
163
// Print out replacement text.
164
if ($message->isPatchable()) {
165
// Strip trailing newlines, since "explode" will create an extra patch
167
if (strlen($patch) && ($patch[strlen($patch) - 1] === "\n")) {
168
$patch = substr($patch, 0, -1);
170
$patch_lines = explode("\n", $patch);
171
$patch_length = count($patch_lines);
173
$patch_line = $patch_lines[0];
175
$len = isset($text_lines[0]) ? strlen($text_lines[0]) : 0;
177
$patched = phutil_console_format('##%s##', $patch_line);
180
$patched = substr_replace(
181
$line_data[$line_num],
187
$out[] = $this->renderLine(null, $patched, false, '+');
189
foreach (array_slice($patch_lines, 1) as $patch_line) {
190
$out[] = $this->renderLine(
192
phutil_console_format('##%s##', $patch_line), false, '+');
196
$end = min($num_lines, $cursor + $lines_of_context);
197
for (; $cursor < $end; $cursor++) {
198
// If there is no original text, we didn't print out a chevron or any
199
// highlighted text above, so print it out here. This allows messages
200
// which don't have any original/replacement information to still
201
// render with indicator chevrons.
202
if ($text || $message->isPatchable()) {
205
$chevron = ($cursor == $line_num);
207
$out[] = $this->renderLine($cursor, $line_data[$cursor], $chevron);
209
// With original text, we'll render the text highlighted above. If the
210
// lint message only has a line/char offset there's nothing to
211
// highlight, so print out a caret on the next line instead.
212
if ($chevron && $message->getChar()) {
213
$out[] = $this->renderCaret($message->getChar());
218
return implode("\n", $out);
221
private function renderCaret($pos) {
222
return str_repeat(' ', 16 + $pos).'^';
225
protected function renderLine($line, $data, $chevron = false, $diff = null) {
226
$chevron = $chevron ? '>>>' : '';
235
public function renderOkayResult() {
236
return phutil_console_format(
237
"<bg:green>** OKAY **</bg> No lint warnings.\n");