Page MenuHomeSealhub

No OneTemporary

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index b278c3a5..1008913c 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,223 +1,228 @@
<?php
/**
* This file is automatically generated. Use 'arc liberate' to rebuild it.
* @generated
* @phutil-library-version 2
*/
phutil_register_library_map(array(
'__library_version__' => 2,
'class' =>
array(
'ArcanistAliasWorkflow' => 'workflow/ArcanistAliasWorkflow.php',
'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php',
'ArcanistApacheLicenseLinter' => 'lint/linter/ArcanistApacheLicenseLinter.php',
'ArcanistApacheLicenseLinterTestCase' => 'lint/linter/__tests__/ArcanistApacheLicenseLinterTestCase.php',
'ArcanistBaseUnitTestEngine' => 'unit/engine/ArcanistBaseUnitTestEngine.php',
'ArcanistBaseWorkflow' => 'workflow/ArcanistBaseWorkflow.php',
'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php',
'ArcanistBundle' => 'parser/ArcanistBundle.php',
'ArcanistBundleTestCase' => 'parser/__tests__/ArcanistBundleTestCase.php',
'ArcanistCallConduitWorkflow' => 'workflow/ArcanistCallConduitWorkflow.php',
'ArcanistCapabilityNotSupportedException' => 'workflow/exception/ArcanistCapabilityNotSupportedException.php',
'ArcanistChooseInvalidRevisionException' => 'exception/ArcanistChooseInvalidRevisionException.php',
'ArcanistChooseNoRevisionsException' => 'exception/ArcanistChooseNoRevisionsException.php',
'ArcanistCloseRevisionWorkflow' => 'workflow/ArcanistCloseRevisionWorkflow.php',
'ArcanistCloseWorkflow' => 'workflow/ArcanistCloseWorkflow.php',
'ArcanistCommentRemover' => 'parser/ArcanistCommentRemover.php',
'ArcanistCommentRemoverTestCase' => 'parser/__tests__/ArcanistCommentRemoverTestCase.php',
'ArcanistCommitWorkflow' => 'workflow/ArcanistCommitWorkflow.php',
'ArcanistConduitLinter' => 'lint/linter/ArcanistConduitLinter.php',
'ArcanistConfiguration' => 'configuration/ArcanistConfiguration.php',
'ArcanistCoverWorkflow' => 'workflow/ArcanistCoverWorkflow.php',
'ArcanistDiffChange' => 'parser/diff/ArcanistDiffChange.php',
'ArcanistDiffChangeType' => 'parser/diff/ArcanistDiffChangeType.php',
'ArcanistDiffHunk' => 'parser/diff/ArcanistDiffHunk.php',
'ArcanistDiffParser' => 'parser/ArcanistDiffParser.php',
'ArcanistDiffParserTestCase' => 'parser/__tests__/ArcanistDiffParserTestCase.php',
'ArcanistDiffUtils' => 'difference/ArcanistDiffUtils.php',
'ArcanistDiffUtilsTestCase' => 'difference/__tests__/ArcanistDiffUtilsTestCase.php',
'ArcanistDiffWorkflow' => 'workflow/ArcanistDiffWorkflow.php',
'ArcanistDifferentialCommitMessage' => 'differential/ArcanistDifferentialCommitMessage.php',
'ArcanistDifferentialCommitMessageParserException' => 'differential/ArcanistDifferentialCommitMessageParserException.php',
'ArcanistDifferentialRevisionHash' => 'differential/constants/ArcanistDifferentialRevisionHash.php',
'ArcanistDifferentialRevisionStatus' => 'differential/constants/ArcanistDifferentialRevisionStatus.php',
'ArcanistDownloadWorkflow' => 'workflow/ArcanistDownloadWorkflow.php',
'ArcanistEventType' => 'events/constant/ArcanistEventType.php',
'ArcanistExportWorkflow' => 'workflow/ArcanistExportWorkflow.php',
'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php',
'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php',
'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php',
'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php',
'ArcanistGitHookPreReceiveWorkflow' => 'workflow/ArcanistGitHookPreReceiveWorkflow.php',
'ArcanistHelpWorkflow' => 'workflow/ArcanistHelpWorkflow.php',
'ArcanistHookAPI' => 'repository/hookapi/ArcanistHookAPI.php',
'ArcanistInstallCertificateWorkflow' => 'workflow/ArcanistInstallCertificateWorkflow.php',
'ArcanistJSHintLinter' => 'lint/linter/ArcanistJSHintLinter.php',
'ArcanistLandWorkflow' => 'workflow/ArcanistLandWorkflow.php',
'ArcanistLiberateLintEngine' => 'lint/engine/ArcanistLiberateLintEngine.php',
'ArcanistLiberateWorkflow' => 'workflow/ArcanistLiberateWorkflow.php',
'ArcanistLicenseLinter' => 'lint/linter/ArcanistLicenseLinter.php',
+ 'ArcanistLintConsoleRenderer' => 'lint/renderer/ArcanistLintConsoleRenderer.php',
'ArcanistLintEngine' => 'lint/engine/ArcanistLintEngine.php',
- 'ArcanistLintJSONRenderer' => 'lint/ArcanistLintJSONRenderer.php',
- 'ArcanistLintLikeCompilerRenderer' => 'lint/ArcanistLintLikeCompilerRenderer.php',
+ 'ArcanistLintJSONRenderer' => 'lint/renderer/ArcanistLintJSONRenderer.php',
+ 'ArcanistLintLikeCompilerRenderer' => 'lint/renderer/ArcanistLintLikeCompilerRenderer.php',
'ArcanistLintMessage' => 'lint/ArcanistLintMessage.php',
'ArcanistLintPatcher' => 'lint/ArcanistLintPatcher.php',
- 'ArcanistLintRenderer' => 'lint/ArcanistLintRenderer.php',
+ 'ArcanistLintRenderer' => 'lint/renderer/ArcanistLintRenderer.php',
'ArcanistLintResult' => 'lint/ArcanistLintResult.php',
'ArcanistLintSeverity' => 'lint/ArcanistLintSeverity.php',
- 'ArcanistLintSummaryRenderer' => 'lint/ArcanistLintSummaryRenderer.php',
+ 'ArcanistLintSummaryRenderer' => 'lint/renderer/ArcanistLintSummaryRenderer.php',
'ArcanistLintWorkflow' => 'workflow/ArcanistLintWorkflow.php',
'ArcanistLinter' => 'lint/linter/ArcanistLinter.php',
'ArcanistLinterTestCase' => 'lint/linter/__tests__/ArcanistLinterTestCase.php',
'ArcanistListWorkflow' => 'workflow/ArcanistListWorkflow.php',
'ArcanistMarkCommittedWorkflow' => 'workflow/ArcanistMarkCommittedWorkflow.php',
'ArcanistMercurialAPI' => 'repository/api/ArcanistMercurialAPI.php',
'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php',
'ArcanistMercurialParserTestCase' => 'repository/parser/__tests__/ArcanistMercurialParserTestCase.php',
'ArcanistNoEffectException' => 'exception/usage/ArcanistNoEffectException.php',
'ArcanistNoEngineException' => 'exception/usage/ArcanistNoEngineException.php',
'ArcanistNoLintLinter' => 'lint/linter/ArcanistNoLintLinter.php',
'ArcanistNoLintTestCaseMisnamed' => 'lint/linter/__tests__/ArcanistNoLintTestCase.php',
'ArcanistPEP8Linter' => 'lint/linter/ArcanistPEP8Linter.php',
'ArcanistPasteWorkflow' => 'workflow/ArcanistPasteWorkflow.php',
'ArcanistPatchWorkflow' => 'workflow/ArcanistPatchWorkflow.php',
'ArcanistPhpcsLinter' => 'lint/linter/ArcanistPhpcsLinter.php',
'ArcanistPhutilLibraryLinter' => 'lint/linter/ArcanistPhutilLibraryLinter.php',
'ArcanistPhutilModuleLinter' => 'lint/linter/ArcanistPhutilModuleLinter.php',
'ArcanistPhutilTestCase' => 'unit/engine/phutil/ArcanistPhutilTestCase.php',
'ArcanistPhutilTestSkippedException' => 'unit/engine/phutil/testcase/ArcanistPhutilTestSkippedException.php',
'ArcanistPhutilTestTerminatedException' => 'unit/engine/phutil/testcase/ArcanistPhutilTestTerminatedException.php',
'ArcanistPyFlakesLinter' => 'lint/linter/ArcanistPyFlakesLinter.php',
'ArcanistPyLintLinter' => 'lint/linter/ArcanistPyLintLinter.php',
'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php',
'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php',
'ArcanistSetConfigWorkflow' => 'workflow/ArcanistSetConfigWorkflow.php',
'ArcanistShellCompleteWorkflow' => 'workflow/ArcanistShellCompleteWorkflow.php',
'ArcanistSingleLintEngine' => 'lint/engine/ArcanistSingleLintEngine.php',
'ArcanistSpellingDefaultData' => 'lint/linter/ArcanistSpellingDefaultData.php',
'ArcanistSpellingLinter' => 'lint/linter/ArcanistSpellingLinter.php',
'ArcanistSpellingLinterTestCase' => 'lint/linter/__tests__/ArcanistSpellingLinterTestCase.php',
'ArcanistSubversionAPI' => 'repository/api/ArcanistSubversionAPI.php',
'ArcanistSubversionHookAPI' => 'repository/hookapi/ArcanistSubversionHookAPI.php',
'ArcanistSvnHookPreCommitWorkflow' => 'workflow/ArcanistSvnHookPreCommitWorkflow.php',
'ArcanistTasksWorkflow' => 'workflow/ArcanistTasksWorkflow.php',
'ArcanistTextLinter' => 'lint/linter/ArcanistTextLinter.php',
'ArcanistTextLinterTestCase' => 'lint/linter/__tests__/ArcanistTextLinterTestCase.php',
'ArcanistUncommittedChangesException' => 'exception/usage/ArcanistUncommittedChangesException.php',
'ArcanistUnitTestResult' => 'unit/ArcanistUnitTestResult.php',
'ArcanistUnitWorkflow' => 'workflow/ArcanistUnitWorkflow.php',
'ArcanistUpgradeWorkflow' => 'workflow/ArcanistUpgradeWorkflow.php',
'ArcanistUploadWorkflow' => 'workflow/ArcanistUploadWorkflow.php',
'ArcanistUsageException' => 'exception/ArcanistUsageException.php',
'ArcanistUserAbortException' => 'exception/usage/ArcanistUserAbortException.php',
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
'ArcanistWorkingCopyIdentity' => 'workingcopyidentity/ArcanistWorkingCopyIdentity.php',
'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/ArcanistXHPASTLintNamingHook.php',
'ArcanistXHPASTLintNamingHookTestCase' => 'lint/linter/xhpast/__tests__/ArcanistXHPASTLintNamingHookTestCase.php',
'ArcanistXHPASTLinter' => 'lint/linter/ArcanistXHPASTLinter.php',
'ArcanistXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php',
'BranchInfo' => 'branch/BranchInfo.php',
'ComprehensiveLintEngine' => 'lint/engine/ComprehensiveLintEngine.php',
'ExampleLintEngine' => 'lint/engine/ExampleLintEngine.php',
'NoseTestEngine' => 'unit/engine/NoseTestEngine.php',
'PhpunitTestEngine' => 'unit/engine/PhpunitTestEngine.php',
'PhutilLintEngine' => 'lint/engine/PhutilLintEngine.php',
'PhutilModuleRequirements' => 'parser/PhutilModuleRequirements.php',
'PhutilUnitTestEngine' => 'unit/engine/PhutilUnitTestEngine.php',
'PhutilUnitTestEngineTestCase' => 'unit/engine/__tests__/PhutilUnitTestEngineTestCase.php',
'UnitTestableArcanistLintEngine' => 'lint/engine/UnitTestableArcanistLintEngine.php',
),
'function' =>
array(
),
'xmap' =>
array(
'ArcanistAliasWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistAmendWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistApacheLicenseLinter' => 'ArcanistLicenseLinter',
'ArcanistApacheLicenseLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistBranchWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistBundleTestCase' => 'ArcanistPhutilTestCase',
'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCapabilityNotSupportedException' => 'Exception',
'ArcanistChooseInvalidRevisionException' => 'Exception',
'ArcanistChooseNoRevisionsException' => 'Exception',
'ArcanistCloseRevisionWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCloseWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCommentRemoverTestCase' => 'ArcanistPhutilTestCase',
'ArcanistCommitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistConduitLinter' => 'ArcanistLinter',
'ArcanistCoverWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistDiffParserTestCase' => 'ArcanistPhutilTestCase',
'ArcanistDiffUtilsTestCase' => 'ArcanistPhutilTestCase',
'ArcanistDiffWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistDifferentialCommitMessageParserException' => 'Exception',
'ArcanistDownloadWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistEventType' => 'PhutilEventType',
'ArcanistExportWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistFilenameLinter' => 'ArcanistLinter',
'ArcanistGeneratedLinter' => 'ArcanistLinter',
'ArcanistGetConfigWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistGitAPI' => 'ArcanistRepositoryAPI',
'ArcanistGitHookPreReceiveWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistHelpWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistInstallCertificateWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistJSHintLinter' => 'ArcanistLinter',
'ArcanistLandWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLiberateLintEngine' => 'ArcanistLintEngine',
'ArcanistLiberateWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLicenseLinter' => 'ArcanistLinter',
+ 'ArcanistLintConsoleRenderer' => 'ArcanistLintRenderer',
+ 'ArcanistLintJSONRenderer' => 'ArcanistLintRenderer',
+ 'ArcanistLintLikeCompilerRenderer' => 'ArcanistLintRenderer',
+ 'ArcanistLintSummaryRenderer' => 'ArcanistLintRenderer',
'ArcanistLintWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLinterTestCase' => 'ArcanistPhutilTestCase',
'ArcanistListWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistMarkCommittedWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
'ArcanistMercurialParserTestCase' => 'ArcanistPhutilTestCase',
'ArcanistNoEffectException' => 'ArcanistUsageException',
'ArcanistNoEngineException' => 'ArcanistUsageException',
'ArcanistNoLintLinter' => 'ArcanistLinter',
'ArcanistNoLintTestCaseMisnamed' => 'ArcanistLinterTestCase',
'ArcanistPEP8Linter' => 'ArcanistLinter',
'ArcanistPasteWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistPhpcsLinter' => 'ArcanistLinter',
'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
'ArcanistPhutilModuleLinter' => 'ArcanistLinter',
'ArcanistPhutilTestSkippedException' => 'Exception',
'ArcanistPhutilTestTerminatedException' => 'Exception',
'ArcanistPyFlakesLinter' => 'ArcanistLinter',
'ArcanistPyLintLinter' => 'ArcanistLinter',
'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
'ArcanistSetConfigWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistShellCompleteWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistSingleLintEngine' => 'ArcanistLintEngine',
'ArcanistSpellingLinter' => 'ArcanistLinter',
'ArcanistSpellingLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI',
'ArcanistSubversionHookAPI' => 'ArcanistHookAPI',
'ArcanistSvnHookPreCommitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistTasksWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistTextLinter' => 'ArcanistLinter',
'ArcanistTextLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistUncommittedChangesException' => 'ArcanistUsageException',
'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUpgradeWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUsageException' => 'Exception',
'ArcanistUserAbortException' => 'ArcanistUsageException',
'ArcanistWhichWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistXHPASTLintNamingHookTestCase' => 'ArcanistPhutilTestCase',
'ArcanistXHPASTLinter' => 'ArcanistLinter',
'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase',
'ComprehensiveLintEngine' => 'ArcanistLintEngine',
'ExampleLintEngine' => 'ArcanistLintEngine',
'NoseTestEngine' => 'ArcanistBaseUnitTestEngine',
'PhpunitTestEngine' => 'ArcanistBaseUnitTestEngine',
'PhutilLintEngine' => 'ArcanistLintEngine',
'PhutilUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
'PhutilUnitTestEngineTestCase' => 'ArcanistPhutilTestCase',
'UnitTestableArcanistLintEngine' => 'ArcanistLintEngine',
),
));
diff --git a/src/lint/ArcanistLintRenderer.php b/src/lint/renderer/ArcanistLintConsoleRenderer.php
similarity index 98%
rename from src/lint/ArcanistLintRenderer.php
rename to src/lint/renderer/ArcanistLintConsoleRenderer.php
index 3f9cb480..944acfe7 100644
--- a/src/lint/ArcanistLintRenderer.php
+++ b/src/lint/renderer/ArcanistLintConsoleRenderer.php
@@ -1,248 +1,248 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Shows lint messages to the user.
*
* @group lint
*/
-final class ArcanistLintRenderer {
+final class ArcanistLintConsoleRenderer implements ArcanistLintRenderer {
private $showAutofixPatches = false;
public function setShowAutofixPatches($show_autofix_patches) {
$this->showAutofixPatches = $show_autofix_patches;
return $this;
}
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;
}
}
protected function renderContext(
ArcanistLintMessage $message,
array $line_data) {
$lines_of_context = 3;
$out = array();
$num_lines = count($line_data);
// make line numbers line up with array indexes
array_unshift($line_data, '');
$line_num = min($message->getLine(), $num_lines);
$line_num = max(1, $line_num);
// Print out preceding context before the impacted region.
$cursor = max(1, $line_num - $lines_of_context);
for (; $cursor < $line_num; $cursor++) {
$out[] = $this->renderLine($cursor, $line_data[$cursor]);
}
$text = $message->getOriginalText();
// Refine original and replacement text to eliminate start and end in common
if ($message->isPatchable()) {
$start = $message->getChar() - 1;
$patch = $message->getReplacementText();
$text_strlen = strlen($text);
$patch_strlen = strlen($patch);
$min_length = min($text_strlen, $patch_strlen);
$same_at_front = 0;
for ($ii = 0; $ii < $min_length; $ii++) {
if ($text[$ii] !== $patch[$ii]) {
break;
}
$same_at_front++;
$start++;
if ($text[$ii] == "\n") {
$out[] = $this->renderLine($cursor, $line_data[$cursor]);
$cursor++;
$start = 0;
$line_num++;
}
}
// deal with shorter string ' ' longer string ' a '
$min_length -= $same_at_front;
// And check the end of the string
$same_at_end = 0;
for ($ii = 1; $ii <= $min_length; $ii++) {
if ($text[$text_strlen - $ii] !== $patch[$patch_strlen - $ii]) {
break;
}
$same_at_end++;
}
$text = substr(
$text,
$same_at_front,
$text_strlen - $same_at_end - $same_at_front
);
$patch = substr(
$patch,
$same_at_front,
$patch_strlen - $same_at_end - $same_at_front
);
}
// Print out the impacted region itself.
$diff = $message->isPatchable() ? '-' : null;
$text_lines = explode("\n", $text);
$text_length = count($text_lines);
if ($text) {
for (; $cursor < $line_num + $text_length; $cursor++) {
$chevron = ($cursor == $line_num);
// We may not have any data if, e.g., the old file does not exist.
$data = idx($line_data, $cursor, null);
// Highlight the problem substring.
$text_line = $text_lines[$cursor - $line_num];
if (strlen($text_line)) {
$data = substr_replace(
$data,
phutil_console_format('##%s##', $text_line),
($cursor == $line_num)
? $message->getChar() - 1
: 0,
strlen($text_line));
}
$out[] = $this->renderLine($cursor, $data, $chevron, $diff);
}
}
// Print out replacement text.
if ($message->isPatchable()) {
// Strip trailing newlines, since "explode" will create an extra patch
// line for these.
if (strlen($patch) && ($patch[strlen($patch) - 1] === "\n")) {
$patch = substr($patch, 0, -1);
}
$patch_lines = explode("\n", $patch);
$patch_length = count($patch_lines);
$patch_line = $patch_lines[0];
$len = isset($text_lines[0]) ? strlen($text_lines[0]) : 0;
$patched = phutil_console_format('##%s##', $patch_line);
if ($text) {
$patched = substr_replace(
$line_data[$line_num],
$patched,
$start,
$len);
}
$out[] = $this->renderLine(null, $patched, false, '+');
foreach (array_slice($patch_lines, 1) as $patch_line) {
$out[] = $this->renderLine(
null,
phutil_console_format('##%s##', $patch_line), false, '+'
);
}
}
$end = min($num_lines, $cursor + $lines_of_context);
for (; $cursor < $end; $cursor++) {
// If there is no original text, we didn't print out a chevron or any
// highlighted text above, so print it out here. This allows messages
// which don't have any original/replacement information to still
// render with indicator chevrons.
if ($text || $message->isPatchable()) {
$chevron = false;
} else {
$chevron = ($cursor == $line_num);
}
$out[] = $this->renderLine($cursor, $line_data[$cursor], $chevron);
// With original text, we'll render the text highlighted above. If the
// lint message only has a line/char offset there's nothing to
// highlight, so print out a caret on the next line instead.
if ($chevron && $message->getChar()) {
$out[] = $this->renderCaret($message->getChar());
}
}
$out[] = null;
return implode("\n", $out);
}
private function renderCaret($pos) {
return str_repeat(' ', 16 + $pos).'^';
}
protected function renderLine($line, $data, $chevron = false, $diff = null) {
$chevron = $chevron ? '>>>' : '';
return sprintf(
" %3s %1s %6s %s",
$chevron,
$diff,
$line,
$data);
}
public function renderOkayResult() {
return
phutil_console_format("<bg:green>** OKAY **</bg> No lint warnings.\n");
}
}
diff --git a/src/lint/ArcanistLintJSONRenderer.php b/src/lint/renderer/ArcanistLintJSONRenderer.php
similarity index 95%
rename from src/lint/ArcanistLintJSONRenderer.php
rename to src/lint/renderer/ArcanistLintJSONRenderer.php
index ddaa70c6..423845ec 100644
--- a/src/lint/ArcanistLintJSONRenderer.php
+++ b/src/lint/renderer/ArcanistLintJSONRenderer.php
@@ -1,57 +1,57 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Shows lint messages to the user.
*
* @group lint
*/
-final class ArcanistLintJSONRenderer {
+final class ArcanistLintJSONRenderer implements ArcanistLintRenderer {
const LINES_OF_CONTEXT = 3;
public function renderLintResult(ArcanistLintResult $result) {
$messages = $result->getMessages();
$path = $result->getPath();
$data = explode("\n", $result->getData());
array_unshift($data, ''); // make the line numbers work as array indices
$output = array($path => array());
foreach ($messages as $message) {
$output[$path][] = array(
'code' => $message->getCode(),
'name' => $message->getName(),
'severity' => $message->getSeverity(),
'line' => $message->getLine(),
'char' => $message->getChar(),
'context' => implode("\n", array_slice(
$data,
$message->getLine() - self::LINES_OF_CONTEXT,
self::LINES_OF_CONTEXT * 2 + 1
)),
'description' => $message->getDescription(),
);
}
return json_encode($output)."\n";
}
public function renderOkayResult() {
return "";
}
}
diff --git a/src/lint/ArcanistLintLikeCompilerRenderer.php b/src/lint/renderer/ArcanistLintLikeCompilerRenderer.php
similarity index 94%
rename from src/lint/ArcanistLintLikeCompilerRenderer.php
rename to src/lint/renderer/ArcanistLintLikeCompilerRenderer.php
index 53316aa9..bc75d5ba 100644
--- a/src/lint/ArcanistLintLikeCompilerRenderer.php
+++ b/src/lint/renderer/ArcanistLintLikeCompilerRenderer.php
@@ -1,52 +1,52 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Shows lint messages to the user.
*
* @group lint
*/
-final class ArcanistLintLikeCompilerRenderer {
+final class ArcanistLintLikeCompilerRenderer implements ArcanistLintRenderer {
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 renderOkayResult() {
return "";
}
}
diff --git a/src/lint/renderer/ArcanistLintRenderer.php b/src/lint/renderer/ArcanistLintRenderer.php
new file mode 100644
index 00000000..aacd4ec6
--- /dev/null
+++ b/src/lint/renderer/ArcanistLintRenderer.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * Copyright 2012 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Shows lint messages to the user.
+ *
+ * @group lint
+ */
+interface ArcanistLintRenderer {
+ public function renderLintResult(ArcanistLintResult $result);
+ public function renderOkayResult();
+}
diff --git a/src/lint/ArcanistLintSummaryRenderer.php b/src/lint/renderer/ArcanistLintSummaryRenderer.php
similarity index 94%
rename from src/lint/ArcanistLintSummaryRenderer.php
rename to src/lint/renderer/ArcanistLintSummaryRenderer.php
index a9d05375..36147642 100644
--- a/src/lint/ArcanistLintSummaryRenderer.php
+++ b/src/lint/renderer/ArcanistLintSummaryRenderer.php
@@ -1,48 +1,48 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Shows lint messages to the user.
*
* @group lint
*/
-final class ArcanistLintSummaryRenderer {
+final class ArcanistLintSummaryRenderer implements ArcanistLintRenderer {
public function renderLintResult(ArcanistLintResult $result) {
$messages = $result->getMessages();
$path = $result->getPath();
$text = array();
$text[] = $path.":";
foreach ($messages as $message) {
$name = $message->getName();
$severity = ArcanistLintSeverity::getStringForSeverity(
$message->getSeverity());
$line = $message->getLine();
$text[] = " {$severity} on line {$line}: {$name}";
}
$text[] = null;
return implode("\n", $text);
}
public function renderOkayResult() {
return
phutil_console_format("<bg:green>** OKAY **</bg> No lint warnings.\n");
}
}
diff --git a/src/workflow/ArcanistLiberateWorkflow.php b/src/workflow/ArcanistLiberateWorkflow.php
index a709aa53..fd189ea1 100644
--- a/src/workflow/ArcanistLiberateWorkflow.php
+++ b/src/workflow/ArcanistLiberateWorkflow.php
@@ -1,431 +1,431 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Create and update libphutil libraries.
*
* This workflow is unusual and involves reexecuting 'arc liberate' as a
* subprocess with "--remap" and "--verify". This is because there is no way
* to unload or reload a library, so every process is stuck with the library
* definition it had when it first loaded. This is normally fine, but
* problematic in this case because 'arc liberate' modifies library definitions.
*
* @group workflow
*/
final class ArcanistLiberateWorkflow extends ArcanistBaseWorkflow {
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**liberate** [__path__]
EOTEXT
);
}
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
Supports: libphutil
Create or update a libphutil library, generating required metadata
files like \__init__.php.
EOTEXT
);
}
public function getArguments() {
return array(
'all' => array(
'help' =>
"Drop the module cache before liberating. This will completely ".
"reanalyze the entire library. Thorough, but slow!",
),
'force-update' => array(
'help' =>
"Force the library map to be updated, even in the presence of ".
"lint errors.",
),
'remap' => array(
'hide' => true,
'help' =>
"Internal. Run the remap step of liberation. You do not need to ".
"run this unless you are debugging the workflow.",
),
'verify' => array(
'hide' => true,
'help' =>
"Internal. Run the verify step of liberation. You do not need to ".
"run this unless you are debugging the workflow.",
),
'upgrade' => array(
'hide' => true,
'help' => "Experimental. Upgrade library to v2.",
),
'*' => 'argv',
);
}
public function run() {
$argv = $this->getArgument('argv');
if (count($argv) > 1) {
throw new ArcanistUsageException(
"Provide only one path to 'arc liberate'. The path should be a ".
"directory where you want to create or update a libphutil library.");
} else if (count($argv) == 0) {
$path = getcwd();
} else {
$path = reset($argv);
}
$is_remap = $this->getArgument('remap');
$is_verify = $this->getArgument('verify');
$path = Filesystem::resolvePath($path);
if (Filesystem::pathExists($path) && is_dir($path)) {
$init = id(new FileFinder($path))
->withPath('*/__phutil_library_init__.php')
->find();
} else {
$init = null;
}
if ($init) {
if (count($init) > 1) {
throw new ArcanistUsageException(
"Specified directory contains more than one libphutil library. Use ".
"a more specific path.");
}
$path = Filesystem::resolvePath(dirname(reset($init)), $path);
} else {
$found = false;
foreach (Filesystem::walkToRoot($path) as $dir) {
if (Filesystem::pathExists($dir.'/__phutil_library_init__.php')) {
$path = $dir;
break;
}
}
if (!$found) {
echo "No library currently exists at that path...\n";
$this->liberateCreateDirectory($path);
$this->liberateCreateLibrary($path);
}
}
$version = $this->getLibraryFormatVersion($path);
switch ($version) {
case 1:
if ($this->getArgument('upgrade')) {
return $this->upgradeLibrary($path);
}
return $this->liberateVersion1($path);
case 2:
if ($this->getArgument('upgrade')) {
throw new ArcanistUsageException(
"Can't upgrade a v2 library!");
}
return $this->liberateVersion2($path);
default:
throw new ArcanistUsageException(
"Unknown library version '{$version}'!");
}
}
private function getLibraryFormatVersion($path) {
$map_file = $path.'/__phutil_library_map__.php';
if (!Filesystem::pathExists($map_file)) {
// Default to library v1.
return 1;
}
$map = Filesystem::readFile($map_file);
$matches = null;
if (preg_match('/@phutil-library-version (\d+)/', $map, $matches)) {
return (int)$matches[1];
}
return 1;
}
private function liberateVersion2($path) {
$bin = $this->getScriptPath('scripts/phutil_rebuild_map.php');
return phutil_passthru(
'%s %C %s',
$bin,
$this->getArgument('all') ? '--drop-cache' : '',
$path);
}
private function upgradeLibrary($path) {
$inits = id(new FileFinder($path))
->withPath('*/__init__.php')
->withType('f')
->find();
echo "Removing __init__.php files...\n";
foreach ($inits as $init) {
Filesystem::remove($path.'/'.$init);
}
echo "Upgrading library to v2...\n";
$this->liberateVersion2($path);
}
private function liberateVersion1($path) {
if ($this->getArgument('remap')) {
return $this->liberateRunRemap($path);
}
if ($this->getArgument('verify')) {
return $this->liberateRunVerify($path);
}
$readable = Filesystem::readablePath($path);
echo "Using library root at '{$readable}'...\n";
$this->checkForLooseFiles($path);
if ($this->getArgument('all')) {
echo "Dropping module cache...\n";
Filesystem::remove($path.'/.phutil_module_cache');
}
echo "Mapping library...\n";
// Force a rebuild of the library map before running lint. The remap
// operation will load the map before regenerating it, so if a class has
// been renamed (say, from OldClass to NewClass) this rebuild will
// cause the initial remap to see NewClass and correctly remove includes
// caused by use of OldClass.
$this->liberateGetChangedPaths($path);
$arc_bin = $this->getScriptPath('bin/arc');
do {
$future = new ExecFuture(
'%s liberate --remap -- %s',
$arc_bin,
$path);
$wrote = $future->resolveJSON();
foreach ($wrote as $wrote_path) {
echo "Updated '{$wrote_path}'...\n";
}
} while ($wrote);
echo "Verifying library...\n";
$err = phutil_passthru('%s liberate --verify -- %s', $arc_bin, $path);
$do_update = (!$err || $this->getArgument('force-update'));
if ($do_update) {
echo "Finalizing library map...\n";
execx('%s %s', $this->getPhutilMapperLocation(), $path);
}
if ($err) {
if ($do_update) {
echo phutil_console_format(
"<bg:yellow>** WARNING **</bg> Library update forced, but lint ".
"failures remain.\n");
} else {
echo phutil_console_format(
"<bg:red>** UNRESOLVED LINT ERRORS **</bg> This library has ".
"unresolved lint failures. The library map was not updated. Use ".
"--force-update to force an update.\n");
}
} else {
echo phutil_console_format(
"<bg:green>** OKAY **</bg> Library updated.\n");
}
return $err;
}
private function liberateLintModules($path, array $changed) {
$engine = $this->liberateBuildLintEngine($path, $changed);
if ($engine) {
return $engine->run();
} else {
return array();
}
}
private function liberateWritePatches(array $results) {
assert_instances_of($results, 'ArcanistLintResult');
$wrote = array();
foreach ($results as $result) {
if ($result->isPatchable()) {
$patcher = ArcanistLintPatcher::newFromArcanistLintResult($result);
$patcher->writePatchToDisk();
$wrote[] = $result->getPath();
}
}
return $wrote;
}
private function liberateBuildLintEngine($path, array $changed) {
$lint_map = array();
foreach ($changed as $module) {
$module_path = $path.'/'.$module;
$files = Filesystem::listDirectory($module_path);
$lint_map[$module] = $files;
}
$working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile(
$path,
json_encode(
array(
'project_id' => '__arcliberate__',
)),
'arc liberate');
$engine = new ArcanistLiberateLintEngine();
$engine->setWorkingCopy($working_copy);
$lint_paths = array();
foreach ($lint_map as $module => $files) {
foreach ($files as $file) {
$lint_paths[] = $module.'/'.$file;
}
}
if (!$lint_paths) {
return null;
}
$engine->setPaths($lint_paths);
$engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ERROR);
return $engine;
}
private function liberateCreateDirectory($path) {
if (Filesystem::pathExists($path)) {
if (!is_dir($path)) {
throw new ArcanistUsageException(
"Provide a directory to create or update a libphutil library in.");
}
return;
}
echo "The directory '{$path}' does not exist.";
if (!phutil_console_confirm('Do you want to create it?')) {
throw new ArcanistUsageException("Cancelled.");
}
execx('mkdir -p %s', $path);
}
private function liberateCreateLibrary($path) {
$init_path = $path.'/__phutil_library_init__.php';
if (Filesystem::pathExists($init_path)) {
return;
}
echo "Creating new libphutil library in '{$path}'.\n";
echo "Choose a name for the new library.\n";
do {
$name = phutil_console_prompt('What do you want to name this library?');
if (preg_match('/^[a-z]+$/', $name)) {
break;
} else {
echo "Library name should contain only lowercase letters.\n";
}
} while (true);
$template =
"<?php\n\n".
"phutil_register_library('{$name}', __FILE__);\n";
echo "Writing '__phutil_library_init__.php' to '{$init_path}'...\n";
Filesystem::writeFile($init_path, $template);
}
private function liberateGetChangedPaths($path) {
$mapper = $this->getPhutilMapperLocation();
$future = new ExecFuture('%s %s --find-paths-for-liberate', $mapper, $path);
return $future->resolveJSON();
}
private function getScriptPath($script) {
$root = dirname(phutil_get_library_root('arcanist'));
return $root.'/'.$script;
}
private function getPhutilMapperLocation() {
return $this->getScriptPath('scripts/phutil_mapper.php');
}
private function liberateRunRemap($path) {
phutil_load_library($path);
$paths = $this->liberateGetChangedPaths($path);
$results = $this->liberateLintModules($path, $paths);
$wrote = $this->liberateWritePatches($results);
echo json_encode($wrote, true);
return 0;
}
private function liberateRunVerify($path) {
phutil_load_library($path);
$paths = $this->liberateGetChangedPaths($path);
$results = $this->liberateLintModules($path, $paths);
- $renderer = new ArcanistLintRenderer();
+ $renderer = new ArcanistLintConsoleRenderer();
$unresolved = false;
foreach ($results as $result) {
foreach ($result->getMessages() as $message) {
echo $renderer->renderLintResult($result);
$unresolved = true;
break;
}
}
return (int)$unresolved;
}
/**
* Sanity check to catch people putting class files in the root of a libphutil
* library.
*/
private function checkForLooseFiles($path) {
foreach (Filesystem::listDirectory($path) as $item) {
if (!preg_match('/\.php$/', $item)) {
// Not a ".php" file.
continue;
}
if (preg_match('/^__/', $item)) {
// Has magic '__' prefix.
continue;
}
echo phutil_console_wrap(
"WARNING: File '{$item}' is not in a module and won't be loaded. ".
"Put source files in subdirectories, not the top level directory.\n");
}
}
}
diff --git a/src/workflow/ArcanistLintWorkflow.php b/src/workflow/ArcanistLintWorkflow.php
index 43c35ddb..96ed2109 100644
--- a/src/workflow/ArcanistLintWorkflow.php
+++ b/src/workflow/ArcanistLintWorkflow.php
@@ -1,363 +1,363 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Runs lint rules on changes.
*
* @group workflow
*/
class ArcanistLintWorkflow extends ArcanistBaseWorkflow {
const RESULT_OKAY = 0;
const RESULT_WARNINGS = 1;
const RESULT_ERRORS = 2;
const RESULT_SKIP = 3;
private $unresolvedMessages;
private $shouldAmendChanges = false;
private $shouldAmendWithoutPrompt = false;
private $shouldAmendAutofixesWithoutPrompt = false;
public function setShouldAmendChanges($should_amend) {
$this->shouldAmendChanges = $should_amend;
return $this;
}
public function setShouldAmendWithoutPrompt($should_amend) {
$this->shouldAmendWithoutPrompt = $should_amend;
return $this;
}
public function setShouldAmendAutofixesWithoutPrompt($should_amend) {
$this->shouldAmendAutofixesWithoutPrompt = $should_amend;
return $this;
}
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**lint** [__options__] [__paths__]
**lint** [__options__] --rev [__rev__]
EOTEXT
);
}
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
Supports: git, svn, hg
Run static analysis on changes to check for mistakes. If no files
are specified, lint will be run on all files which have been modified.
EOTEXT
);
}
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 'compiler', show lint warnings in suitable for your editor."
),
'advice' => array(
'help' =>
"Show lint advice, not just warnings and errors."
),
'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.',
),
'*' => 'paths',
);
}
public function requiresWorkingCopy() {
return true;
}
public function requiresRepositoryAPI() {
return true;
}
public function run() {
$working_copy = $this->getWorkingCopy();
$engine = $this->getArgument('engine');
if (!$engine) {
$engine = $working_copy->getConfigFromAnySource('lint.engine');
if (!$engine) {
throw new ArcanistNoEngineException(
"No lint engine configured for this project. Edit .arcconfig to ".
"specify a lint engine.");
}
}
$rev = $this->getArgument('rev');
$paths = $this->getArgument('paths');
if ($rev && $paths) {
throw new ArcanistUsageException("Specify either --rev or paths.");
}
$should_lint_all = $this->getArgument('lintall');
if ($paths) {
// NOTE: When the user specifies paths, we imply --lintall and show all
// warnings for the paths in question. This is easier to deal with for
// us and less confusing for users.
$should_lint_all = true;
}
$paths = $this->selectPathsForWorkflow($paths, $rev);
// is_subclass_of() doesn't autoload under HPHP.
if (!class_exists($engine) ||
!is_subclass_of($engine, 'ArcanistLintEngine')) {
throw new ArcanistUsageException(
"Configured lint engine '{$engine}' is not a subclass of ".
"'ArcanistLintEngine'.");
}
$engine = newv($engine, array());
$engine->setWorkingCopy($working_copy);
if ($this->getArgument('advice')) {
$engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ADVICE);
} else {
$engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_AUTOFIX);
}
// Propagate information about which lines changed to the lint engine.
// This is used so that the lint engine can drop warning messages
// concerning lines that weren't in the change.
$engine->setPaths($paths);
if (!$should_lint_all) {
foreach ($paths as $path) {
// Note that getChangedLines() returns null to indicate that a file
// is binary or a directory (i.e., changed lines are not relevant).
$engine->setPathChangedLines(
$path,
$this->getChangedLines($path, 'new'));
}
}
$results = $engine->run();
if ($this->getArgument('never-apply-patches')) {
$apply_patches = false;
} else {
$apply_patches = true;
}
if ($this->getArgument('apply-patches')) {
$prompt_patches = false;
} else {
$prompt_patches = true;
}
if ($this->getArgument('amend-all')) {
$this->shouldAmendChanges = true;
$this->shouldAmendWithoutPrompt = true;
}
if ($this->getArgument('amend-autofixes')) {
$prompt_autofix_patches = false;
$this->shouldAmendChanges = true;
$this->shouldAmendAutofixesWithoutPrompt = true;
} else {
$prompt_autofix_patches = true;
}
$wrote_to_disk = false;
switch ($this->getArgument('output')) {
case 'json':
$renderer = new ArcanistLintJSONRenderer();
$prompt_patches = false;
$apply_patches = $this->getArgument('apply-patches');
break;
case 'summary':
$renderer = new ArcanistLintSummaryRenderer();
break;
case 'compiler':
$renderer = new ArcanistLintLikeCompilerRenderer();
$prompt_patches = false;
$apply_patches = $this->getArgument('apply-patches');
break;
default:
- $renderer = new ArcanistLintRenderer();
+ $renderer = new ArcanistLintConsoleRenderer();
$renderer->setShowAutofixPatches($prompt_autofix_patches);
break;
}
$all_autofix = true;
foreach ($results as $result) {
$result_all_autofix = $result->isAllAutofix();
if (!$result->getMessages() && !$result_all_autofix) {
continue;
}
if (!$result_all_autofix) {
$all_autofix = false;
}
$lint_result = $renderer->renderLintResult($result);
if ($lint_result) {
echo $lint_result;
}
if ($apply_patches && $result->isPatchable()) {
$patcher = ArcanistLintPatcher::newFromArcanistLintResult($result);
$old = $patcher->getUnmodifiedFileContent();
$new = $patcher->getModifiedFileContent();
if ($prompt_patches &&
!($result_all_autofix && !$prompt_autofix_patches)) {
$old_file = $result->getFilePathOnDisk();
if (!Filesystem::pathExists($old_file)) {
$old_file = '/dev/null';
}
$new_file = new TempFile();
Filesystem::writeFile($new_file, $new);
// TODO: Improve the behavior here, make it more like
// difference_render().
passthru(csprintf("diff -u %s %s", $old_file, $new_file));
$prompt = phutil_console_format(
"Apply this patch to __%s__?",
$result->getPath());
if (!phutil_console_confirm($prompt, $default_no = false)) {
continue;
}
}
$patcher->writePatchToDisk();
$wrote_to_disk = true;
}
}
$repository_api = $this->getRepositoryAPI();
if ($wrote_to_disk &&
($repository_api instanceof ArcanistGitAPI) &&
$this->shouldAmendChanges) {
if ($this->shouldAmendWithoutPrompt ||
($this->shouldAmendAutofixesWithoutPrompt && $all_autofix)) {
echo phutil_console_format(
"<bg:yellow>** LINT NOTICE **</bg> Automatically amending HEAD ".
"with lint patches.\n");
$amend = true;
} else {
$amend = phutil_console_confirm("Amend HEAD with lint patches?");
}
if ($amend) {
execx(
'(cd %s; git commit -a --amend -C HEAD)',
$repository_api->getPath());
} else {
throw new ArcanistUsageException(
"Sort out the lint changes that were applied to the working ".
"copy and relint.");
}
}
$unresolved = array();
$has_warnings = false;
$has_errors = false;
foreach ($results as $result) {
foreach ($result->getMessages() as $message) {
if (!$message->isPatchApplied()) {
if ($message->isError()) {
$has_errors = true;
} else if ($message->isWarning()) {
$has_warnings = true;
}
$unresolved[] = $message;
}
}
}
$this->unresolvedMessages = $unresolved;
// Take the most severe lint message severity and use that
// as the result code.
if ($has_errors) {
$result_code = self::RESULT_ERRORS;
} else if ($has_warnings) {
$result_code = self::RESULT_WARNINGS;
} else {
$result_code = self::RESULT_OKAY;
}
if (!$this->getParentWorkflow()) {
if ($result_code == self::RESULT_OKAY) {
echo $renderer->renderOkayResult();
}
}
return $result_code;
}
public function getUnresolvedMessages() {
return $this->unresolvedMessages;
}
}
diff --git a/src/workflow/ArcanistSvnHookPreCommitWorkflow.php b/src/workflow/ArcanistSvnHookPreCommitWorkflow.php
index d8e4fe49..a4379fc5 100644
--- a/src/workflow/ArcanistSvnHookPreCommitWorkflow.php
+++ b/src/workflow/ArcanistSvnHookPreCommitWorkflow.php
@@ -1,242 +1,243 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Installable as an SVN "pre-commit" hook.
*
* @group workflow
*/
final class ArcanistSvnHookPreCommitWorkflow extends ArcanistBaseWorkflow {
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**svn-hook-pre-commit** __repository__ __transaction__
EOTEXT
);
}
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
Supports: svn
You can install this as an SVN pre-commit hook. For more information,
see the article "Installing Arcanist SVN Hooks" in the Arcanist
documentation.
EOTEXT
);
}
public function getArguments() {
return array(
'*' => 'svnargs',
);
}
public function shouldShellComplete() {
return false;
}
public function run() {
$svnargs = $this->getArgument('svnargs');
$repository = $svnargs[0];
$transaction = $svnargs[1];
list($commit_message) = execx(
'svnlook log --transaction %s %s',
$transaction,
$repository);
if (strpos($commit_message, '@bypass-lint') !== false) {
return 0;
}
// TODO: Do stuff with commit message.
list($changed) = execx(
'svnlook changed --transaction %s %s',
$transaction,
$repository);
$paths = array();
$changed = explode("\n", trim($changed));
foreach ($changed as $line) {
$matches = null;
preg_match('/^..\s*(.*)$/', $line, $matches);
$paths[$matches[1]] = strlen($matches[1]);
}
$resolved = array();
$failed = array();
$missing = array();
$found = array();
asort($paths);
foreach ($paths as $path => $length) {
foreach ($resolved as $rpath => $root) {
if (!strncmp($path, $rpath, strlen($rpath))) {
$resolved[$path] = $root;
continue 2;
}
}
$config = $path;
if (basename($config) == '.arcconfig') {
$resolved[$config] = $config;
continue;
}
$config = rtrim($config, '/');
$last_config = $config;
do {
if (!empty($missing[$config])) {
break;
} else if (!empty($found[$config])) {
$resolved[$path] = $found[$config];
break;
}
list($err) = exec_manual(
'svnlook cat --transaction %s %s %s',
$transaction,
$repository,
$config ? $config.'/.arcconfig' : '.arcconfig');
if ($err) {
$missing[$path] = true;
} else {
$resolved[$path] = $config ? $config.'/.arcconfig' : '.arcconfig';
$found[$config] = $resolved[$path];
break;
}
$config = dirname($config);
if ($config == '.') {
$config = '';
}
if ($config == $last_config) {
break;
}
$last_config = $config;
} while (true);
if (empty($resolved[$path])) {
$failed[] = $path;
}
}
if ($failed && $resolved) {
$failed_paths = ' '.implode("\n ", $failed);
$resolved_paths = ' '.implode("\n ", array_keys($resolved));
throw new ArcanistUsageException(
"This commit includes a mixture of files in Arcanist projects and ".
"outside of Arcanist projects. A commit which affects an Arcanist ".
"project must affect only that project.\n\n".
"Files in projects:\n\n".
$resolved_paths."\n\n".
"Files not in projects:\n\n".
$failed_paths);
}
if (!$resolved) {
// None of the affected paths are beneath a .arcconfig file.
return 0;
}
$groups = array();
foreach ($resolved as $path => $project) {
$groups[$project][] = $path;
}
if (count($groups) > 1) {
$message = array();
foreach ($groups as $project => $group) {
$message[] = "Files underneath '{$project}':\n\n";
$message[] = " ".implode("\n ", $group)."\n\n";
}
$message = implode('', $message);
throw new ArcanistUsageException(
"This commit includes a mixture of files from different Arcanist ".
"projects. A commit which affects an Arcanist project must affect ".
"only that project.\n\n".
$message);
}
$config_file = key($groups);
$project_root = dirname($config_file);
$paths = reset($groups);
list($config) = execx(
'svnlook cat --transaction %s %s %s',
$transaction,
$repository,
$config_file);
$working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile(
$project_root,
$config,
$config_file." (svnlook: {$transaction} {$repository})");
$repository_api = new ArcanistSubversionHookAPI(
$project_root,
$transaction,
$repository);
$lint_engine = $working_copy->getConfig('lint_engine');
if (!$lint_engine) {
return 0;
}
$engine = newv($lint_engine, array());
$engine->setWorkingCopy($working_copy);
$engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ERROR);
$engine->setPaths($paths);
$engine->setCommitHookMode(true);
$engine->setHookAPI($repository_api);
try {
$results = $engine->run();
} catch (ArcanistNoEffectException $no_effect) {
// Nothing to do, bail out.
return 0;
}
- $renderer = new ArcanistLintRenderer();
$failures = array();
foreach ($results as $result) {
if (!$result->getMessages()) {
continue;
}
$failures[] = $result;
}
if ($failures) {
$at = "@";
$msg = phutil_console_format(
"\n**LINT ERRORS**\n\n".
"This changeset has lint errors. You must fix all lint errors before ".
"you can commit.\n\n".
"You can add '{$at}bypass-lint' to your commit message to disable ".
"lint checks for this commit, or '{$at}nolint' to the file with ".
"errors to disable lint for that file.\n\n");
echo phutil_console_wrap($msg);
+
+ $renderer = new ArcanistLintConsoleRenderer();
foreach ($failures as $result) {
echo $renderer->renderLintResult($result);
}
return 1;
}
return 0;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 11, 07:21 (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
983966
Default Alt Text
(60 KB)

Event Timeline