protected function processDiffusionRequest(AphrontRequest $request) { $limit = 500; $offset = $request->getInt('offset', 0); $drequest = $this->getDiffusionRequest(); $branch = $drequest->loadBranch(); $messages = $this->loadLintMessages($branch, $limit, $offset); $is_dir = substr('/' . $drequest->getPath(), -1) == '/'; $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID')); $rows = array(); foreach ($messages as $message) { $path = phutil_tag('a', array('href' => $drequest->generateURI(array('action' => 'lint', 'path' => $message['path']))), substr($message['path'], strlen($drequest->getPath()) + 1)); $line = phutil_tag('a', array('href' => $drequest->generateURI(array('action' => 'browse', 'path' => $message['path'], 'line' => $message['line'], 'commit' => $branch->getLintCommit()))), $message['line']); $author = $message['authorPHID']; if ($author && $authors[$author]) { $author = $authors[$author]->renderLink(); } $rows[] = array($path, $line, $author, ArcanistLintSeverity::getStringForSeverity($message['severity']), $message['name'], $message['description']); } $table = id(new AphrontTableView($rows))->setHeaders(array(pht('Path'), pht('Line'), pht('Author'), pht('Severity'), pht('Name'), pht('Description')))->setColumnClasses(array('', 'n'))->setColumnVisibility(array($is_dir)); $content = array(); $pager = id(new AphrontPagerView())->setPageSize($limit)->setOffset($offset)->setHasMorePages(count($messages) >= $limit)->setURI($request->getRequestURI(), 'offset'); $content[] = id(new PHUIObjectBoxView())->setHeaderText(pht('Lint Details'))->appendChild($table); $crumbs = $this->buildCrumbs(array('branch' => true, 'path' => true, 'view' => 'lint')); return $this->buildApplicationPage(array($crumbs, $content, $pager), array('title' => array(pht('Lint'), $drequest->getRepository()->getCallsign()))); }
public function renderLintResult(ArcanistLintResult $result) { $messages = $result->getMessages(); $path = $result->getPath(); $lines = explode("\n", $result->getData()); $text = array(); foreach ($messages as $message) { if (!$this->showAutofixPatches && $message->isAutofix()) { continue; } if ($message->isError()) { $color = 'red'; } else { $color = 'yellow'; } $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity()); $code = $message->getCode(); $name = $message->getName(); $description = phutil_console_wrap($message->getDescription(), 4); $text[] = phutil_console_format(" **<bg:{$color}> %s </bg>** (%s) __%s__\n%s\n", $severity, $code, $name, $description); if ($message->hasFileContext()) { $text[] = $this->renderContext($message, $lines); } } if ($text) { $prefix = phutil_console_format("**>>>** Lint for __%s__:\n\n\n", $path); return $prefix . implode("\n", $text); } else { return null; } }
private function renderSeverity($severity) { $names = ArcanistLintSeverity::getLintSeverities(); $name = idx($names, $severity, $severity); // TODO: Add some color here? return $name; }
public function renderLintResult(ArcanistLintResult $result) { $messages = $result->getMessages(); $path = $result->getPath(); $text = array(); foreach ($messages as $message) { $name = $message->getName(); $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity()); $line = $message->getLine(); $text[] = "{$path}:{$line}:{$severity}: {$name}\n"; } return implode('', $text); }
public function renderLintResult(ArcanistLintResult $result) { $lines = array(); $messages = $result->getMessages(); $path = $result->getPath(); foreach ($messages as $message) { $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity()); $line = $message->getLine(); $code = $message->getCode(); $description = $message->getDescription(); $lines[] = sprintf("%s:%d:%s (%s) %s\n", $path, $line, $severity, $code, $description); } return implode('', $lines); }
public function renderLintResult(ArcanistLintResult $result) { $this->writer->startElement('file'); $this->writer->writeAttribute('name', $result->getPath()); foreach ($result->getMessages() as $message) { $this->writer->startElement('error'); $this->writer->writeAttribute('line', $message->getLine()); $this->writer->writeAttribute('column', $message->getChar()); $this->writer->writeAttribute('severity', ArcanistLintSeverity::getStringForSeverity($message->getSeverity())); $this->writer->writeAttribute('message', $message->getDescription()); $this->writer->writeAttribute('source', $message->getCode()); $this->writer->endElement(); } $this->writer->endElement(); return $this->writer->flush(); }
public function renderLintResult(ArcanistLintResult $result) { $messages = $result->getMessages(); $path = $result->getPath(); $lines = explode("\n", $result->getData()); $text = array(); foreach ($messages as $message) { if (!$this->showAutofixPatches && $message->isAutofix()) { continue; } if ($message->isError()) { $color = 'red'; } else { $color = 'yellow'; } $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity()); $code = $message->getCode(); $name = $message->getName(); $description = $message->getDescription(); if ($message->getOtherLocations()) { $locations = array(); foreach ($message->getOtherLocations() as $location) { $locations[] = idx($location, 'path', $path) . (!empty($location['line']) ? ":{$location['line']}" : ''); } $description .= "\n" . pht('Other locations: %s', implode(', ', $locations)); } $text[] = phutil_console_format(" **<bg:{$color}> %s </bg>** (%s) __%s__\n%s\n", $severity, $code, $name, phutil_console_wrap($description, 4)); if ($message->hasFileContext()) { $text[] = $this->renderContext($message, $lines); } } if ($text) { $prefix = phutil_console_format("**>>>** %s\n\n\n", pht('Lint for %s:', phutil_console_format('__%s__', $path))); return $prefix . implode("\n", $text); } else { return null; } }
public function getArguments() { return array('lintall' => array('help' => pht('Show all lint warnings, not just those on changed lines. When ' . 'paths are specified, this is the default behavior.'), 'conflicts' => array('only-changed' => true)), 'only-changed' => array('help' => pht('Show lint warnings just on changed lines. When no paths are ' . 'specified, this is the default. This differs from only-new ' . 'in cases where line modifications introduce lint on other ' . 'unmodified lines.'), 'conflicts' => array('lintall' => true)), 'rev' => array('param' => 'revision', 'help' => pht('Lint changes since a specific revision.'), 'supports' => array('git', 'hg'), 'nosupport' => array('svn' => pht('Lint does not currently support %s in SVN.', '--rev'))), 'output' => array('param' => 'format', 'help' => pht("With 'summary', show lint warnings in a more compact format. " . "With 'json', show lint warnings in machine-readable JSON format. " . "With 'none', show no lint warnings. " . "With 'compiler', show lint warnings in suitable for your editor. " . "With 'xml', show lint warnings in the Checkstyle XML format.")), 'outfile' => array('param' => 'path', 'help' => pht('Output the linter results to a file. Defaults to stdout.')), 'only-new' => array('param' => 'bool', 'supports' => array('git', 'hg'), 'help' => pht('Display only messages not present in the original code.')), 'engine' => array('param' => 'classname', 'help' => pht('Override configured lint engine for this project.')), 'apply-patches' => array('help' => pht('Apply patches suggested by lint to the working copy without ' . 'prompting.'), 'conflicts' => array('never-apply-patches' => true)), 'never-apply-patches' => array('help' => pht('Never apply patches suggested by lint.'), 'conflicts' => array('apply-patches' => true)), 'amend-all' => array('help' => pht('When linting git repositories, amend HEAD with all patches ' . 'suggested by lint without prompting.')), 'amend-autofixes' => array('help' => pht('When linting git repositories, amend HEAD with autofix ' . 'patches suggested by lint without prompting.')), 'everything' => array('help' => pht('Lint all files in the project.'), 'conflicts' => array('cache' => pht('%s lints all files', '--everything'), 'rev' => pht('%s lints all files', '--everything'))), 'severity' => array('param' => 'string', 'help' => pht("Set minimum message severity. One of: %s. Defaults to '%s'.", sprintf("'%s'", implode("', '", array_keys(ArcanistLintSeverity::getLintSeverities()))), self::DEFAULT_SEVERITY)), 'cache' => array('param' => 'bool', 'help' => pht("%d to disable cache, %d to enable. The default value is determined " . "by '%s' in configuration, which defaults to off. See notes in '%s'.", 0, 1, 'arc.lint.cache', 'arc.lint.cache')), '*' => 'paths'); }
public final function isSeverityEnabled($severity) { $minimum = $this->minimumSeverity; return ArcanistLintSeverity::isAtLeastAsSevere($severity, $minimum); }
protected function processDiffusionRequest(AphrontRequest $request) { $user = $request->getUser(); $drequest = $this->diffusionRequest; if ($request->getStr('lint') !== null) { $controller = new DiffusionLintDetailsController(); $controller->setDiffusionRequest($drequest); $controller->setCurrentApplication($this->getCurrentApplication()); return $this->delegateToController($controller); } $owners = array(); if (!$drequest) { if (!$request->getArr('owner')) { $owners = array($user->getPHID()); } else { $owners = array(head($request->getArr('owner'))); } } $codes = $this->loadLintCodes($owners); if ($codes && !$drequest) { // TODO: Build some real Query classes for this stuff. $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere('id IN (%Ld)', array_unique(ipull($codes, 'branchID'))); $repositories = id(new PhabricatorRepositoryQuery())->setViewer($user)->withIDs(mpull($branches, 'getRepositoryID'))->execute(); $drequests = array(); foreach ($branches as $id => $branch) { if (empty($repositories[$branch->getRepositoryID()])) { continue; } $drequests[$id] = DiffusionRequest::newFromDictionary(array('user' => $user, 'repository' => $repositories[$branch->getRepositoryID()], 'branch' => $branch->getName())); } } $rows = array(); $total = 0; foreach ($codes as $code) { if (!$this->diffusionRequest) { $drequest = idx($drequests, $code['branchID']); } if (!$drequest) { continue; } $total += $code['n']; $href_lint = $drequest->generateURI(array('action' => 'lint', 'lint' => $code['code'])); $href_browse = $drequest->generateURI(array('action' => 'browse', 'lint' => $code['code'])); $href_repo = $drequest->generateURI(array('action' => 'lint')); $rows[] = array(phutil_tag('a', array('href' => $href_lint), $code['n']), phutil_tag('a', array('href' => $href_browse), $code['files']), phutil_tag('a', array('href' => $href_repo), $drequest->getRepository()->getDisplayName()), ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']), $code['code'], $code['maxName'], $code['maxDescription']); } $table = id(new AphrontTableView($rows))->setHeaders(array(pht('Problems'), pht('Files'), pht('Repository'), pht('Severity'), pht('Code'), pht('Name'), pht('Example')))->setColumnVisibility(array(true, true, !$this->diffusionRequest))->setColumnClasses(array('n', 'n', '', '', 'pri', '', '')); $content = array(); if (!$this->diffusionRequest) { $form = id(new AphrontFormView())->setUser($user)->setMethod('GET')->appendControl(id(new AphrontFormTokenizerControl())->setDatasource(new PhabricatorPeopleDatasource())->setLimit(1)->setName('owner')->setLabel(pht('Owner'))->setValue($owners))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Filter'))); $content[] = id(new AphrontListFilterView())->appendChild($form); } $content[] = id(new PHUIObjectBoxView())->setHeaderText(pht('Lint'))->setTable($table); $title = array('Lint'); $crumbs = $this->buildCrumbs(array('branch' => true, 'path' => true, 'view' => 'lint')); if ($this->diffusionRequest) { $title[] = $drequest->getRepository()->getDisplayName(); } else { $crumbs->addTextCrumb(pht('All Lint')); } if ($this->diffusionRequest) { $branch = $drequest->loadBranch(); $header = id(new PHUIHeaderView())->setHeader($this->renderPathLinks($drequest, 'lint'))->setUser($user)->setPolicyObject($drequest->getRepository()); $actions = $this->buildActionView($drequest); $properties = $this->buildPropertyView($drequest, $branch, $total, $actions); $object_box = id(new PHUIObjectBoxView())->setHeader($header)->addPropertyList($properties); } else { $object_box = null; } return $this->buildApplicationPage(array($crumbs, $object_box, $content), array('title' => $title)); }
public function run() { $stopped = array(); $linters = $this->buildLinters(); if (!$linters) { throw new ArcanistNoEffectException("No linters to run."); } $have_paths = false; foreach ($linters as $linter) { if ($linter->getPaths()) { $have_paths = true; break; } } if (!$have_paths) { throw new ArcanistNoEffectException("No paths are lintable."); } foreach ($linters as $linter) { $linter->setEngine($this); if (!$linter->canRun()) { continue; } $paths = $linter->getPaths(); foreach ($paths as $key => $path) { // Make sure each path has a result generated, even if it is empty // (i.e., the file has no lint messages). $result = $this->getResultForPath($path); if (isset($stopped[$path])) { unset($paths[$key]); } } $paths = array_values($paths); if ($paths) { $linter->willLintPaths($paths); foreach ($paths as $path) { $linter->willLintPath($path); $linter->lintPath($path); if ($linter->didStopAllLinters()) { $stopped[$path] = true; } } } $minimum = $this->minimumSeverity; foreach ($linter->getLintMessages() as $message) { if (!ArcanistLintSeverity::isAtLeastAsSevere($message, $minimum)) { continue; } // When a user runs "arc lint", we default to raising only warnings on // lines they have changed (errors are still raised anywhere in the // file). The list of $changed lines may be null, to indicate that the // path is a directory or a binary file so we should not exclude // warnings. $changed = $this->getPathChangedLines($message->getPath()); if ($changed !== null && !$message->isError() && $message->getLine()) { if (empty($changed[$message->getLine()])) { continue; } } $result = $this->getResultForPath($message->getPath()); $result->addMessage($message); } } foreach ($this->results as $path => $result) { $disk_path = $this->getFilePathOnDisk($path); $result->setFilePathOnDisk($disk_path); if (isset($this->fileData[$path])) { $result->setData($this->fileData[$path]); } else { if ($disk_path && Filesystem::pathExists($disk_path)) { // TODO: this may cause us to, e.g., load a large binary when we only // raised an error about its filename. We could refine this by looking // through the lint messages and doing this load only if any of them // have original/replacement text or something like that. try { $this->fileData[$path] = Filesystem::readFile($disk_path); $result->setData($this->fileData[$path]); } catch (FilesystemException $ex) { // Ignore this, it's noncritical that we access this data and it // might be unreadable or a directory or whatever else for plenty // of legitimate reasons. } } } } return $this->results; }
private function buildDisplayRows(array $lines, array $blame_list, array $blame_commits, $show_color, $show_blame) { $request = $this->getRequest(); $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $revision_map = array(); $revisions = array(); if ($blame_commits) { $commit_map = mpull($blame_commits, 'getCommitIdentifier', 'getPHID'); $revision_ids = id(new DifferentialRevision())->loadIDsByCommitPHIDs(array_keys($commit_map)); if ($revision_ids) { $revisions = id(new DifferentialRevisionQuery())->setViewer($viewer)->withIDs($revision_ids)->execute(); $revisions = mpull($revisions, null, 'getID'); } foreach ($revision_ids as $commit_phid => $revision_id) { $revision_map[$commit_map[$commit_phid]] = $revision_id; } } $phids = array(); foreach ($blame_commits as $commit) { $author_phid = $commit->getAuthorPHID(); if ($author_phid === null) { continue; } $phids[$author_phid] = $author_phid; } foreach ($revisions as $revision) { $author_phid = $revision->getAuthorPHID(); if ($author_phid === null) { continue; } $phids[$author_phid] = $author_phid; } $handles = $viewer->loadHandles($phids); $colors = array(); if ($blame_commits) { $epochs = array(); foreach ($blame_commits as $identifier => $commit) { $epochs[$identifier] = $commit->getEpoch(); } $epoch_list = array_filter($epochs); $epoch_list = array_unique($epoch_list); $epoch_list = array_values($epoch_list); $epoch_min = min($epoch_list); $epoch_max = max($epoch_list); $epoch_range = $epoch_max - $epoch_min + 1; foreach ($blame_commits as $identifier => $commit) { $epoch = $epochs[$identifier]; if (!$epoch) { $color = '#ffffdd'; // Warning color, missing data. } else { $color_ratio = ($epoch - $epoch_min) / $epoch_range; $color_value = 0xe6 * (1.0 - $color_ratio); $color = sprintf('#%02x%02x%02x', $color_value, 0xf6, $color_value); } $colors[$identifier] = $color; } } $display = array(); $last_identifier = null; $last_color = null; foreach ($lines as $line_index => $line) { $color = '#f6f6f6'; $duplicate = false; if (isset($blame_list[$line_index])) { $identifier = $blame_list[$line_index]; if (isset($colors[$identifier])) { $color = $colors[$identifier]; } if ($identifier === $last_identifier) { $duplicate = true; } else { $last_identifier = $identifier; } } $display[$line_index] = array('data' => $line, 'target' => false, 'highlighted' => false, 'color' => $color, 'duplicate' => $duplicate); } $line_arr = array(); $line_str = $drequest->getLine(); $ranges = explode(',', $line_str); foreach ($ranges as $range) { if (strpos($range, '-') !== false) { list($min, $max) = explode('-', $range, 2); $line_arr[] = array('min' => min($min, $max), 'max' => max($min, $max)); } else { if (strlen($range)) { $line_arr[] = array('min' => $range, 'max' => $range); } } } // Mark the first highlighted line as the target line. if ($line_arr) { $target_line = $line_arr[0]['min']; if (isset($display[$target_line - 1])) { $display[$target_line - 1]['target'] = true; } } // Mark all other highlighted lines as highlighted. foreach ($line_arr as $range) { for ($ii = $range['min']; $ii <= $range['max']; $ii++) { if (isset($display[$ii - 1])) { $display[$ii - 1]['highlighted'] = true; } } } $engine = null; $inlines = array(); if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { $engine = new PhabricatorMarkupEngine(); $engine->setViewer($viewer); foreach ($this->lintMessages as $message) { $inline = id(new PhabricatorAuditInlineComment())->setSyntheticAuthor(ArcanistLintSeverity::getStringForSeverity($message['severity']) . ' ' . $message['code'] . ' (' . $message['name'] . ')')->setLineNumber($message['line'])->setContent($message['description']); $inlines[$message['line']][] = $inline; $engine->addObject($inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); require_celerity_resource('differential-changeset-view-css'); } $rows = $this->renderInlines(idx($inlines, 0, array()), $show_blame, (bool) $this->coverage, $engine); // NOTE: We're doing this manually because rendering is otherwise // dominated by URI generation for very large files. $line_base = (string) $drequest->generateURI(array('action' => 'browse', 'stable' => true)); require_celerity_resource('aphront-tooltip-css'); Javelin::initBehavior('phabricator-oncopy'); Javelin::initBehavior('phabricator-tooltips'); Javelin::initBehavior('phabricator-line-linker'); // Render these once, since they tend to get repeated many times in large // blame outputs. $commit_links = $this->renderCommitLinks($blame_commits, $handles); $revision_links = $this->renderRevisionLinks($revisions, $handles); $skip_text = pht('Skip Past This Commit'); foreach ($display as $line_index => $line) { $row = array(); $line_number = $line_index + 1; $line_href = $line_base . '$' . $line_number; if (isset($blame_list[$line_index])) { $identifier = $blame_list[$line_index]; } else { $identifier = null; } $revision_link = null; $commit_link = null; $before_link = null; $style = 'background: ' . $line['color'] . ';'; if ($identifier && !$line['duplicate']) { if (isset($commit_links[$identifier])) { $commit_link = $commit_links[$identifier]; } if (isset($revision_map[$identifier])) { $revision_id = $revision_map[$identifier]; if (isset($revision_links[$revision_id])) { $revision_link = $revision_links[$revision_id]; } } $skip_href = $line_href . '?before=' . $identifier . '&view=blame'; $before_link = javelin_tag('a', array('href' => $skip_href, 'sigil' => 'has-tooltip', 'meta' => array('tip' => $skip_text, 'align' => 'E', 'size' => 300)), "«"); } $row[] = phutil_tag('th', array('class' => 'diffusion-blame-link'), $before_link); $object_links = array(); $object_links[] = $commit_link; if ($revision_link) { $object_links[] = phutil_tag('span', array(), '/'); $object_links[] = $revision_link; } $row[] = phutil_tag('th', array('class' => 'diffusion-rev-link'), $object_links); $line_link = phutil_tag('a', array('href' => $line_href, 'style' => $style), $line_number); $row[] = javelin_tag('th', array('class' => 'diffusion-line-link', 'sigil' => 'phabricator-source-line', 'style' => $style), $line_link); if ($line['target']) { Javelin::initBehavior('diffusion-jump-to', array('target' => 'scroll_target')); $anchor_text = phutil_tag('a', array('id' => 'scroll_target'), ''); } else { $anchor_text = null; } $row[] = phutil_tag('td', array(), array($anchor_text, "", phutil_safe_html(str_replace("\t", ' ', $line['data'])))); if ($this->coverage) { require_celerity_resource('differential-changeset-view-css'); $cov_index = $line_index; if (isset($this->coverage[$cov_index])) { $cov_class = $this->coverage[$cov_index]; } else { $cov_class = 'N'; } $row[] = phutil_tag('td', array('class' => 'cov cov-' . $cov_class), ''); } $rows[] = phutil_tag('tr', array('class' => $line['highlighted'] ? 'phabricator-source-highlight' : null), $row); $cur_inlines = $this->renderInlines(idx($inlines, $line_number, array()), $show_blame, $this->coverage, $engine); foreach ($cur_inlines as $cur_inline) { $rows[] = $cur_inline; } } return $rows; }
private function buildDisplayRows(array $text_list, array $rev_list, array $blame_dict, $needs_blame, DiffusionRequest $drequest, $show_blame, $show_color) { $handles = array(); if ($blame_dict) { $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); $epoch_min = min($epoch_list); $epoch_max = max($epoch_list); $epoch_range = $epoch_max - $epoch_min + 1; $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID'); $handles = $this->loadViewerHandles($author_phids); } $line_arr = array(); $line_str = $drequest->getLine(); $ranges = explode(',', $line_str); foreach ($ranges as $range) { if (strpos($range, '-') !== false) { list($min, $max) = explode('-', $range, 2); $line_arr[] = array('min' => min($min, $max), 'max' => max($min, $max)); } else { if (strlen($range)) { $line_arr[] = array('min' => $range, 'max' => $range); } } } $display = array(); $line_number = 1; $last_rev = null; $color = null; foreach ($text_list as $k => $line) { $display_line = array('epoch' => null, 'commit' => null, 'author' => null, 'target' => null, 'highlighted' => null, 'line' => $line_number, 'data' => $line); if ($show_blame) { // If the line's rev is same as the line above, show empty content // with same color; otherwise generate blame info. The newer a change // is, the more saturated the color. $rev = idx($rev_list, $k, $last_rev); if ($last_rev == $rev) { $display_line['color'] = $color; } else { $blame = $blame_dict[$rev]; if (!isset($blame['epoch'])) { $color = '#ffd'; // Render as warning. } else { $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; $color_value = 0xe6 * (1.0 - $color_ratio); $color = sprintf('#%02x%02x%02x', $color_value, 0xf6, $color_value); } $display_line['epoch'] = idx($blame, 'epoch'); $display_line['color'] = $color; $display_line['commit'] = $rev; $author_phid = idx($blame, 'authorPHID'); if ($author_phid && $handles[$author_phid]) { $author_link = $handles[$author_phid]->renderLink(); } else { $author_link = $blame['author']; } $display_line['author'] = $author_link; $last_rev = $rev; } } if ($line_arr) { if ($line_number == $line_arr[0]['min']) { $display_line['target'] = true; } foreach ($line_arr as $range) { if ($line_number >= $range['min'] && $line_number <= $range['max']) { $display_line['highlighted'] = true; } } } $display[] = $display_line; ++$line_number; } $request = $this->getRequest(); $viewer = $request->getUser(); $commits = array_filter(ipull($display, 'commit')); if ($commits) { $commits = id(new DiffusionCommitQuery())->setViewer($viewer)->withRepository($drequest->getRepository())->withIdentifiers($commits)->execute(); $commits = mpull($commits, null, 'getCommitIdentifier'); } $revision_ids = id(new DifferentialRevision())->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); $revisions = array(); if ($revision_ids) { $revisions = id(new DifferentialRevisionQuery())->setViewer($viewer)->withIDs($revision_ids)->execute(); } $phids = array(); foreach ($commits as $commit) { if ($commit->getAuthorPHID()) { $phids[] = $commit->getAuthorPHID(); } } foreach ($revisions as $revision) { if ($revision->getAuthorPHID()) { $phids[] = $revision->getAuthorPHID(); } } $handles = $this->loadViewerHandles($phids); Javelin::initBehavior('phabricator-oncopy', array()); $engine = null; $inlines = array(); if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { $engine = new PhabricatorMarkupEngine(); $engine->setViewer($viewer); foreach ($this->lintMessages as $message) { $inline = id(new PhabricatorAuditInlineComment())->setSyntheticAuthor(ArcanistLintSeverity::getStringForSeverity($message['severity']) . ' ' . $message['code'] . ' (' . $message['name'] . ')')->setLineNumber($message['line'])->setContent($message['description']); $inlines[$message['line']][] = $inline; $engine->addObject($inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); require_celerity_resource('differential-changeset-view-css'); } $rows = $this->renderInlines(idx($inlines, 0, array()), $show_blame, (bool) $this->coverage, $engine); foreach ($display as $line) { $line_href = $drequest->generateURI(array('action' => 'browse', 'line' => $line['line'], 'stable' => true)); $blame = array(); $style = null; if (array_key_exists('color', $line)) { if ($line['color']) { $style = 'background: ' . $line['color'] . ';'; } $before_link = null; $commit_link = null; $revision_link = null; if (idx($line, 'commit')) { $commit = $line['commit']; if (idx($commits, $commit)) { $tooltip = $this->renderCommitTooltip($commits[$commit], $handles, $line['author']); } else { $tooltip = null; } Javelin::initBehavior('phabricator-tooltips', array()); require_celerity_resource('aphront-tooltip-css'); $commit_link = javelin_tag('a', array('href' => $drequest->generateURI(array('action' => 'commit', 'commit' => $line['commit'])), 'sigil' => 'has-tooltip', 'meta' => array('tip' => $tooltip, 'align' => 'E', 'size' => 600)), id(new PhutilUTF8StringTruncator())->setMaximumGlyphs(9)->setTerminator('')->truncateString($line['commit'])); $revision_id = null; if (idx($commits, $commit)) { $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); } if ($revision_id) { $revision = idx($revisions, $revision_id); if ($revision) { $tooltip = $this->renderRevisionTooltip($revision, $handles); $revision_link = javelin_tag('a', array('href' => '/D' . $revision->getID(), 'sigil' => 'has-tooltip', 'meta' => array('tip' => $tooltip, 'align' => 'E', 'size' => 600)), 'D' . $revision->getID()); } } $uri = $line_href->alter('before', $commit); $before_link = javelin_tag('a', array('href' => $uri->setQueryParam('view', 'blame'), 'sigil' => 'has-tooltip', 'meta' => array('tip' => pht('Skip Past This Commit'), 'align' => 'E', 'size' => 300)), "«"); } $blame[] = phutil_tag('th', array('class' => 'diffusion-blame-link'), $before_link); $object_links = array(); $object_links[] = $commit_link; if ($revision_link) { $object_links[] = phutil_tag('span', array(), '/'); $object_links[] = $revision_link; } $blame[] = phutil_tag('th', array('class' => 'diffusion-rev-link'), $object_links); } $line_link = phutil_tag('a', array('href' => $line_href, 'style' => $style), $line['line']); $blame[] = javelin_tag('th', array('class' => 'diffusion-line-link', 'sigil' => 'phabricator-source-line', 'style' => $style), $line_link); Javelin::initBehavior('phabricator-line-linker'); if ($line['target']) { Javelin::initBehavior('diffusion-jump-to', array('target' => 'scroll_target')); $anchor_text = phutil_tag('a', array('id' => 'scroll_target'), ''); } else { $anchor_text = null; } $blame[] = phutil_tag('td', array(), array($anchor_text, "", phutil_safe_html(str_replace("\t", ' ', $line['data'])))); if ($this->coverage) { require_celerity_resource('differential-changeset-view-css'); $cov_index = $line['line'] - 1; if (isset($this->coverage[$cov_index])) { $cov_class = $this->coverage[$cov_index]; } else { $cov_class = 'N'; } $blame[] = phutil_tag('td', array('class' => 'cov cov-' . $cov_class), ''); } $rows[] = phutil_tag('tr', array('class' => $line['highlighted'] ? 'phabricator-source-highlight' : null), $blame); $cur_inlines = $this->renderInlines(idx($inlines, $line['line'], array()), $show_blame, $this->coverage, $engine); foreach ($cur_inlines as $cur_inline) { $rows[] = $cur_inline; } } return $rows; }
public function getMethodDescription() { $messages = HarbormasterMessageType::getAllMessages(); $head_type = pht('Constant'); $head_desc = pht('Description'); $head_key = pht('Key'); $head_type = pht('Type'); $head_name = pht('Name'); $rows = array(); $rows[] = "| {$head_type} | {$head_desc} |"; $rows[] = '|--------------|--------------|'; foreach ($messages as $message) { $description = HarbormasterMessageType::getMessageDescription($message); $rows[] = "| `{$message}` | {$description} |"; } $message_table = implode("\n", $rows); $rows = array(); $rows[] = "| {$head_key} | {$head_type} | {$head_desc} |"; $rows[] = '|-------------|--------------|--------------|'; $unit_spec = HarbormasterBuildUnitMessage::getParameterSpec(); foreach ($unit_spec as $key => $parameter) { $type = idx($parameter, 'type'); $type = str_replace('|', ' ' . pht('or') . ' ', $type); $description = idx($parameter, 'description'); $rows[] = "| `{$key}` | //{$type}// | {$description} |"; } $unit_table = implode("\n", $rows); $rows = array(); $rows[] = "| {$head_key} | {$head_name} | {$head_desc} |"; $rows[] = '|-------------|--------------|--------------|'; $results = ArcanistUnitTestResult::getAllResultCodes(); foreach ($results as $result_code) { $name = ArcanistUnitTestResult::getResultCodeName($result_code); $description = ArcanistUnitTestResult::getResultCodeDescription($result_code); $rows[] = "| `{$result_code}` | **{$name}** | {$description} |"; } $result_table = implode("\n", $rows); $rows = array(); $rows[] = "| {$head_key} | {$head_type} | {$head_desc} |"; $rows[] = '|-------------|--------------|--------------|'; $lint_spec = HarbormasterBuildLintMessage::getParameterSpec(); foreach ($lint_spec as $key => $parameter) { $type = idx($parameter, 'type'); $type = str_replace('|', ' ' . pht('or') . ' ', $type); $description = idx($parameter, 'description'); $rows[] = "| `{$key}` | //{$type}// | {$description} |"; } $lint_table = implode("\n", $rows); $rows = array(); $rows[] = "| {$head_key} | {$head_name} |"; $rows[] = '|-------------|--------------|'; $severities = ArcanistLintSeverity::getLintSeverities(); foreach ($severities as $key => $name) { $rows[] = "| `{$key}` | **{$name}** |"; } $severity_table = implode("\n", $rows); $valid_unit = array(array('name' => 'PassingTest', 'result' => ArcanistUnitTestResult::RESULT_PASS), array('name' => 'FailingTest', 'result' => ArcanistUnitTestResult::RESULT_FAIL)); $valid_lint = array(array('name' => pht('Syntax Error'), 'code' => 'EXAMPLE1', 'severity' => ArcanistLintSeverity::SEVERITY_ERROR, 'path' => 'path/to/example.c', 'line' => 17, 'char' => 3), array('name' => pht('Not A Haiku'), 'code' => 'EXAMPLE2', 'severity' => ArcanistLintSeverity::SEVERITY_ERROR, 'path' => 'path/to/source.cpp', 'line' => 23, 'char' => 1, 'description' => pht('This function definition is not a haiku.'))); $json = new PhutilJSON(); $valid_unit = $json->encodeAsList($valid_unit); $valid_lint = $json->encodeAsList($valid_lint); return pht("Send a message about the status of a build target to Harbormaster, " . "notifying the application of build results in an external system." . "\n\n" . "Sending Messages\n" . "================\n" . "If you run external builds, you can use this method to publish build " . "results back into Harbormaster after the external system finishes work " . "or as it makes progress." . "\n\n" . "The simplest way to use this method is to call it once after the " . "build finishes with a `pass` or `fail` message. This will record the " . "build result, and continue the next step in the build if the build was " . "waiting for a result." . "\n\n" . "When you send a status message about a build target, you can " . "optionally include detailed `lint` or `unit` results alongside the " . "message. See below for details." . "\n\n" . "If you want to report intermediate results but a build hasn't " . "completed yet, you can use the `work` message. This message doesn't " . "have any direct effects, but allows you to send additional data to " . "update the progress of the build target. The target will continue " . "waiting for a completion message, but the UI will update to show the " . "progress which has been made." . "\n\n" . "Message Types\n" . "=============\n" . "When you send Harbormaster a message, you must include a `type`, " . "which describes the overall state of the build. For example, use " . "`pass` to tell Harbomaster that a build completed successfully." . "\n\n" . "Supported message types are:" . "\n\n" . "%s" . "\n\n" . "Unit Results\n" . "============\n" . "You can report test results alongside a message. The simplest way to " . "do this is to report all the results alongside a `pass` or `fail` " . "message, but you can also send a `work` message to report intermediate " . "results.\n\n" . "To provide unit test results, pass a list of results in the `unit` " . "parameter. Each result shoud be a dictionary with these keys:" . "\n\n" . "%s" . "\n\n" . "The `result` parameter recognizes these test results:" . "\n\n" . "%s" . "\n\n" . "This is a simple, valid value for the `unit` parameter. It reports " . "one passing test and one failing test:\n\n" . "\n\n" . "```lang=json\n" . "%s" . "```" . "\n\n" . "Lint Results\n" . "============\n" . "Like unit test results, you can report lint results alongside a " . "message. The `lint` parameter should contain results as a list of " . "dictionaries with these keys:" . "\n\n" . "%s" . "\n\n" . "The `severity` parameter recognizes these severity levels:" . "\n\n" . "%s" . "\n\n" . "This is a simple, valid value for the `lint` parameter. It reports one " . "error and one warning:" . "\n\n" . "```lang=json\n" . "%s" . "```" . "\n\n", $message_table, $unit_table, $result_table, $valid_unit, $lint_table, $severity_table, $valid_lint); }
public function getArguments() { return array('lintall' => array('help' => "Show all lint warnings, not just those on changed lines."), 'rev' => array('param' => 'revision', 'help' => "Lint changes since a specific revision.", 'supports' => array('git', 'hg'), 'nosupport' => array('svn' => "Lint does not currently support --rev in SVN.")), 'output' => array('param' => 'format', 'help' => "With 'summary', show lint warnings in a more compact format. " . "With 'json', show lint warnings in machine-readable JSON format. " . "With 'none', show no lint warnings. " . "With 'compiler', show lint warnings in suitable for your editor."), 'only-new' => array('param' => 'bool', 'supports' => array('git', 'hg'), 'help' => 'Display only messages not present in the original code.'), 'engine' => array('param' => 'classname', 'help' => "Override configured lint engine for this project."), 'apply-patches' => array('help' => 'Apply patches suggested by lint to the working copy without ' . 'prompting.', 'conflicts' => array('never-apply-patches' => true)), 'never-apply-patches' => array('help' => 'Never apply patches suggested by lint.', 'conflicts' => array('apply-patches' => true)), 'amend-all' => array('help' => 'When linting git repositories, amend HEAD with all patches ' . 'suggested by lint without prompting.'), 'amend-autofixes' => array('help' => 'When linting git repositories, amend HEAD with autofix ' . 'patches suggested by lint without prompting.'), 'severity' => array('param' => 'string', 'help' => "Set minimum message severity. One of: '" . implode("', '", array_keys(ArcanistLintSeverity::getLintSeverities())) . "'. Defaults to '" . self::DEFAULT_SEVERITY . "'."), 'cache' => array('param' => 'bool', 'help' => "0 to disable cache, 1 to enable (default)."), '*' => 'paths'); }
private function buildDetailsResponse() { $request = $this->getRequest(); $limit = 500; $pager = id(new PHUIPagerView())->readFromRequest($request)->setPageSize($limit); $offset = $pager->getOffset(); $drequest = $this->getDiffusionRequest(); $branch = $drequest->loadBranch(); $messages = $this->loadLintMessages($branch, $limit, $offset); $is_dir = substr('/' . $drequest->getPath(), -1) == '/'; $pager->setHasMorePages(count($messages) >= $limit); $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID')); $rows = array(); foreach ($messages as $message) { $path = phutil_tag('a', array('href' => $drequest->generateURI(array('action' => 'lint', 'path' => $message['path']))), substr($message['path'], strlen($drequest->getPath()) + 1)); $line = phutil_tag('a', array('href' => $drequest->generateURI(array('action' => 'browse', 'path' => $message['path'], 'line' => $message['line'], 'commit' => $branch->getLintCommit()))), $message['line']); $author = $message['authorPHID']; if ($author && $authors[$author]) { $author = $authors[$author]->renderLink(); } $rows[] = array($path, $line, $author, ArcanistLintSeverity::getStringForSeverity($message['severity']), $message['name'], $message['description']); } $table = id(new AphrontTableView($rows))->setHeaders(array(pht('Path'), pht('Line'), pht('Author'), pht('Severity'), pht('Name'), pht('Description')))->setColumnClasses(array('', 'n'))->setColumnVisibility(array($is_dir)); $content = array(); $content[] = id(new PHUIObjectBoxView())->setHeaderText(pht('Lint Details'))->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)->setTable($table); $crumbs = $this->buildCrumbs(array('branch' => true, 'path' => true, 'view' => 'lint')); $pager_box = $this->renderTablePagerBox($pager); $header = id(new PHUIHeaderView())->setHeader(pht('Lint: %s', $drequest->getRepository()->getDisplayName()))->setHeaderIcon('fa-code'); $view = id(new PHUITwoColumnView())->setHeader($header)->setFooter(array($content, $pager_box)); return $this->newPage()->setTitle(array(pht('Lint'), $drequest->getRepository()->getDisplayName()))->setCrumbs($crumbs)->appendChild(array($view)); }