Page MenuHomeSealhub

No OneTemporary

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 39e447e0..64785c92 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,308 +1,314 @@
<?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',
'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php',
'ArcanistApacheLicenseLinter' => 'lint/linter/ArcanistApacheLicenseLinter.php',
'ArcanistArcanistLinterTestCase' => 'lint/linter/__tests__/ArcanistArcanistLinterTestCase.php',
'ArcanistBackoutWorkflow' => 'workflow/ArcanistBackoutWorkflow.php',
'ArcanistBaseCommitParser' => 'parser/ArcanistBaseCommitParser.php',
'ArcanistBaseCommitParserTestCase' => 'parser/__tests__/ArcanistBaseCommitParserTestCase.php',
'ArcanistBaseTestResultParser' => 'unit/engine/ArcanistBaseTestResultParser.php',
'ArcanistBaseUnitTestEngine' => 'unit/engine/ArcanistBaseUnitTestEngine.php',
'ArcanistBaseWorkflow' => 'workflow/ArcanistBaseWorkflow.php',
'ArcanistBaseXHPASTLinter' => 'lint/linter/ArcanistBaseXHPASTLinter.php',
'ArcanistBookmarkWorkflow' => 'workflow/ArcanistBookmarkWorkflow.php',
'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php',
'ArcanistBritishTestCase' => 'configuration/__tests__/ArcanistBritishTestCase.php',
'ArcanistBrowseWorkflow' => 'workflow/ArcanistBrowseWorkflow.php',
'ArcanistBundle' => 'parser/ArcanistBundle.php',
'ArcanistBundleTestCase' => 'parser/__tests__/ArcanistBundleTestCase.php',
'ArcanistCSSLintLinter' => 'lint/linter/ArcanistCSSLintLinter.php',
+ 'ArcanistCSSLintLinterTestCase' => 'lint/linter/__tests__/ArcanistCSSLintLinterTestCase.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',
'ArcanistConfigurationDrivenLintEngine' => 'lint/engine/ArcanistConfigurationDrivenLintEngine.php',
'ArcanistCoverWorkflow' => 'workflow/ArcanistCoverWorkflow.php',
'ArcanistCppcheckLinter' => 'lint/linter/ArcanistCppcheckLinter.php',
'ArcanistCpplintLinter' => 'lint/linter/ArcanistCpplintLinter.php',
'ArcanistCpplintLinterTestCase' => 'lint/linter/__tests__/ArcanistCpplintLinterTestCase.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',
+ 'ArcanistExternalLinter' => 'lint/linter/ArcanistExternalLinter.php',
'ArcanistFeatureWorkflow' => 'workflow/ArcanistFeatureWorkflow.php',
'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php',
'ArcanistFlagWorkflow' => 'workflow/ArcanistFlagWorkflow.php',
'ArcanistFlake8Linter' => 'lint/linter/ArcanistFlake8Linter.php',
'ArcanistFlake8LinterTestCase' => 'lint/linter/__tests__/ArcanistFlake8LinterTestCase.php',
'ArcanistFutureLinter' => 'lint/linter/ArcanistFutureLinter.php',
'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php',
'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php',
'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php',
'ArcanistGitHookPreReceiveWorkflow' => 'workflow/ArcanistGitHookPreReceiveWorkflow.php',
'ArcanistHelpWorkflow' => 'workflow/ArcanistHelpWorkflow.php',
'ArcanistHgClientChannel' => 'hgdaemon/ArcanistHgClientChannel.php',
'ArcanistHgProxyClient' => 'hgdaemon/ArcanistHgProxyClient.php',
'ArcanistHgProxyServer' => 'hgdaemon/ArcanistHgProxyServer.php',
'ArcanistHgServerChannel' => 'hgdaemon/ArcanistHgServerChannel.php',
'ArcanistHookAPI' => 'repository/hookapi/ArcanistHookAPI.php',
'ArcanistInlinesWorkflow' => 'workflow/ArcanistInlinesWorkflow.php',
'ArcanistInstallCertificateWorkflow' => 'workflow/ArcanistInstallCertificateWorkflow.php',
'ArcanistJSHintLinter' => 'lint/linter/ArcanistJSHintLinter.php',
'ArcanistLandWorkflow' => 'workflow/ArcanistLandWorkflow.php',
'ArcanistLiberateWorkflow' => 'workflow/ArcanistLiberateWorkflow.php',
'ArcanistLicenseLinter' => 'lint/linter/ArcanistLicenseLinter.php',
'ArcanistLintConsoleRenderer' => 'lint/renderer/ArcanistLintConsoleRenderer.php',
'ArcanistLintEngine' => 'lint/engine/ArcanistLintEngine.php',
'ArcanistLintJSONRenderer' => 'lint/renderer/ArcanistLintJSONRenderer.php',
'ArcanistLintLikeCompilerRenderer' => 'lint/renderer/ArcanistLintLikeCompilerRenderer.php',
'ArcanistLintMessage' => 'lint/ArcanistLintMessage.php',
'ArcanistLintNoneRenderer' => 'lint/renderer/ArcanistLintNoneRenderer.php',
'ArcanistLintPatcher' => 'lint/ArcanistLintPatcher.php',
'ArcanistLintRenderer' => 'lint/renderer/ArcanistLintRenderer.php',
'ArcanistLintResult' => 'lint/ArcanistLintResult.php',
'ArcanistLintSeverity' => 'lint/ArcanistLintSeverity.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',
'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.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',
+ 'ArcanistPEP8LinterTestCase' => 'lint/linter/__tests__/ArcanistPEP8LinterTestCase.php',
'ArcanistPasteWorkflow' => 'workflow/ArcanistPasteWorkflow.php',
'ArcanistPatchWorkflow' => 'workflow/ArcanistPatchWorkflow.php',
'ArcanistPhpcsLinter' => 'lint/linter/ArcanistPhpcsLinter.php',
'ArcanistPhutilLibraryLinter' => 'lint/linter/ArcanistPhutilLibraryLinter.php',
'ArcanistPhutilTestCase' => 'unit/engine/phutil/ArcanistPhutilTestCase.php',
'ArcanistPhutilTestCaseTestCase' => 'unit/engine/phutil/testcase/ArcanistPhutilTestCaseTestCase.php',
'ArcanistPhutilTestSkippedException' => 'unit/engine/phutil/testcase/ArcanistPhutilTestSkippedException.php',
'ArcanistPhutilTestTerminatedException' => 'unit/engine/phutil/testcase/ArcanistPhutilTestTerminatedException.php',
'ArcanistPhutilXHPASTLinter' => 'lint/linter/ArcanistPhutilXHPASTLinter.php',
'ArcanistPhutilXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php',
'ArcanistPyFlakesLinter' => 'lint/linter/ArcanistPyFlakesLinter.php',
'ArcanistPyLintLinter' => 'lint/linter/ArcanistPyLintLinter.php',
'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php',
'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php',
'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php',
'ArcanistRevertWorkflow' => 'workflow/ArcanistRevertWorkflow.php',
'ArcanistRubyLinter' => 'lint/linter/ArcanistRubyLinter.php',
'ArcanistRubyLinterTestCase' => 'lint/linter/__tests__/ArcanistRubyLinterTestCase.php',
'ArcanistScalaSBTLinter' => 'lint/linter/ArcanistScalaSBTLinter.php',
'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php',
'ArcanistSetConfigWorkflow' => 'workflow/ArcanistSetConfigWorkflow.php',
'ArcanistSettings' => 'configuration/ArcanistSettings.php',
'ArcanistShellCompleteWorkflow' => 'workflow/ArcanistShellCompleteWorkflow.php',
'ArcanistSingleLintEngine' => 'lint/engine/ArcanistSingleLintEngine.php',
'ArcanistSpellingDefaultData' => 'lint/linter/spelling/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',
'ArcanistTestCase' => 'infrastructure/testing/ArcanistTestCase.php',
'ArcanistTextLinter' => 'lint/linter/ArcanistTextLinter.php',
'ArcanistTextLinterTestCase' => 'lint/linter/__tests__/ArcanistTextLinterTestCase.php',
'ArcanistTodoWorkflow' => 'workflow/ArcanistTodoWorkflow.php',
'ArcanistUncommittedChangesException' => 'exception/usage/ArcanistUncommittedChangesException.php',
'ArcanistUnitConsoleRenderer' => 'unit/renderer/ArcanistUnitConsoleRenderer.php',
'ArcanistUnitRenderer' => 'unit/renderer/ArcanistUnitRenderer.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',
'ArcanistXHPASTLintSwitchHook' => 'lint/linter/xhpast/ArcanistXHPASTLintSwitchHook.php',
'ArcanistXHPASTLintTestSwitchHook' => 'lint/linter/__tests__/ArcanistXHPASTLintTestSwitchHook.php',
'ArcanistXHPASTLinter' => 'lint/linter/ArcanistXHPASTLinter.php',
'ArcanistXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php',
'ComprehensiveLintEngine' => 'lint/engine/ComprehensiveLintEngine.php',
'ExampleLintEngine' => 'lint/engine/ExampleLintEngine.php',
'GoTestResultParser' => 'unit/engine/GoTestResultParser.php',
'GoTestResultParserTestCase' => 'unit/engine/__tests__/GoTestResultParserTestCase.php',
'NoseTestEngine' => 'unit/engine/NoseTestEngine.php',
'PHPUnitTestEngineTestCase' => 'unit/engine/__tests__/PHPUnitTestEngineTestCase.php',
'PhpunitResultParser' => 'unit/engine/PhpunitResultParser.php',
'PhpunitTestEngine' => 'unit/engine/PhpunitTestEngine.php',
'PhutilLintEngine' => 'lint/engine/PhutilLintEngine.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',
'ArcanistAnoidWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistApacheLicenseLinter' => 'ArcanistLicenseLinter',
'ArcanistArcanistLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistBackoutWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistBaseCommitParserTestCase' => 'ArcanistTestCase',
'ArcanistBaseWorkflow' => 'Phobject',
'ArcanistBaseXHPASTLinter' => 'ArcanistFutureLinter',
'ArcanistBookmarkWorkflow' => 'ArcanistFeatureWorkflow',
'ArcanistBranchWorkflow' => 'ArcanistFeatureWorkflow',
'ArcanistBritishTestCase' => 'ArcanistTestCase',
'ArcanistBrowseWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistBundleTestCase' => 'ArcanistTestCase',
- 'ArcanistCSSLintLinter' => 'ArcanistLinter',
+ 'ArcanistCSSLintLinter' => 'ArcanistExternalLinter',
+ 'ArcanistCSSLintLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCapabilityNotSupportedException' => 'Exception',
'ArcanistChooseInvalidRevisionException' => 'Exception',
'ArcanistChooseNoRevisionsException' => 'Exception',
'ArcanistCloseRevisionWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCloseWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCommentRemoverTestCase' => 'ArcanistTestCase',
'ArcanistCommitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistConduitLinter' => 'ArcanistLinter',
'ArcanistConfigurationDrivenLintEngine' => 'ArcanistLintEngine',
'ArcanistCoverWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCppcheckLinter' => 'ArcanistLinter',
'ArcanistCpplintLinter' => 'ArcanistLinter',
'ArcanistCpplintLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistDiffParserTestCase' => 'ArcanistTestCase',
'ArcanistDiffUtilsTestCase' => 'ArcanistTestCase',
'ArcanistDiffWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistDifferentialCommitMessageParserException' => 'Exception',
'ArcanistDownloadWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistEventType' => 'PhutilEventType',
'ArcanistExportWorkflow' => 'ArcanistBaseWorkflow',
+ 'ArcanistExternalLinter' => 'ArcanistFutureLinter',
'ArcanistFeatureWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistFilenameLinter' => 'ArcanistLinter',
'ArcanistFlagWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistFlake8Linter' => 'ArcanistLinter',
'ArcanistFlake8LinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistFutureLinter' => 'ArcanistLinter',
'ArcanistGeneratedLinter' => 'ArcanistLinter',
'ArcanistGetConfigWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistGitAPI' => 'ArcanistRepositoryAPI',
'ArcanistGitHookPreReceiveWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistHelpWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistHgClientChannel' => 'PhutilProtocolChannel',
'ArcanistHgServerChannel' => 'PhutilProtocolChannel',
'ArcanistInlinesWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistInstallCertificateWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistJSHintLinter' => 'ArcanistLinter',
'ArcanistLandWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLiberateWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLicenseLinter' => 'ArcanistLinter',
'ArcanistLintConsoleRenderer' => 'ArcanistLintRenderer',
'ArcanistLintJSONRenderer' => 'ArcanistLintRenderer',
'ArcanistLintLikeCompilerRenderer' => 'ArcanistLintRenderer',
'ArcanistLintNoneRenderer' => 'ArcanistLintRenderer',
'ArcanistLintSummaryRenderer' => 'ArcanistLintRenderer',
'ArcanistLintWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistLinterTestCase' => 'ArcanistPhutilTestCase',
'ArcanistListWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistMarkCommittedWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
'ArcanistMercurialParserTestCase' => 'ArcanistTestCase',
'ArcanistMergeConflictLinter' => 'ArcanistLinter',
'ArcanistNoEffectException' => 'ArcanistUsageException',
'ArcanistNoEngineException' => 'ArcanistUsageException',
'ArcanistNoLintLinter' => 'ArcanistLinter',
'ArcanistNoLintTestCaseMisnamed' => 'ArcanistLinterTestCase',
- 'ArcanistPEP8Linter' => 'ArcanistFutureLinter',
+ 'ArcanistPEP8Linter' => 'ArcanistExternalLinter',
+ 'ArcanistPEP8LinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistPasteWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistPhpcsLinter' => 'ArcanistLinter',
'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
'ArcanistPhutilTestCaseTestCase' => 'ArcanistPhutilTestCase',
'ArcanistPhutilTestSkippedException' => 'Exception',
'ArcanistPhutilTestTerminatedException' => 'Exception',
'ArcanistPhutilXHPASTLinter' => 'ArcanistBaseXHPASTLinter',
'ArcanistPhutilXHPASTLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistPyFlakesLinter' => 'ArcanistLinter',
'ArcanistPyLintLinter' => 'ArcanistLinter',
'ArcanistRepositoryAPIMiscTestCase' => 'ArcanistTestCase',
'ArcanistRepositoryAPIStateTestCase' => 'ArcanistTestCase',
'ArcanistRevertWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistRubyLinter' => 'ArcanistLinter',
'ArcanistRubyLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistScalaSBTLinter' => 'ArcanistLinter',
'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
'ArcanistSetConfigWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistShellCompleteWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistSingleLintEngine' => 'ArcanistLintEngine',
'ArcanistSpellingLinter' => 'ArcanistLinter',
'ArcanistSpellingLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI',
'ArcanistSubversionHookAPI' => 'ArcanistHookAPI',
'ArcanistSvnHookPreCommitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistTasksWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistTestCase' => 'ArcanistPhutilTestCase',
'ArcanistTextLinter' => 'ArcanistLinter',
'ArcanistTextLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistTodoWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUncommittedChangesException' => 'ArcanistUsageException',
'ArcanistUnitConsoleRenderer' => 'ArcanistUnitRenderer',
'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUpgradeWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistUsageException' => 'Exception',
'ArcanistUserAbortException' => 'ArcanistUsageException',
'ArcanistWhichWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistXHPASTLintNamingHookTestCase' => 'ArcanistTestCase',
'ArcanistXHPASTLintTestSwitchHook' => 'ArcanistXHPASTLintSwitchHook',
'ArcanistXHPASTLinter' => 'ArcanistBaseXHPASTLinter',
'ArcanistXHPASTLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ComprehensiveLintEngine' => 'ArcanistLintEngine',
'ExampleLintEngine' => 'ArcanistLintEngine',
'GoTestResultParser' => 'ArcanistBaseTestResultParser',
'GoTestResultParserTestCase' => 'ArcanistTestCase',
'NoseTestEngine' => 'ArcanistBaseUnitTestEngine',
'PHPUnitTestEngineTestCase' => 'ArcanistTestCase',
'PhpunitResultParser' => 'ArcanistBaseTestResultParser',
'PhpunitTestEngine' => 'ArcanistBaseUnitTestEngine',
'PhutilLintEngine' => 'ArcanistLintEngine',
'PhutilUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
'PhutilUnitTestEngineTestCase' => 'ArcanistTestCase',
'UnitTestableArcanistLintEngine' => 'ArcanistLintEngine',
),
));
diff --git a/src/lint/engine/ArcanistConfigurationDrivenLintEngine.php b/src/lint/engine/ArcanistConfigurationDrivenLintEngine.php
index 672a3023..c2f8b5ef 100644
--- a/src/lint/engine/ArcanistConfigurationDrivenLintEngine.php
+++ b/src/lint/engine/ArcanistConfigurationDrivenLintEngine.php
@@ -1,183 +1,208 @@
<?php
final class ArcanistConfigurationDrivenLintEngine extends ArcanistLintEngine {
private $debugMode;
public function buildLinters() {
$working_copy = $this->getWorkingCopy();
$config_path = $working_copy->getProjectPath('.arclint');
if (!Filesystem::pathExists($config_path)) {
throw new Exception(
"Unable to find '.arclint' file to configure linters. Create a ".
"'.arclint' file in the root directory of the working copy.");
}
$data = Filesystem::readFile($config_path);
$config = json_decode($data, true);
if (!is_array($config)) {
throw new Exception(
"Expected '.arclint' file to be a valid JSON file, but failed to ".
"decode it: {$config_path}");
}
$linters = $this->loadAvailableLinters();
PhutilTypeSpec::checkMap(
$config,
array(
'linters' => 'map<string, map<string, wild>>',
'debug' => 'optional bool',
));
$this->debugMode = idx($config, 'debug', false);
$built_linters = array();
$all_paths = $this->getPaths();
foreach ($config['linters'] as $name => $spec) {
+ $type = idx($spec, 'type');
+ if ($type !== null) {
+ if (empty($linters[$type])) {
+ $list = implode(', ', array_keys($linters));
+ throw new Exception(
+ "Linter '{$name}' specifies invalid type '{$type}'. Available ".
+ "linters are: {$list}.");
+ }
+
+ $linter = clone $linters[$type];
+ $linter->setEngine($this);
+ $more = $linter->getLinterConfigurationOptions();
+ } else {
+ // We'll raise an error below about the invalid "type" key.
+ $linter = null;
+ $more = array();
+ }
+
PhutilTypeSpec::checkMap(
$spec,
array(
'type' => 'string',
'include' => 'optional string | list<string>',
'exclude' => 'optional string | list<string>',
- ));
-
- $type = $spec['type'];
- if (empty($linters[$type])) {
- $list = implode(', ', array_keys($linters));
- throw new Exception(
- "Linter '{$name}' specifies invalid type '{$type}'. Available ".
- "linters are: {$list}.");
+ ) + $more);
+
+ foreach ($more as $key => $value) {
+ if (array_key_exists($key, $spec)) {
+ try {
+ $linter->setLinterConfigurationValue($key, $spec);
+ } catch (Exception $ex) {
+ $message = pht(
+ 'Error in parsing ".arclint" file, in key "%s" for '.
+ 'linter "%s".',
+ $key,
+ $name);
+ throw new PhutilProxyException($message, $ex);
+ }
+ }
}
- $linter = clone $linters[$type];
-
$include = (array)idx($spec, 'include', array());
$exclude = (array)idx($spec, 'exclude', array());
$this->validateRegexps($include, $name, 'include');
$this->validateRegexps($exclude, $name, 'exclude');
$this->debugLog('Examining paths for linter "%s".', $name);
$paths = $this->matchPaths($all_paths, $include, $exclude);
$this->debugLog(
'Found %d matching paths for linter "%s".',
count($paths),
$name);
$linter->setPaths($paths);
+
$built_linters[] = $linter;
}
return $built_linters;
}
private function loadAvailableLinters() {
$linters = id(new PhutilSymbolLoader())
->setAncestorClass('ArcanistLinter')
->loadObjects();
$map = array();
foreach ($linters as $linter) {
$name = $linter->getLinterConfigurationName();
// This linter isn't selectable through configuration.
if ($name === null) {
continue;
}
if (empty($map[$name])) {
$map[$name] = $linter;
continue;
}
$orig_class = get_class($map[$name]);
$this_class = get_class($linter);
throw new Exception(
"Two linters ({$orig_class}, {$this_class}) both have the same ".
"configuration name ({$name}). Linters must have unique configuration ".
"names.");
}
return $map;
}
private function matchPaths(array $paths, array $include, array $exclude) {
$debug = $this->debugMode;
$match = array();
foreach ($paths as $path) {
$this->debugLog("Examining path '%s'...", $path);
$keep = false;
if (!$include) {
$keep = true;
$this->debugLog(
" Including path by default because there is no 'include' rule.");
} else {
$this->debugLog(' Testing "include" rules.');
foreach ($include as $rule) {
if (preg_match($rule, $path)) {
$keep = true;
$this->debugLog(' Path matches include rule: %s', $rule);
break;
} else {
$this->debugLog(' Path does not match include rule: %s', $rule);
}
}
}
if (!$keep) {
$this->debugLog(' Path does not match any include rules, discarding.');
continue;
}
if ($exclude) {
$this->debugLog(' Testing "exclude" rules.');
foreach ($exclude as $rule) {
if (preg_match($rule, $path)) {
$this->debugLog(' Path matches "exclude" rule: %s', $rule);
continue 2;
} else {
$this->debugLog(' Path does not match "exclude" rule: %s', $rule);
}
}
}
$this->debugLog(' Path matches.');
$match[] = $path;
}
return $match;
}
private function validateRegexps(array $regexps, $linter, $config) {
foreach ($regexps as $regexp) {
$ok = @preg_match($regexp, '');
if ($ok === false) {
throw new Exception(
pht(
'Regular expression "%s" (in "%s" configuration for linter "%s") '.
'is not a valid regular expression.',
$regexp,
$config,
$linter));
}
}
}
private function debugLog($pattern /* , $arg, ... */) {
if (!$this->debugMode) {
return;
}
$console = PhutilConsole::getConsole();
$argv = func_get_args();
$argv[0] .= "\n";
call_user_func_array(array($console, 'writeErr'), $argv);
}
+
}
diff --git a/src/lint/linter/ArcanistCSSLintLinter.php b/src/lint/linter/ArcanistCSSLintLinter.php
index 81042cc6..a40c3cdc 100644
--- a/src/lint/linter/ArcanistCSSLintLinter.php
+++ b/src/lint/linter/ArcanistCSSLintLinter.php
@@ -1,125 +1,96 @@
<?php
/**
* Uses "CSS lint" to detect checkstyle errors in css code.
* To use this linter, you must install CSS lint.
* ##npm install csslint -g## (don't forget the -g flag or NPM will install
* the package locally).
*
* Based on ArcanistPhpcsLinter.php
*
* lint.csslint.options
* lint.csslint.bin
*
* @group linter
*/
-final class ArcanistCSSLintLinter extends ArcanistLinter {
-
- private $reports;
+final class ArcanistCSSLintLinter extends ArcanistExternalLinter {
public function getLinterName() {
return 'CSSLint';
}
- public function getLintSeverityMap() {
- return array();
- }
-
- public function getLintNameMap() {
- return array();
+ public function getMandatoryFlags() {
+ return '--format=lint-xml';
}
- public function getCSSLintOptions() {
+ public function getDefaultFlags() {
$working_copy = $this->getEngine()->getWorkingCopy();
$options = $working_copy->getConfig('lint.csslint.options');
+ // TODO: Deprecation warning.
return $options;
}
- private function getCSSLintPath() {
+ public function getDefaultBinary() {
+ // TODO: Deprecation warning.
$working_copy = $this->getEngine()->getWorkingCopy();
$bin = $working_copy->getConfig('lint.csslint.bin');
-
- if ($bin === null) {
- $bin = 'csslint';
+ if ($bin) {
+ return $bin;
}
- return $bin;
+ return 'csslint';
}
- public function willLintPaths(array $paths) {
- $csslint_bin = $this->getCSSLintPath();
- $csslint_options = $this->getCSSLintOptions();
- $futures = array();
-
- foreach ($paths as $path) {
- $filepath = $this->getEngine()->getFilePathOnDisk($path);
- $this->reports[$path] = new TempFile();
- $futures[$path] = new ExecFuture('%C %C --format=lint-xml >%s %s',
- $csslint_bin,
- $csslint_options,
- $this->reports[$path],
- $filepath);
- }
-
- foreach (Futures($futures)->limit(8) as $path => $future) {
- $this->results[$path] = $future->resolve();
- }
-
- libxml_use_internal_errors(true);
+ public function getInstallInstructions() {
+ return pht('Install CSSLint using `npm install -g csslint`.');
}
- public function lintPath($path) {
- list($rc, $stdout) = $this->results[$path];
-
- $report = Filesystem::readFile($this->reports[$path]);
+ protected function parseLinterOutput($path, $err, $stdout, $stderr) {
+ $report_dom = new DOMDocument();
+ $ok = @$report_dom->loadXML($stdout);
- if ($report) {
- $report_dom = new DOMDocument();
- libxml_clear_errors();
- $report_dom->loadXML($report);
- }
- if (!$report || libxml_get_errors()) {
- throw new ArcanistUsageException('CSS Linter failed to load ' .
- 'reporting file. Something happened when running csslint. ' .
- "Output:\n$stdout" .
- "\nTry running lint with --trace flag to get more details.");
+ if (!$ok) {
+ return false;
}
$files = $report_dom->getElementsByTagName('file');
+ $messages = array();
foreach ($files as $file) {
foreach ($file->childNodes as $child) {
if (!($child instanceof DOMElement)) {
continue;
}
$data = $this->getData($path);
$lines = explode("\n", $data);
$name = $this->getLinterName() . ' - ' . $child->getAttribute('reason');
$severity = ($child->getAttribute('severity') == 'warning')
? ArcanistLintSeverity::SEVERITY_WARNING
: ArcanistLintSeverity::SEVERITY_ERROR;
$message = new ArcanistLintMessage();
$message->setPath($path);
$message->setLine($child->getAttribute('line'));
$message->setChar($child->getAttribute('char'));
$message->setCode($child->getAttribute('severity'));
$message->setName($name);
$message->setDescription(
$child->getAttribute('reason').
"\nEvidence:".$child->getAttribute('evidence'));
$message->setSeverity($severity);
if ($child->hasAttribute('line')) {
$line = $lines[$child->getAttribute('line') - 1];
$text = substr($line, $child->getAttribute('char') - 1);
$message->setOriginalText($text);
}
- $this->addLintMessage($message);
+ $messages[] = $message;
}
}
+
+ return $messages;
}
}
diff --git a/src/lint/linter/ArcanistExternalLinter.php b/src/lint/linter/ArcanistExternalLinter.php
new file mode 100644
index 00000000..d8423754
--- /dev/null
+++ b/src/lint/linter/ArcanistExternalLinter.php
@@ -0,0 +1,473 @@
+<?php
+
+/**
+ * Base class for linters which operate by invoking an external program and
+ * parsing results.
+ *
+ * @task bin Interpreters, Binaries and Flags
+ * @task parse Parsing Linter Output
+ * @task exec Executing the Linter
+ */
+abstract class ArcanistExternalLinter extends ArcanistFutureLinter {
+
+ private $bin;
+ private $interpreter;
+ private $flags;
+
+
+/* -( Interpreters, Binaries and Flags )----------------------------------- */
+
+
+ /**
+ * Return the default binary name or binary path where the external linter
+ * lives. This can either be a binary name which is expected to be installed
+ * in PATH (like "jshint"), or a relative path from the project root
+ * (like "resources/support/bin/linter") or an absolute path.
+ *
+ * If the binary needs an interpreter (like "python" or "node"), you should
+ * also override @{method:shouldUseInterpreter} and provide the interpreter
+ * in @{method:getDefaultInterpreter}.
+ *
+ * @return string Default binary to execute.
+ * @task bin
+ */
+ abstract public function getDefaultBinary();
+
+
+ /**
+ * Return a human-readable string describing how to install the linter. This
+ * is normally something like "Install such-and-such by running `npm install
+ * -g such-and-such`.", but will differ from linter to linter.
+ *
+ * @return string Human readable install instructions
+ * @task bin
+ */
+ abstract public function getInstallInstructions();
+
+
+ /**
+ * Return true to continue when the external linter exits with an error code.
+ * By default, linters which exit with an error code are assumed to have
+ * failed. However, some linters exit with a specific code to indicate that
+ * lint messages were detected.
+ *
+ * If the linter sometimes raises errors during normal operation, override
+ * this method and return true so execution continues when it exits with
+ * a nonzero status.
+ *
+ * @param bool Return true to continue on nonzero error code.
+ * @task bin
+ */
+ public function shouldExpectCommandErrors() {
+ return false;
+ }
+
+
+ /**
+ * Return true to indicate that the external linter can read input from
+ * stdin, rather than requiring a file. If this mode is supported, it is
+ * slightly more flexible and may perform better, and is thus preferable.
+ *
+ * To send data over stdin instead of via a command line parameter, override
+ * this method and return true. If the linter also needs a command line
+ * flag (like `--stdin` or `-`), override
+ * @{method:getReadDataFromStdinFilename} to provide it.
+ *
+ * For example, linters are normally invoked something like this:
+ *
+ * $ linter file.js
+ *
+ * If you override this method, invocation will be more similar to this:
+ *
+ * $ linter < file.js
+ *
+ * If you additionally override @{method:getReadDataFromStdinFilename} to
+ * return `"-"`, invocation will be similar to this:
+ *
+ * $ linter - < file.js
+ *
+ * @return bool True to send data over stdin.
+ * @task bin
+ */
+ public function supportsReadDataFromStdin() {
+ return false;
+ }
+
+
+ /**
+ * If the linter can read data over stdin, override
+ * @{method:supportsReadDataFromStdin} and then optionally override this
+ * method to provide any required arguments (like `-` or `--stdin`). See
+ * that method for discussion.
+ *
+ * @return string|null Additional arguments required by the linter when
+ * operating in stdin mode.
+ * @task bin
+ */
+ public function getReadDataFromStdinFilename() {
+ return null;
+ }
+
+
+ /**
+ * Provide mandatory, non-overridable flags to the linter. Generally these
+ * are format flags, like `--format=xml`, which must always be given for
+ * the output to be usable.
+ *
+ * Flags which are not mandatory should be provided in
+ * @{method:getDefaultFlags} instead.
+ *
+ * @return string|null Mandatory flags, like `"--format=xml"`.
+ * @task bin
+ */
+ protected function getMandatoryFlags() {
+ return null;
+ }
+
+
+ /**
+ * Provide default, overridable flags to the linter. Generally these are
+ * configuration flags which affect behavior but aren't critical. Flags
+ * which are required should be provided in @{method:getMandatoryFlags}
+ * instead.
+ *
+ * Default flags can be overridden with @{method:setFlags}.
+ *
+ * @return string|null Overridable default flags.
+ * @task bin
+ */
+ protected function getDefaultFlags() {
+ return null;
+ }
+
+
+ /**
+ * Override default flags with custom flags. If not overridden, flags provided
+ * by @{method:getDefaultFlags} are used.
+ *
+ * @param string New flags.
+ * @return this
+ * @task bin
+ */
+ final public function setFlags($flags) {
+ $this->flags = $flags;
+ return $this;
+ }
+
+
+ /**
+ * Return the binary or script to execute. This method synthesizes defaults
+ * and configuration. You can override the binary with @{method:setBinary}.
+ *
+ * @return string Binary to execute.
+ * @task bin
+ */
+ final public function getBinary() {
+ return coalesce($this->bin, $this->getDefaultBinary());
+ }
+
+
+ /**
+ * Override the default binary with a new one.
+ *
+ * @param string New binary.
+ * @return this
+ * @task bin
+ */
+ final public function setBinary($bin) {
+ $this->bin = $bin;
+ return $this;
+ }
+
+
+ /**
+ * Return true if this linter should use an interpreter (like "python" or
+ * "node") in addition to the script.
+ *
+ * After overriding this method to return `true`, override
+ * @{method:getDefaultInterpreter} to set a default.
+ *
+ * @return bool True to use an interpreter.
+ * @task bin
+ */
+ public function shouldUseInterpreter() {
+ return false;
+ }
+
+
+ /**
+ * Return the default interpreter, like "python" or "node". This method is
+ * only invoked if @{method:shouldUseInterpreter} has been overridden to
+ * return `true`.
+ *
+ * @return string Default interpreter.
+ * @task bin
+ */
+ public function getDefaultInterpreter() {
+ throw new Exception("Incomplete implementation!");
+ }
+
+
+ /**
+ * Get the effective interpreter. This method synthesizes configuration and
+ * defaults.
+ *
+ * @return string Effective interpreter.
+ * @task bin
+ */
+ final public function getInterpreter() {
+ return coalesce($this->interpreter, $this->getDefaultInterpreter());
+ }
+
+
+ /**
+ * Set the interpreter, overriding any default.
+ *
+ * @param string New interpreter.
+ * @return this
+ * @task bin
+ */
+ final public function setInterpreter($interpreter) {
+ $this->interpreter = $interpreter;
+ return $this;
+ }
+
+
+/* -( Parsing Linter Output )---------------------------------------------- */
+
+
+ /**
+ * Parse the output of the external lint program into objects of class
+ * @{class:ArcanistLintMessage} which `arc` can consume. Generally, this
+ * means examining the output and converting each warning or error into a
+ * message.
+ *
+ * If parsing fails, returning `false` will cause the caller to throw an
+ * appropriate exception. (You can also throw a more specific exception if
+ * you're able to detect a more specific condition.) Otherwise, return a list
+ * of messages.
+ *
+ * @param string Path to the file being linted.
+ * @param int Exit code of the linter.
+ * @param string Stdout of the linter.
+ * @param string Stderr of the linter.
+ * @return list<ArcanistLintMessage>|false List of lint messages, or false
+ * to indicate parser failure.
+ * @task parse
+ */
+ abstract protected function parseLinterOutput($path, $err, $stdout, $stderr);
+
+
+/* -( Executing the Linter )----------------------------------------------- */
+
+
+ /**
+ * Check that the binary and interpreter (if applicable) exist, and throw
+ * an exception with a message about how to install them if they do not.
+ *
+ * @return void
+ */
+ final public function checkBinaryConfiguration() {
+ $interpreter = null;
+ if ($this->shouldUseInterpreter()) {
+ $interpreter = $this->getInterpreter();
+ }
+
+ $binary = $this->getBinary();
+
+ // NOTE: If we have an interpreter, we don't require the script to be
+ // executable (so we just check that the path exists). Otherwise, the
+ // binary must be executable.
+
+ if ($interpreter) {
+ if (!Filesystem::binaryExists($interpreter)) {
+ throw new ArcanistUsageException(
+ pht(
+ 'Unable to locate interpreter "%s" to run linter %s. You may '.
+ 'need to install the intepreter, or adjust your linter '.
+ 'configuration.',
+ "\nTO INSTALL: %s",
+ $interpreter,
+ get_class($this),
+ $this->getInstallInstructions()));
+ }
+ if (!Filesystem::pathExists($binary)) {
+ throw new ArcanistUsageException(
+ pht(
+ 'Unable to locate script "%s" to run linter %s. You may need '.
+ 'to install the script, or adjust your linter configuration. '.
+ "\nTO INSTALL: %s",
+ $binary,
+ get_class($this),
+ $this->getInstallInstructions()));
+ }
+ } else {
+ if (!Filesystem::binaryExists($binary)) {
+ throw new ArcanistUsageException(
+ pht(
+ 'Unable to locate binary "%s" to run linter %s. You may need '.
+ 'to install the binary, or adjust your linter configuration. '.
+ "\nTO INSTALL: %s",
+ $binary,
+ get_class($this),
+ $this->getInstallInstructions()));
+ }
+ }
+ }
+
+
+ /**
+ * Get the composed executable command, including the interpreter and binary
+ * but without flags or paths. This can be used to execute `--version`
+ * commands.
+ *
+ * @return string Command to execute the raw linter.
+ * @task exec
+ */
+ protected function getExecutableCommand() {
+ $this->checkBinaryConfiguration();
+
+ $interpreter = null;
+ if ($this->shouldUseInterpreter()) {
+ $interpreter = $this->getInterpreter();
+ }
+
+ $binary = $this->getBinary();
+
+ if ($interpreter) {
+ $bin = csprintf('%s %s', $interpreter, $binary);
+ } else {
+ $bin = csprintf('%s', $binary);
+ }
+
+ return $bin;
+ }
+
+
+ /**
+ * Get the composed flags for the executable, including both mandatory and
+ * configured flags.
+ *
+ * @return string Composed flags.
+ * @task exec
+ */
+ protected function getCommandFlags() {
+ return csprintf(
+ '%C %C',
+ $this->getMandatoryFlags(),
+ coalesce($this->flags, $this->getDefaultFlags()));
+ }
+
+
+ protected function buildFutures(array $paths) {
+ $executable = $this->getExecutableCommand();
+
+ $bin = csprintf('%C %C', $executable, $this->getCommandFlags());
+
+ $futures = array();
+ foreach ($paths as $path) {
+ if ($this->supportsReadDataFromStdin()) {
+ $future = new ExecFuture(
+ '%C %C',
+ $bin,
+ $this->getReadDataFromStdinFilename());
+ $future->write($this->getFileData($path));
+ } else {
+ // TODO: In commit hook mode, we need to do more handling here.
+ $disk_path = $this->getEngine()->getFilePathOnDisk($path);
+ $future = new ExecFuture('%C %s', $bin, $disk_path);
+ }
+
+ $futures[$path] = $future;
+ }
+
+ return $futures;
+ }
+
+ protected function resolveFuture($path, Future $future) {
+ list($err, $stdout, $stderr) = $future->resolve();
+ if ($err && !$this->shouldExpectCommandErrors()) {
+ $future->resolvex();
+ }
+
+ $messages = $this->parseLinterOutput($path, $err, $stdout, $stderr);
+
+ if ($messages === false) {
+ $future->resolvex();
+ return;
+ }
+
+ foreach ($messages as $message) {
+ $this->addLintMessage($message);
+ }
+ }
+
+
+ public function getLinterConfigurationOptions() {
+ $options = array(
+ 'bin' => 'optional string | list<string>',
+ 'flags' => 'optional string',
+ );
+
+ if ($this->shouldUseInterpreter()) {
+ $options['interpreter'] = 'optional string | list<string>';
+ }
+
+ return $options;
+ }
+
+ public function setLinterConfigurationValue($key, $value) {
+ switch ($key) {
+ case 'interpreter':
+ $working_copy = $this->getEngine()->getWorkingCopy();
+ $root = $working_copy->getProjectRoot();
+
+ foreach ((array)$value as $path) {
+ if (Filesystem::binaryExists($path)) {
+ $this->setInterpreter($path);
+ return;
+ }
+
+ $path = Filesystem::resolvePath($path, $root);
+
+ if (Filesystem::binaryExists($path)) {
+ $this->setInterpreter($path);
+ return;
+ }
+ }
+
+ throw new Exception(
+ pht('None of the configured interpreters can be located.'));
+ case 'bin':
+ $is_script = $this->shouldUseInterpreter();
+
+ $working_copy = $this->getEngine()->getWorkingCopy();
+ $root = $working_copy->getProjectRoot();
+
+ foreach ((array)$value as $path) {
+ if (!$is_script && Filesystem::binaryExists($path)) {
+ $this->setBinary($path);
+ return;
+ }
+
+ $path = Filesystem::resolvePath($path, $root);
+ if ((!$is_script && Filesystem::binaryExists($path)) ||
+ ($is_script && Filesystem::pathExists($path))) {
+ $this->setBinary($path);
+ return;
+ }
+ }
+
+ throw new Exception(
+ pht('None of the configured binaries can be located.'));
+ case 'flags':
+ if (strlen($value)) {
+ $this->setFlags($value);
+ }
+ break;
+ }
+
+ return parent::setLinterConfigurationValue($key, $value);
+ }
+
+}
diff --git a/src/lint/linter/ArcanistLinter.php b/src/lint/linter/ArcanistLinter.php
index d22fd48b..316538b2 100644
--- a/src/lint/linter/ArcanistLinter.php
+++ b/src/lint/linter/ArcanistLinter.php
@@ -1,259 +1,267 @@
<?php
/**
* Implements lint rules, like syntax checks for a specific language.
*
* @group linter
* @stable
*/
abstract class ArcanistLinter {
const GRANULARITY_FILE = 1;
const GRANULARITY_DIRECTORY = 2;
const GRANULARITY_REPOSITORY = 3;
const GRANULARITY_GLOBAL = 4;
protected $paths = array();
protected $data = array();
protected $engine;
protected $activePath;
protected $messages = array();
protected $stopAllLinters = false;
private $customSeverityMap = array();
private $config = array();
public function setCustomSeverityMap(array $map) {
$this->customSeverityMap = $map;
return $this;
}
public function setConfig(array $config) {
$this->config = $config;
return $this;
}
protected function getConfig($key, $default = null) {
return idx($this->config, $key, $default);
}
public function getActivePath() {
return $this->activePath;
}
public function getOtherLocation($offset, $path = null) {
if ($path === null) {
$path = $this->getActivePath();
}
list($line, $char) = $this->getEngine()->getLineAndCharFromOffset(
$path,
$offset);
return array(
'path' => $path,
'line' => $line + 1,
'char' => $char,
);
}
public function stopAllLinters() {
$this->stopAllLinters = true;
return $this;
}
public function didStopAllLinters() {
return $this->stopAllLinters;
}
public function addPath($path) {
$this->paths[$path] = $path;
return $this;
}
public function setPaths(array $paths) {
$this->paths = $paths;
return $this;
}
public function getPaths() {
return array_values($this->paths);
}
public function addData($path, $data) {
$this->data[$path] = $data;
return $this;
}
protected function getData($path) {
if (!array_key_exists($path, $this->data)) {
$this->data[$path] = $this->getEngine()->loadData($path);
}
return $this->data[$path];
}
public function setEngine(ArcanistLintEngine $engine) {
$this->engine = $engine;
return $this;
}
protected function getEngine() {
return $this->engine;
}
public function getCacheVersion() {
return 0;
}
public function getLintMessageFullCode($short_code) {
return $this->getLinterName().$short_code;
}
public function getLintMessageSeverity($code) {
$map = $this->customSeverityMap;
if (isset($map[$code])) {
return $map[$code];
}
$map = $this->getLintSeverityMap();
if (isset($map[$code])) {
return $map[$code];
}
return ArcanistLintSeverity::SEVERITY_ERROR;
}
public function isMessageEnabled($code) {
return ($this->getLintMessageSeverity($code) !==
ArcanistLintSeverity::SEVERITY_DISABLED);
}
public function getLintMessageName($code) {
$map = $this->getLintNameMap();
if (isset($map[$code])) {
return $map[$code];
}
return "Unknown lint message!";
}
protected function addLintMessage(ArcanistLintMessage $message) {
if (!$this->getEngine()->getCommitHookMode()) {
$root = $this->getEngine()->getWorkingCopy()->getProjectRoot();
$path = Filesystem::resolvePath($message->getPath(), $root);
$message->setPath(Filesystem::readablePath($path, $root));
}
$this->messages[] = $message;
return $message;
}
public function getLintMessages() {
return $this->messages;
}
protected function raiseLintAtLine(
$line,
$char,
$code,
$desc,
$original = null,
$replacement = null) {
$message = id(new ArcanistLintMessage())
->setPath($this->getActivePath())
->setLine($line)
->setChar($char)
->setCode($this->getLintMessageFullCode($code))
->setSeverity($this->getLintMessageSeverity($code))
->setName($this->getLintMessageName($code))
->setDescription($desc)
->setOriginalText($original)
->setReplacementText($replacement);
return $this->addLintMessage($message);
}
protected function raiseLintAtPath(
$code,
$desc) {
return $this->raiseLintAtLine(null, null, $code, $desc, null, null);
}
protected function raiseLintAtOffset(
$offset,
$code,
$desc,
$original = null,
$replacement = null) {
$path = $this->getActivePath();
$engine = $this->getEngine();
if ($offset === null) {
$line = null;
$char = null;
} else {
list($line, $char) = $engine->getLineAndCharFromOffset($path, $offset);
}
return $this->raiseLintAtLine(
$line + 1,
$char + 1,
$code,
$desc,
$original,
$replacement);
}
public function willLintPath($path) {
$this->stopAllLinters = false;
$this->activePath = $path;
}
public function canRun() {
return true;
}
abstract public function willLintPaths(array $paths);
abstract public function lintPath($path);
abstract public function getLinterName();
public function didRunLinters() {
// This is a hook.
}
protected function isCodeEnabled($code) {
$severity = $this->getLintMessageSeverity($code);
return $this->getEngine()->isSeverityEnabled($severity);
}
public function getLintSeverityMap() {
return array();
}
public function getLintNameMap() {
return array();
}
public function getCacheGranularity() {
return self::GRANULARITY_FILE;
}
public function isBinaryFile($path) {
// Note that we need the lint engine set before this can be used.
return ArcanistDiffUtils::isHeuristicBinaryFile($this->getData($path));
}
/**
* If this linter is selectable via `.arclint` configuration files, return
* a short, human-readable name to identify it. For example, `"jshint"` or
* `"pep8"`.
*
* If you do not implement this method, the linter will not be selectable
* through `.arclint` files.
*/
public function getLinterConfigurationName() {
return null;
}
+ public function getLinterConfigurationOptions() {
+ return array();
+ }
+
+ public function setLinterConfigurationValue($key, $value) {
+ throw new Exception("Incomplete implementation: {$key}!");
+ }
+
}
diff --git a/src/lint/linter/ArcanistPEP8Linter.php b/src/lint/linter/ArcanistPEP8Linter.php
index 346fd2ef..c9d26990 100644
--- a/src/lint/linter/ArcanistPEP8Linter.php
+++ b/src/lint/linter/ArcanistPEP8Linter.php
@@ -1,129 +1,101 @@
<?php
/**
* Uses "pep8.py" to enforce PEP8 rules for Python.
*
* @group linter
*/
-final class ArcanistPEP8Linter extends ArcanistFutureLinter {
+final class ArcanistPEP8Linter extends ArcanistExternalLinter {
public function getLinterName() {
return 'PEP8';
}
- public function getLintSeverityMap() {
- return array();
- }
-
- public function getLintNameMap() {
- return array();
- }
-
public function getCacheVersion() {
- list($stdout) = execx('%C --version', $this->getPEP8Path());
- return $stdout.$this->getPEP8Options();
+ list($stdout) = execx('%C --version', $this->getExecutableCommand());
+ return $stdout.$this->getCommandFlags();
}
- public function getPEP8Options() {
+ public function getDefaultFlags() {
+ // TODO: Warn that all of this is deprecated.
+
$working_copy = $this->getEngine()->getWorkingCopy();
$options = $working_copy->getConfig('lint.pep8.options');
if ($options === null) {
$options = $this->getConfig('options');
}
return $options;
}
- public function getPEP8Path() {
- $working_copy = $this->getEngine()->getWorkingCopy();
- $prefix = $working_copy->getConfig('lint.pep8.prefix');
- $bin = $working_copy->getConfig('lint.pep8.bin');
-
- if ($bin === null && $prefix === null) {
- $bin = csprintf('/usr/bin/env python2.6 %s',
- phutil_get_library_root('arcanist').
- '/../externals/pep8/pep8.py');
- } else {
- if ($bin === null) {
- $bin = 'pep8';
- }
-
- if ($prefix !== null) {
- if (!Filesystem::pathExists($prefix.'/'.$bin)) {
- throw new ArcanistUsageException(
- "Unable to find PEP8 binary in a specified directory. Make sure ".
- "that 'lint.pep8.prefix' and 'lint.pep8.bin' keys are set ".
- "correctly. If you'd rather use a copy of PEP8 installed ".
- "globally, you can just remove these keys from your .arcconfig.");
- }
-
- $bin = csprintf("%s/%s", $prefix, $bin);
+ public function shouldUseInterpreter() {
+ return ($this->getDefaultBinary() !== 'pep8');
+ }
- return $bin;
- }
+ public function getDefaultInterpreter() {
+ return 'python2.6';
+ }
- // Look for globally installed PEP8
- list($err) = exec_manual('which %s', $bin);
- if ($err) {
- throw new ArcanistUsageException(
- "PEP8 does not appear to be installed on this system. Install it ".
- "(e.g., with 'easy_install pep8') or configure ".
- "'lint.pep8.prefix' in your .arcconfig to point to the directory ".
- "where it resides.");
- }
+ public function getDefaultBinary() {
+ if (Filesystem::binaryExists('pep8')) {
+ return 'pep8';
}
- return $bin;
- }
+ $working_copy = $this->getEngine()->getWorkingCopy();
+ $old_prefix = $working_copy->getConfig('lint.pep8.prefix');
+ $old_bin = $working_copy->getConfig('lint.pep8.bin');
- protected function buildFutures(array $paths) {
- $severity = ArcanistLintSeverity::SEVERITY_WARNING;
- if (!$this->getEngine()->isSeverityEnabled($severity)) {
- return;
+ if ($old_prefix || $old_bin) {
+ // TODO: Deprecation warning.
+ $old_bin = nonempty($old_bin, 'pep8');
+ return $old_prefix.'/'.$old_bin;
}
- $pep8_bin = $this->getPEP8Path();
- $options = $this->getPEP8Options();
-
- $futures = array();
+ $arc_root = dirname(phutil_get_library_root('arcanist'));
+ return $arc_root.'/externals/pep8/pep8.py';
+ }
- foreach ($paths as $path) {
- $futures[$path] = new ExecFuture(
- "%C %C %s",
- $pep8_bin,
- $options,
- $this->getEngine()->getFilePathOnDisk($path));
- }
+ public function getInstallInstructions() {
+ return pht('Install PEP8 using `easy_install pep8`.');
+ }
- return $futures;
+ public function shouldExpectCommandErrors() {
+ return true;
}
- protected function resolveFuture($path, Future $future) {
- list($rc, $stdout) = $future->resolve();
- $lines = explode("\n", $stdout);
+ protected function parseLinterOutput($path, $err, $stdout, $stderr) {
+ $lines = phutil_split_lines($stdout, $retain_endings = false);
+
$messages = array();
foreach ($lines as $line) {
$matches = null;
if (!preg_match('/^(.*?):(\d+):(\d+): (\S+) (.*)$/', $line, $matches)) {
continue;
}
foreach ($matches as $key => $match) {
$matches[$key] = trim($match);
}
if (!$this->isMessageEnabled($matches[4])) {
continue;
}
$message = new ArcanistLintMessage();
$message->setPath($path);
$message->setLine($matches[2]);
$message->setChar($matches[3]);
$message->setCode($matches[4]);
$message->setName('PEP8 '.$matches[4]);
$message->setDescription($matches[5]);
$message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING);
- $this->addLintMessage($message);
+
+ $messages[] = $message;
}
+
+ if ($err && !$messages) {
+ return false;
+ }
+
+ return $messages;
}
}
diff --git a/src/lint/linter/__tests__/ArcanistCSSLintLinterTestCase.php b/src/lint/linter/__tests__/ArcanistCSSLintLinterTestCase.php
new file mode 100644
index 00000000..5e869966
--- /dev/null
+++ b/src/lint/linter/__tests__/ArcanistCSSLintLinterTestCase.php
@@ -0,0 +1,12 @@
+<?php
+
+final class ArcanistCSSLintLinterTestCase
+ extends ArcanistArcanistLinterTestCase {
+
+ public function testCSSLintLinter() {
+ $this->executeTestsInDirectory(
+ dirname(__FILE__).'/csslint/',
+ new ArcanistCSSLintLinter());
+ }
+
+}
diff --git a/src/lint/linter/__tests__/ArcanistPEP8LinterTestCase.php b/src/lint/linter/__tests__/ArcanistPEP8LinterTestCase.php
new file mode 100644
index 00000000..184e4879
--- /dev/null
+++ b/src/lint/linter/__tests__/ArcanistPEP8LinterTestCase.php
@@ -0,0 +1,12 @@
+<?php
+
+final class ArcanistPEP8LinterTestCase
+ extends ArcanistArcanistLinterTestCase {
+
+ public function testPEP8Linter() {
+ $this->executeTestsInDirectory(
+ dirname(__FILE__).'/pep8/',
+ new ArcanistPEP8Linter());
+ }
+
+}
diff --git a/src/lint/linter/__tests__/csslint/duplicate-properties.lint-test b/src/lint/linter/__tests__/csslint/duplicate-properties.lint-test
new file mode 100644
index 00000000..5883bbcb
--- /dev/null
+++ b/src/lint/linter/__tests__/csslint/duplicate-properties.lint-test
@@ -0,0 +1,10 @@
+.rule {
+ font-weight: bold;
+ font-weight: bold;
+ font-weight: bold;
+ font-weight: bold;
+}
+~~~~~~~~~~
+warning:3:3
+warning:4:3
+warning:5:3
diff --git a/src/lint/linter/__tests__/csslint/empty-rule.lint-test b/src/lint/linter/__tests__/csslint/empty-rule.lint-test
new file mode 100644
index 00000000..9a055388
--- /dev/null
+++ b/src/lint/linter/__tests__/csslint/empty-rule.lint-test
@@ -0,0 +1,3 @@
+.rule { }
+~~~~~~~~~~
+warning:1:1
diff --git a/src/lint/linter/__tests__/pep8/imports.lint-test b/src/lint/linter/__tests__/pep8/imports.lint-test
new file mode 100644
index 00000000..54ef8efd
--- /dev/null
+++ b/src/lint/linter/__tests__/pep8/imports.lint-test
@@ -0,0 +1,3 @@
+import os, sys
+~~~~~~~~~~
+warning:1:10

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 04:28 (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
547839
Default Alt Text
(59 KB)

Event Timeline