Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F995551
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
59 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Dec 23, 07:43 (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
556861
Default Alt Text
(59 KB)
Attached To
Mode
R118 Arcanist - fork
Attached
Detach File
Event Timeline
Log In to Comment