Page MenuHomePhorge

No OneTemporary

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 1808e2e9..eb440b3a 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,314 +1,316 @@
<?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',
+ 'ArcanistPHPCSLinterTestCase' => 'lint/linter/__tests__/ArcanistPHPCSLinterTestCase.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' => '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' => 'ArcanistExternalLinter',
'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' => 'ArcanistExternalLinter',
'ArcanistPEP8LinterTestCase' => 'ArcanistArcanistLinterTestCase',
+ 'ArcanistPHPCSLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistPasteWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow',
- 'ArcanistPhpcsLinter' => 'ArcanistLinter',
+ 'ArcanistPhpcsLinter' => 'ArcanistExternalLinter',
'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
'ArcanistPhutilTestCaseTestCase' => 'ArcanistPhutilTestCase',
'ArcanistPhutilTestSkippedException' => 'Exception',
'ArcanistPhutilTestTerminatedException' => 'Exception',
'ArcanistPhutilXHPASTLinter' => 'ArcanistBaseXHPASTLinter',
'ArcanistPhutilXHPASTLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistPyFlakesLinter' => 'ArcanistLinter',
'ArcanistPyLintLinter' => 'ArcanistLinter',
'ArcanistRepositoryAPIMiscTestCase' => 'ArcanistTestCase',
'ArcanistRepositoryAPIStateTestCase' => 'ArcanistTestCase',
'ArcanistRevertWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistRubyLinter' => 'ArcanistExternalLinter',
'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/ArcanistLintEngine.php b/src/lint/engine/ArcanistLintEngine.php
index 17ed0f1c..81fddbf6 100644
--- a/src/lint/engine/ArcanistLintEngine.php
+++ b/src/lint/engine/ArcanistLintEngine.php
@@ -1,514 +1,540 @@
<?php
/**
* Manages lint execution. When you run 'arc lint' or 'arc diff', Arcanist
* checks your .arcconfig to see if you have specified a lint engine in the
* key "lint.engine". The engine must extend this class. For example:
*
* lang=js
* {
* // ...
* "lint.engine" : "ExampleLintEngine",
* // ...
* }
*
* The lint engine is given a list of paths (generally, the paths that you
* modified in your change) and determines which linters to run on them. The
* linters themselves are responsible for actually analyzing file text and
* finding warnings and errors. For example, if the modified paths include some
* JS files and some Python files, you might want to run JSLint on the JS files
* and PyLint on the Python files.
*
* You can also run multiple linters on a single file. For instance, you might
* run one linter on all text files to make sure they don't have trailing
* whitespace, or enforce tab vs space rules, or make sure there are enough
* curse words in them.
*
* Because lint engines are pretty custom to the rules of a project, you will
* generally need to build your own. Fortunately, it's pretty easy (and you
* can use the prebuilt //linters//, you just need to write a little glue code
* to tell Arcanist which linters to run). For a simple example of how to build
* a lint engine, see @{class:ExampleLintEngine}.
*
* You can test an engine like this:
*
* arc lint --engine ExampleLintEngine --lintall some_file.py
*
* ...which will show you all the lint issues raised in the file.
*
* See @{article@phabricator:Arcanist User Guide: Customizing Lint, Unit Tests
* and Workflows} for more information about configuring lint engines.
*
* @group lint
* @stable
*/
abstract class ArcanistLintEngine {
protected $workingCopy;
protected $paths = array();
protected $fileData = array();
protected $charToLine = array();
protected $lineToFirstChar = array();
private $cachedResults;
private $cacheVersion;
private $repositoryVersion;
private $results = array();
private $stopped = array();
private $minimumSeverity = ArcanistLintSeverity::SEVERITY_DISABLED;
private $changedLines = array();
private $commitHookMode = false;
private $hookAPI;
private $enableAsyncLint = false;
private $postponedLinters = array();
public function __construct() {
}
public function setWorkingCopy(ArcanistWorkingCopyIdentity $working_copy) {
$this->workingCopy = $working_copy;
return $this;
}
public function getWorkingCopy() {
return $this->workingCopy;
}
public function setPaths($paths) {
$this->paths = $paths;
return $this;
}
public function getPaths() {
return $this->paths;
}
public function setPathChangedLines($path, $changed) {
if ($changed === null) {
$this->changedLines[$path] = null;
} else {
$this->changedLines[$path] = array_fill_keys($changed, true);
}
return $this;
}
public function getPathChangedLines($path) {
return idx($this->changedLines, $path);
}
public function setFileData($data) {
$this->fileData = $data + $this->fileData;
return $this;
}
public function setCommitHookMode($mode) {
$this->commitHookMode = $mode;
return $this;
}
public function setHookAPI(ArcanistHookAPI $hook_api) {
$this->hookAPI = $hook_api;
return $this;
}
public function getHookAPI() {
return $this->hookAPI;
}
public function setEnableAsyncLint($enable_async_lint) {
$this->enableAsyncLint = $enable_async_lint;
return $this;
}
public function getEnableAsyncLint() {
return $this->enableAsyncLint;
}
public function loadData($path) {
if (!isset($this->fileData[$path])) {
if ($this->getCommitHookMode()) {
$this->fileData[$path] = $this->getHookAPI()
->getCurrentFileData($path);
} else {
$disk_path = $this->getFilePathOnDisk($path);
$this->fileData[$path] = Filesystem::readFile($disk_path);
}
}
return $this->fileData[$path];
}
public function pathExists($path) {
if ($this->getCommitHookMode()) {
$file_data = $this->loadData($path);
return ($file_data !== null);
} else {
$disk_path = $this->getFilePathOnDisk($path);
return Filesystem::pathExists($disk_path);
}
}
+ public function isDirectory($path) {
+ if ($this->getCommitHookMode()) {
+ // TODO: This won't get the right result in every case (we need more
+ // metadata) but should almost always be correct.
+ try {
+ $this->loadData($path);
+ return false;
+ } catch (Exception $ex) {
+ return true;
+ }
+ } else {
+ $disk_path = $this->getFilePathOnDisk($path);
+ return is_dir($disk_path);
+ }
+ }
+
+ public function isBinaryFile($path) {
+ try {
+ $data = $this->loadData($path);
+ } catch (Exception $ex) {
+ return false;
+ }
+
+ return ArcanistDiffUtils::isHeuristicBinaryFile($data);
+ }
+
public function getFilePathOnDisk($path) {
return Filesystem::resolvePath(
$path,
$this->getWorkingCopy()->getProjectRoot());
}
public function setMinimumSeverity($severity) {
$this->minimumSeverity = $severity;
return $this;
}
public function getCommitHookMode() {
return $this->commitHookMode;
}
public function run() {
$linters = $this->buildLinters();
if (!$linters) {
throw new ArcanistNoEffectException("No linters to run.");
}
$linters = msort($linters, 'getLinterPriority');
foreach ($linters as $linter) {
$linter->setEngine($this);
}
$have_paths = false;
foreach ($linters as $linter) {
if ($linter->getPaths()) {
$have_paths = true;
break;
}
}
if (!$have_paths) {
throw new ArcanistNoEffectException("No paths are lintable.");
}
$versions = array($this->getCacheVersion());
foreach ($linters as $linter) {
$version = get_class($linter).':'.$linter->getCacheVersion();
$symbols = id(new PhutilSymbolLoader())
->setType('class')
->setName(get_class($linter))
->selectSymbolsWithoutLoading();
$symbol = idx($symbols, 'class$'.get_class($linter));
if ($symbol) {
$version .= ':'.md5_file(
phutil_get_library_root($symbol['library']).'/'.$symbol['where']);
}
$versions[] = $version;
}
$this->cacheVersion = crc32(implode("\n", $versions));
$this->stopped = array();
$exceptions = array();
foreach ($linters as $linter_name => $linter) {
if (!is_string($linter_name)) {
$linter_name = get_class($linter);
}
try {
if (!$linter->canRun()) {
continue;
}
$paths = $linter->getPaths();
foreach ($paths as $key => $path) {
// Make sure each path has a result generated, even if it is empty
// (i.e., the file has no lint messages).
$result = $this->getResultForPath($path);
if (isset($this->stopped[$path])) {
unset($paths[$key]);
}
if (isset($this->cachedResults[$path][$this->cacheVersion])) {
$cached_result = $this->cachedResults[$path][$this->cacheVersion];
$use_cache = $this->shouldUseCache(
$linter->getCacheGranularity(),
idx($cached_result, 'repository_version'));
if ($use_cache) {
unset($paths[$key]);
if (idx($cached_result, 'stopped') == $linter_name) {
$this->stopped[$path] = $linter_name;
}
}
}
}
$paths = array_values($paths);
if ($paths) {
$profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall(array(
'type' => 'lint',
'linter' => $linter_name,
'paths' => $paths,
));
try {
$linter->willLintPaths($paths);
foreach ($paths as $path) {
$linter->willLintPath($path);
$linter->lintPath($path);
if ($linter->didStopAllLinters()) {
$this->stopped[$path] = $linter_name;
}
}
} catch (Exception $ex) {
$profiler->endServiceCall($call_id, array());
throw $ex;
}
$profiler->endServiceCall($call_id, array());
}
} catch (Exception $ex) {
$exceptions[$linter_name] = $ex;
}
}
$exceptions += $this->didRunLinters($linters);
foreach ($linters as $linter) {
foreach ($linter->getLintMessages() as $message) {
if (!$this->isSeverityEnabled($message->getSeverity())) {
continue;
}
if (!$this->isRelevantMessage($message)) {
continue;
}
$message->setGranularity($linter->getCacheGranularity());
$result = $this->getResultForPath($message->getPath());
$result->addMessage($message);
}
}
if ($this->cachedResults) {
foreach ($this->cachedResults as $path => $messages) {
$messages = idx($messages, $this->cacheVersion, array());
$repository_version = idx($messages, 'repository_version');
unset($messages['stopped']);
unset($messages['repository_version']);
foreach ($messages as $message) {
$use_cache = $this->shouldUseCache(
idx($message, 'granularity'),
$repository_version);
if ($use_cache) {
$this->getResultForPath($path)->addMessage(
ArcanistLintMessage::newFromDictionary($message));
}
}
}
}
foreach ($this->results as $path => $result) {
$disk_path = $this->getFilePathOnDisk($path);
$result->setFilePathOnDisk($disk_path);
if (isset($this->fileData[$path])) {
$result->setData($this->fileData[$path]);
} else if ($disk_path && Filesystem::pathExists($disk_path)) {
// TODO: this may cause us to, e.g., load a large binary when we only
// raised an error about its filename. We could refine this by looking
// through the lint messages and doing this load only if any of them
// have original/replacement text or something like that.
try {
$this->fileData[$path] = Filesystem::readFile($disk_path);
$result->setData($this->fileData[$path]);
} catch (FilesystemException $ex) {
// Ignore this, it's noncritical that we access this data and it
// might be unreadable or a directory or whatever else for plenty
// of legitimate reasons.
}
}
}
if ($exceptions) {
throw new PhutilAggregateException('Some linters failed:', $exceptions);
}
return $this->results;
}
public function isSeverityEnabled($severity) {
$minimum = $this->minimumSeverity;
return ArcanistLintSeverity::isAtLeastAsSevere($severity, $minimum);
}
private function shouldUseCache($cache_granularity, $repository_version) {
if ($this->commitHookMode) {
return false;
}
switch ($cache_granularity) {
case ArcanistLinter::GRANULARITY_FILE:
return true;
case ArcanistLinter::GRANULARITY_DIRECTORY:
case ArcanistLinter::GRANULARITY_REPOSITORY:
return ($this->repositoryVersion == $repository_version);
default:
return false;
}
}
/**
* @param dict<string path, dict<string version, list<dict message>>>
* @return this
*/
public function setCachedResults(array $results) {
$this->cachedResults = $results;
return $this;
}
public function getResults() {
return $this->results;
}
public function getStoppedPaths() {
return $this->stopped;
}
abstract protected function buildLinters();
protected function didRunLinters(array $linters) {
assert_instances_of($linters, 'ArcanistLinter');
$exceptions = array();
$profiler = PhutilServiceProfiler::getInstance();
foreach ($linters as $linter_name => $linter) {
if (!is_string($linter_name)) {
$linter_name = get_class($linter);
}
$call_id = $profiler->beginServiceCall(array(
'type' => 'lint',
'linter' => $linter_name,
));
try {
$linter->didRunLinters();
} catch (Exception $ex) {
$exceptions[$linter_name] = $ex;
}
$profiler->endServiceCall($call_id, array());
}
return $exceptions;
}
public function setRepositoryVersion($version) {
$this->repositoryVersion = $version;
return $this;
}
private function isRelevantMessage(ArcanistLintMessage $message) {
// When a user runs "arc lint", we default to raising only warnings on
// lines they have changed (errors are still raised anywhere in the
// file). The list of $changed lines may be null, to indicate that the
// path is a directory or a binary file so we should not exclude
// warnings.
if (!$this->changedLines ||
$message->isError() ||
$message->shouldBypassChangedLineFiltering()) {
return true;
}
$locations = $message->getOtherLocations();
$locations[] = $message->toDictionary();
foreach ($locations as $location) {
$path = idx($location, 'path', $message->getPath());
if (!array_key_exists($path, $this->changedLines)) {
continue;
}
$changed = $this->getPathChangedLines($path);
if ($changed === null || !$location['line']) {
return true;
}
$last_line = $location['line'];
if (isset($location['original'])) {
$last_line += substr_count($location['original'], "\n");
}
for ($l = $location['line']; $l <= $last_line; $l++) {
if (!empty($changed[$l])) {
return true;
}
}
}
return false;
}
protected function getResultForPath($path) {
if (empty($this->results[$path])) {
$result = new ArcanistLintResult();
$result->setPath($path);
$result->setCacheVersion($this->cacheVersion);
$this->results[$path] = $result;
}
return $this->results[$path];
}
public function getLineAndCharFromOffset($path, $offset) {
if (!isset($this->charToLine[$path])) {
$char_to_line = array();
$line_to_first_char = array();
$lines = explode("\n", $this->loadData($path));
$line_number = 0;
$line_start = 0;
foreach ($lines as $line) {
$len = strlen($line) + 1; // Account for "\n".
$line_to_first_char[] = $line_start;
$line_start += $len;
for ($ii = 0; $ii < $len; $ii++) {
$char_to_line[] = $line_number;
}
$line_number++;
}
$this->charToLine[$path] = $char_to_line;
$this->lineToFirstChar[$path] = $line_to_first_char;
}
$line = $this->charToLine[$path][$offset];
$char = $offset - $this->lineToFirstChar[$path][$line];
return array($line, $char);
}
public function getPostponedLinters() {
return $this->postponedLinters;
}
public function setPostponedLinters(array $linters) {
$this->postponedLinters = $linters;
return $this;
}
protected function getCacheVersion() {
return 1;
}
protected function getPEP8WithTextOptions() {
// E101 is subset of TXT2 (Tab Literal).
// E501 is same as TXT3 (Line Too Long).
// W291 is same as TXT6 (Trailing Whitespace).
// W292 is same as TXT4 (File Does Not End in Newline).
// W293 is same as TXT6 (Trailing Whitespace).
return '--ignore=E101,E501,W291,W292,W293';
}
}
diff --git a/src/lint/linter/ArcanistFilenameLinter.php b/src/lint/linter/ArcanistFilenameLinter.php
index 9aeffdf5..63de545c 100644
--- a/src/lint/linter/ArcanistFilenameLinter.php
+++ b/src/lint/linter/ArcanistFilenameLinter.php
@@ -1,40 +1,44 @@
<?php
/**
* Stifles creativity in choosing imaginative file names.
*
* @group linter
*/
final class ArcanistFilenameLinter extends ArcanistLinter {
const LINT_BAD_FILENAME = 1;
public function getLinterName() {
return 'NAME';
}
public function getLinterConfigurationName() {
return 'filename';
}
public function shouldLintBinaryFiles() {
return true;
}
public function getLintNameMap() {
return array(
self::LINT_BAD_FILENAME => pht('Bad Filename'),
);
}
public function lintPath($path) {
if (!preg_match('@^[a-z0-9./\\\\_-]+$@i', $path)) {
$this->raiseLintAtPath(
self::LINT_BAD_FILENAME,
pht(
'Name files using only letters, numbers, period, hyphen and '.
'underscore.'));
}
}
+ public function shouldLintDirectories() {
+ return true;
+ }
+
}
diff --git a/src/lint/linter/ArcanistLinter.php b/src/lint/linter/ArcanistLinter.php
index 7cbd309c..4d5d6fce 100644
--- a/src/lint/linter/ArcanistLinter.php
+++ b/src/lint/linter/ArcanistLinter.php
@@ -1,337 +1,388 @@
<?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 $customSeverityRules = array();
private $config = array();
public function getLinterPriority() {
return 1.0;
}
public function setCustomSeverityMap(array $map) {
$this->customSeverityMap = $map;
return $this;
}
+ public function setCustomSeverityRules(array $rules) {
+ $this->customSeverityRules = $rules;
+ 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;
}
/**
* Filter out paths which this linter doesn't act on (for example, because
* they are binaries and the linter doesn't apply to binaries).
*/
private function filterPaths($paths) {
$engine = $this->getEngine();
$keep = array();
foreach ($paths as $path) {
if (!$this->shouldLintDeletedFiles() && !$engine->pathExists($path)) {
continue;
}
- if (!$this->shouldLintBinaryFiles() && $this->isBinaryFile($path)) {
+
+ if (!$this->shouldLintDirectories() && $engine->isDirectory($path)) {
+ continue;
+ }
+
+ if (!$this->shouldLintBinaryFiles() && $engine->isBinaryFile($path)) {
continue;
}
+
$keep[] = $path;
}
return $keep;
}
public function getPaths() {
return $this->filterPaths(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];
}
+ foreach ($this->customSeverityRules as $rule => $severity) {
+ if (preg_match($rule, $code)) {
+ return $severity;
+ }
+ }
+
return $this->getDefaultMessageSeverity($code);
}
protected function getDefaultMessageSeverity($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;
}
public function willLintPaths(array $paths) {
return;
}
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(
'severity' => 'optional map<string, string>',
+ 'severity.rules' => 'optional map<string, string>',
);
}
public function setLinterConfigurationValue($key, $value) {
+ $sev_map = array(
+ 'error' => ArcanistLintSeverity::SEVERITY_ERROR,
+ 'warning' => ArcanistLintSeverity::SEVERITY_WARNING,
+ 'autofix' => ArcanistLintSeverity::SEVERITY_AUTOFIX,
+ 'advice' => ArcanistLintSeverity::SEVERITY_ADVICE,
+ 'disabled' => ArcanistLintSeverity::SEVERITY_DISABLED,
+ );
+
switch ($key) {
case 'severity':
- $sev_map = array(
- 'error' => ArcanistLintSeverity::SEVERITY_ERROR,
- 'warning' => ArcanistLintSeverity::SEVERITY_WARNING,
- 'autofix' => ArcanistLintSeverity::SEVERITY_AUTOFIX,
- 'advice' => ArcanistLintSeverity::SEVERITY_ADVICE,
- 'disabled' => ArcanistLintSeverity::SEVERITY_DISABLED,
- );
-
$custom = array();
foreach ($value as $code => $severity) {
if (empty($sev_map[$severity])) {
$valid = implode(', ', array_keys($sev_map));
throw new Exception(
pht(
'Unknown lint severity "%s". Valid severities are: %s.',
$severity,
$valid));
}
$code = $this->getLintCodeFromLinterConfigurationKey($code);
$custom[$code] = $severity;
}
$this->setCustomSeverityMap($custom);
return;
+ case 'severity.rules':
+ foreach ($value as $rule => $severity) {
+ if (@preg_match($rule, '') === false) {
+ throw new Exception(
+ pht(
+ 'Severity rule "%s" is not a valid regular expression.',
+ $rule));
+ }
+ if (empty($sev_map[$severity])) {
+ $valid = implode(', ', array_keys($sev_map));
+ throw new Exception(
+ pht(
+ 'Unknown lint severity "%s". Valid severities are: %s.',
+ $severity,
+ $valid));
+ }
+ }
+ $this->setCustomSeverityRules($value);
+ return;
}
throw new Exception("Incomplete implementation: {$key}!");
}
protected function shouldLintBinaryFiles() {
return false;
}
protected function shouldLintDeletedFiles() {
return false;
}
+ protected function shouldLintDirectories() {
+ return false;
+ }
+
+ /**
+ * Map a configuration lint code to an `arc` lint code. Primarily, this is
+ * intended for validation, but can also be used to normalize case or
+ * otherwise be more permissive in accepted inputs.
+ *
+ * If the code is not recognized, you should throw an exception.
+ *
+ * @param string Code specified in configuration.
+ * @return string Normalized code to use in severity map.
+ */
+ protected function getLintCodeFromLinterConfigurationKey($code) {
+ return $code;
+ }
+
}
diff --git a/src/lint/linter/ArcanistMergeConflictLinter.php b/src/lint/linter/ArcanistMergeConflictLinter.php
index 9cbbe70e..93c4d61b 100644
--- a/src/lint/linter/ArcanistMergeConflictLinter.php
+++ b/src/lint/linter/ArcanistMergeConflictLinter.php
@@ -1,50 +1,46 @@
<?php
/**
* Checks files for unresolved merge conflicts.
*
* @group linter
*/
final class ArcanistMergeConflictLinter extends ArcanistLinter {
const LINT_MERGECONFLICT = 1;
public function willLintPaths(array $paths) {
return;
}
public function lintPath($path) {
- if ($this->isBinaryFile($path)) {
- return;
- }
-
$lines = phutil_split_lines($this->getData($path), false);
foreach ($lines as $lineno => $line) {
// An unresolved merge conflict will contain a series of seven
// '<', '=', or '>'.
if (preg_match('/^(>{7}|<{7}|={7})$/', $line)) {
$this->raiseLintAtLine(
$lineno + 1,
0,
self::LINT_MERGECONFLICT,
"This syntax indicates there is an unresolved merge conflict.");
}
}
}
public function getLinterName() {
return "MERGECONFLICT";
}
public function getLintSeverityMap() {
return array(
self::LINT_MERGECONFLICT => ArcanistLintSeverity::SEVERITY_ERROR
);
}
public function getLintNameMap() {
return array(
self::LINT_MERGECONFLICT => "Unresolved merge conflict"
);
}
}
diff --git a/src/lint/linter/ArcanistPhpcsLinter.php b/src/lint/linter/ArcanistPhpcsLinter.php
index 0213b565..ace53f07 100644
--- a/src/lint/linter/ArcanistPhpcsLinter.php
+++ b/src/lint/linter/ArcanistPhpcsLinter.php
@@ -1,122 +1,118 @@
<?php
/**
* Uses "PHP_CodeSniffer" to detect checkstyle errors in php code.
* To use this linter, you must install PHP_CodeSniffer.
* http://pear.php.net/package/PHP_CodeSniffer.
*
* Optional configurations in .arcconfig:
*
* lint.phpcs.standard
* lint.phpcs.options
* lint.phpcs.bin
*
* @group linter
*/
-final class ArcanistPhpcsLinter extends ArcanistLinter {
+final class ArcanistPhpcsLinter extends ArcanistExternalLinter {
private $reports;
public function getLinterName() {
return 'PHPCS';
}
- public function getLintSeverityMap() {
- return array();
+ public function getLinterConfigurationName() {
+ return 'phpcs';
}
- public function getLintNameMap() {
- return array();
+ public function getMandatoryFlags() {
+ return '--report=xml';
}
- public function getPhpcsOptions() {
+ public function getInstallInstructions() {
+ return pht('Install PHPCS with `pear install PHP_CodeSniffer`.');
+ }
+
+ public function getDefaultFlags() {
+ // TODO: Deprecation warnings.
+
$working_copy = $this->getEngine()->getWorkingCopy();
$options = $working_copy->getConfig('lint.phpcs.options');
$standard = $working_copy->getConfig('lint.phpcs.standard');
$options .= !empty($standard) ? ' --standard=' . $standard : '';
return $options;
}
- private function getPhpcsPath() {
+ public function getDefaultBinary() {
+ // TODO: Deprecation warnings.
$working_copy = $this->getEngine()->getWorkingCopy();
$bin = $working_copy->getConfig('lint.phpcs.bin');
-
- if ($bin === null) {
- $bin = 'phpcs';
+ if ($bin) {
+ return $bin;
}
- return $bin;
+ return 'phpcs';
}
- public function willLintPaths(array $paths) {
- $phpcs_bin = $this->getPhpcsPath();
- $phpcs_options = $this->getPhpcsOptions();
- $futures = array();
-
- foreach ($paths as $path) {
- $filepath = $this->getEngine()->getFilePathOnDisk($path);
- $this->reports[$path] = new TempFile();
- $futures[$path] = new ExecFuture('%C %C --report=xml --report-file=%s %s',
- $phpcs_bin,
- $phpcs_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 shouldExpectCommandErrors() {
+ return true;
}
- public function lintPath($path) {
- list($rc, $stdout) = $this->results[$path];
-
- $report = Filesystem::readFile($this->reports[$path]);
-
- if ($report) {
- $report_dom = new DOMDocument();
- libxml_clear_errors();
- $report_dom->loadXML($report);
- }
- if (!$report || libxml_get_errors()) {
- throw new ArcanistUsageException('PHPCS Linter failed to load ' .
- 'reporting file. Something happened when running phpcs. ' .
- "Output:\n$stdout" .
- "\nTry running lint with --trace flag to get more details.");
+ protected function parseLinterOutput($path, $err, $stdout, $stderr) {
+ $report_dom = new DOMDocument();
+ $ok = @$report_dom->loadXML($stdout);
+ 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);
- $line = $lines[$child->getAttribute('line') - 1];
- $text = substr($line, $child->getAttribute('column') - 1);
- $name = $this->getLinterName() . ' - ' . $child->getAttribute('source');
- $severity = $child->tagName == 'error' ?
- ArcanistLintSeverity::SEVERITY_ERROR
- : ArcanistLintSeverity::SEVERITY_WARNING;
+ if ($child->tagName == 'error') {
+ $prefix = 'E';
+ } else {
+ $prefix = 'W';
+ }
+
+ $code = 'PHPCS.'.$prefix.'.'.$child->getAttribute('source');
$message = new ArcanistLintMessage();
$message->setPath($path);
$message->setLine($child->getAttribute('line'));
$message->setChar($child->getAttribute('column'));
- $message->setCode($child->getAttribute('severity'));
- $message->setName($name);
+ $message->setCode($code);
$message->setDescription($child->nodeValue);
- $message->setSeverity($severity);
- $message->setOriginalText($text);
- $this->addLintMessage($message);
+ $message->setSeverity($this->getLintMessageSeverity($code));
+
+ $messages[] = $message;
}
}
+
+ return $messages;
}
+
+ protected function getDefaultMessageSeverity($code) {
+ if (preg_match('/^PHPCS\\.W\\./', $code)) {
+ return ArcanistLintSeverity::SEVERITY_WARNING;
+ } else {
+ return ArcanistLintSeverity::SEVERITY_ERROR;
+ }
+ }
+
+ protected function getLintCodeFromLinterConfigurationKey($code) {
+ if (!preg_match('/^PHPCS\\.(E|W)\\./', $code)) {
+ throw new Exception(
+ "Invalid severity code '{$code}', should begin with 'PHPCS.'.");
+ }
+ return $code;
+ }
+
}
diff --git a/src/lint/linter/__tests__/ArcanistPHPCSLinterTestCase.php b/src/lint/linter/__tests__/ArcanistPHPCSLinterTestCase.php
new file mode 100644
index 00000000..85afaf9b
--- /dev/null
+++ b/src/lint/linter/__tests__/ArcanistPHPCSLinterTestCase.php
@@ -0,0 +1,11 @@
+<?php
+
+final class ArcanistPHPCSLinterTestCase extends ArcanistArcanistLinterTestCase {
+
+ public function testPHPCSLint() {
+ return $this->executeTestsInDirectory(
+ dirname(__FILE__).'/phpcs/',
+ new ArcanistPhpcsLinter());
+ }
+
+}
diff --git a/src/lint/linter/__tests__/phpcs/basics.lint-test b/src/lint/linter/__tests__/phpcs/basics.lint-test
new file mode 100644
index 00000000..a69827a0
--- /dev/null
+++ b/src/lint/linter/__tests__/phpcs/basics.lint-test
@@ -0,0 +1,13 @@
+<?php
+
+function f() {
+ $$g;
+
+ $h++;
+}
+~~~~~~~~~~
+error:2:1
+error:3:1
+error:3:14
+error:4:3
+error:6:3
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 20:03 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1128266
Default Alt Text
(56 KB)

Event Timeline