Page MenuHomeSealhub

No OneTemporary

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 627d50f7..1808e2e9 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,314 +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' => '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',
'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',
+ '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 3cb5dbc5..17ed0f1c 100644
--- a/src/lint/engine/ArcanistLintEngine.php
+++ b/src/lint/engine/ArcanistLintEngine.php
@@ -1,510 +1,514 @@
<?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 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) {
- $linter->setEngine($this);
$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/engine/ComprehensiveLintEngine.php b/src/lint/engine/ComprehensiveLintEngine.php
index e8a104d2..06555014 100644
--- a/src/lint/engine/ComprehensiveLintEngine.php
+++ b/src/lint/engine/ComprehensiveLintEngine.php
@@ -1,54 +1,51 @@
<?php
/**
* Basic lint engine which just applies several linters based on the file types
*
* @group linter
*/
final class ComprehensiveLintEngine extends ArcanistLintEngine {
public function buildLinters() {
$linters = array();
$paths = $this->getPaths();
foreach ($paths as $key => $path) {
- if (!$this->pathExists($path)) {
- unset($paths[$key]);
- }
if (preg_match('@^externals/@', $path)) {
// Third-party stuff lives in /externals/; don't run lint engines
// against it.
unset($paths[$key]);
}
}
$text_paths = preg_grep('/\.(php|css|hpp|cpp|l|y|py|pl)$/', $paths);
$linters[] = id(new ArcanistGeneratedLinter())->setPaths($text_paths);
$linters[] = id(new ArcanistNoLintLinter())->setPaths($text_paths);
$linters[] = id(new ArcanistTextLinter())->setPaths($text_paths);
$linters[] = id(new ArcanistFilenameLinter())->setPaths($paths);
$linters[] = id(new ArcanistXHPASTLinter())
->setPaths(preg_grep('/\.php$/', $paths));
$py_paths = preg_grep('/\.py$/', $paths);
$linters[] = id(new ArcanistPyFlakesLinter())->setPaths($py_paths);
$linters[] = id(new ArcanistPEP8Linter())
->setConfig(array('options' => $this->getPEP8WithTextOptions()))
->setPaths($py_paths);
$linters[] = id(new ArcanistRubyLinter())
->setPaths(preg_grep('/\.rb$/', $paths));
$linters[] = id(new ArcanistScalaSBTLinter())
->setPaths(preg_grep('/\.scala$/', $paths));
$linters[] = id(new ArcanistJSHintLinter())
->setPaths(preg_grep('/\.js$/', $paths));
return $linters;
}
}
diff --git a/src/lint/engine/PhutilLintEngine.php b/src/lint/engine/PhutilLintEngine.php
index 6328f68f..968f9b20 100644
--- a/src/lint/engine/PhutilLintEngine.php
+++ b/src/lint/engine/PhutilLintEngine.php
@@ -1,84 +1,89 @@
<?php
/**
* Lint engine which enforces libphutil rules.
*
* TODO: Deal with PhabricatorLintEngine extending this and then finalize it.
*
* @group linter
*/
class PhutilLintEngine extends ArcanistLintEngine {
public function buildLinters() {
$linters = array();
$paths = $this->getPaths();
$linters[] = id(new ArcanistPhutilLibraryLinter())->setPaths($paths);
// Remaining linters operate on file contents and ignore removed files.
foreach ($paths as $key => $path) {
if (!$this->pathExists($path)) {
unset($paths[$key]);
}
if (preg_match('@^externals/@', $path)) {
// Third-party stuff lives in /externals/; don't run lint engines
// against it.
unset($paths[$key]);
}
+ if (preg_match('(\\.lint-test$)', $path)) {
+ // Don't try to lint these, since they're tests for linters and
+ // often have intentional lint errors.
+ unset($paths[$key]);
+ }
}
$linters[] = id(new ArcanistFilenameLinter())->setPaths($paths);
// Skip directories and lint only regular files in remaining linters.
foreach ($paths as $key => $path) {
if ($this->getCommitHookMode()) {
continue;
}
if (!is_file($this->getFilePathOnDisk($path))) {
unset($paths[$key]);
}
}
$linters[] = id(new ArcanistGeneratedLinter())->setPaths($paths);
$linters[] = id(new ArcanistNoLintLinter())->setPaths($paths);
$linters[] = id(new ArcanistTextLinter())->setPaths($paths);
$linters[] = id(new ArcanistSpellingLinter())->setPaths($paths);
$php_paths = preg_grep('/\.php$/', $paths);
$xhpast_linter = id(new ArcanistXHPASTLinter())
->setCustomSeverityMap($this->getXHPASTSeverityMap())
->setPaths($php_paths);
$linters[] = $xhpast_linter;
$linters[] = id(new ArcanistPhutilXHPASTLinter())
->setXHPASTLinter($xhpast_linter)
->setPaths($php_paths);
$merge_conflict_linter = id(new ArcanistMergeConflictLinter());
foreach ($paths as $path) {
$merge_conflict_linter->addPath($path);
$merge_conflict_linter->addData($path, $this->loadData($path));
}
$linters[] = $merge_conflict_linter;
return $linters;
}
private function getXHPASTSeverityMap() {
$error = ArcanistLintSeverity::SEVERITY_ERROR;
$warning = ArcanistLintSeverity::SEVERITY_WARNING;
$advice = ArcanistLintSeverity::SEVERITY_ADVICE;
return array(
ArcanistXHPASTLinter::LINT_PHP_53_FEATURES => $error,
ArcanistXHPASTLinter::LINT_PHP_54_FEATURES => $error,
ArcanistXHPASTLinter::LINT_COMMENT_SPACING => $error,
ArcanistXHPASTLinter::LINT_RAGGED_CLASSTREE_EDGE => $warning,
ArcanistXHPASTLinter::LINT_TODO_COMMENT => $advice,
);
}
}
diff --git a/src/lint/linter/ArcanistExternalLinter.php b/src/lint/linter/ArcanistExternalLinter.php
index 88cda3ed..b7031b66 100644
--- a/src/lint/linter/ArcanistExternalLinter.php
+++ b/src/lint/linter/ArcanistExternalLinter.php
@@ -1,518 +1,492 @@
<?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->getEngine()->loadData($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) {
if ($err) {
$future->resolvex();
} else {
throw new Exception(
"Linter failed to parse output!\n\n{$stdout}\n\n{$stderr}");
}
}
foreach ($messages as $message) {
$this->addLintMessage($message);
}
}
public function getLinterConfigurationOptions() {
$options = array(
'bin' => 'optional string | list<string>',
'flags' => 'optional string',
- 'severity' => 'optional map<string, string>',
);
if ($this->shouldUseInterpreter()) {
$options['interpreter'] = 'optional string | list<string>';
}
- return $options;
+ return $options + parent::getLinterConfigurationOptions();
}
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);
}
return;
- 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;
}
return parent::setLinterConfigurationValue($key, $value);
}
/**
* 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/ArcanistFilenameLinter.php b/src/lint/linter/ArcanistFilenameLinter.php
index 48fedf38..9aeffdf5 100644
--- a/src/lint/linter/ArcanistFilenameLinter.php
+++ b/src/lint/linter/ArcanistFilenameLinter.php
@@ -1,43 +1,40 @@
<?php
/**
* Stifles creativity in choosing imaginative file names.
*
* @group linter
*/
final class ArcanistFilenameLinter extends ArcanistLinter {
const LINT_BAD_FILENAME = 1;
- public function willLintPaths(array $paths) {
- return;
- }
-
public function getLinterName() {
return 'NAME';
}
- public function getLintSeverityMap() {
- return array();
- }
-
public function getLinterConfigurationName() {
return 'filename';
}
+ public function shouldLintBinaryFiles() {
+ return true;
+ }
+
public function getLintNameMap() {
return array(
- self::LINT_BAD_FILENAME => 'Bad Filename',
+ 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,
- 'Name files using only letters, numbers, period, hyphen and '.
- 'underscore.');
+ pht(
+ 'Name files using only letters, numbers, period, hyphen and '.
+ 'underscore.'));
}
}
}
diff --git a/src/lint/linter/ArcanistGeneratedLinter.php b/src/lint/linter/ArcanistGeneratedLinter.php
index b6d49ad6..6efdfb15 100644
--- a/src/lint/linter/ArcanistGeneratedLinter.php
+++ b/src/lint/linter/ArcanistGeneratedLinter.php
@@ -1,39 +1,29 @@
<?php
/**
* Stops other linters from running on generated code.
*
* @group linter
*/
final class ArcanistGeneratedLinter extends ArcanistLinter {
- public function willLintPaths(array $paths) {
- return;
- }
-
public function getLinterName() {
return 'GEN';
}
- public function getLintSeverityMap() {
- return array();
+ public function getLinterPriority() {
+ return 0.25;
}
- public function getLintNameMap() {
- return array(
- );
+ public function getLinterConfigurationName() {
+ return 'generated';
}
public function lintPath($path) {
- if ($this->isBinaryFile($path)) {
- return;
- }
-
$data = $this->getData($path);
-
if (preg_match('/@'.'generated/', $data)) {
$this->stopAllLinters();
}
}
}
diff --git a/src/lint/linter/ArcanistLinter.php b/src/lint/linter/ArcanistLinter.php
index 69636d9b..7cbd309c 100644
--- a/src/lint/linter/ArcanistLinter.php
+++ b/src/lint/linter/ArcanistLinter.php
@@ -1,271 +1,337 @@
<?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 getLinterPriority() {
+ return 1.0;
+ }
+
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;
}
+ /**
+ * 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)) {
+ continue;
+ }
+ $keep[] = $path;
+ }
+
+ return $keep;
+ }
+
public function getPaths() {
- return array_values($this->paths);
+ 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];
}
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;
}
- abstract public function willLintPaths(array $paths);
+ 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();
+ return array(
+ 'severity' => 'optional map<string, string>',
+ );
}
public function setLinterConfigurationValue($key, $value) {
+ 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;
+ }
+
throw new Exception("Incomplete implementation: {$key}!");
}
+ protected function shouldLintBinaryFiles() {
+ return false;
+ }
+
+ protected function shouldLintDeletedFiles() {
+ return false;
+ }
+
}
diff --git a/src/lint/linter/ArcanistNoLintLinter.php b/src/lint/linter/ArcanistNoLintLinter.php
index d302d2a2..04fac3d3 100644
--- a/src/lint/linter/ArcanistNoLintLinter.php
+++ b/src/lint/linter/ArcanistNoLintLinter.php
@@ -1,38 +1,30 @@
<?php
/**
* Stops other linters from running on code marked with
* a nolint annotation.
*
* @group linter
*/
final class ArcanistNoLintLinter extends ArcanistLinter {
- public function willLintPaths(array $paths) {
- return;
- }
public function getLinterName() {
return 'NOLINT';
}
- public function getLintSeverityMap() {
- return array();
+ public function getLinterPriority() {
+ return 0.25;
}
- public function getLintNameMap() {
- return array(
- );
+ public function getLinterConfigurationName() {
+ return 'nolint';
}
public function lintPath($path) {
- if ($this->isBinaryFile($path)) {
- return;
- }
-
$data = $this->getData($path);
-
if (preg_match('/@'.'nolint/', $data)) {
$this->stopAllLinters();
}
}
+
}
diff --git a/src/lint/linter/ArcanistRubyLinter.php b/src/lint/linter/ArcanistRubyLinter.php
index e6e1e7d5..24a9ec7c 100644
--- a/src/lint/linter/ArcanistRubyLinter.php
+++ b/src/lint/linter/ArcanistRubyLinter.php
@@ -1,86 +1,86 @@
<?php
/**
- * Uses "Ruby" to detect various errors in Ruby code.
+ * Uses `ruby` to detect various errors in Ruby code.
*
* @group linter
*/
-final class ArcanistRubyLinter extends ArcanistLinter {
-
- public function willLintPaths(array $paths) {
- return;
- }
+final class ArcanistRubyLinter extends ArcanistExternalLinter {
public function getLinterName() {
- return 'Ruby';
+ return 'RUBY';
}
- public function getLintSeverityMap() {
- return array();
+ public function getLinterConfigurationName() {
+ return 'ruby';
}
- public function getLintNameMap() {
- return array();
- }
-
- private function getRubyPath() {
- $ruby_bin = "ruby";
-
- // Use the Ruby prefix specified in the config file
+ public function getDefaultBinary() {
+ // TODO: Deprecation warning.
$working_copy = $this->getEngine()->getWorkingCopy();
$prefix = $working_copy->getConfig('lint.ruby.prefix');
if ($prefix !== null) {
- $ruby_bin = $prefix . $ruby_bin;
+ $ruby_bin = $prefix.'ruby';
}
- if (!Filesystem::pathExists($ruby_bin)) {
+ return 'ruby';
+ }
- list($err) = exec_manual('which %s', $ruby_bin);
- if ($err) {
- throw new ArcanistUsageException(
- "Ruby does not appear to be installed on this system. Install it or ".
- "add 'lint.ruby.prefix' in your .arcconfig to point to ".
- "the directory where it resides.");
- }
- }
+ public function getInstallInstructions() {
+ return pht('Install `ruby` from <http://www.ruby-lang.org/>.');
+ }
+
+ public function supportsReadDataFromStdin() {
+ return true;
+ }
+
+ public function shouldExpectCommandErrors() {
+ return true;
+ }
- return $ruby_bin;
+ protected function getMandatoryFlags() {
+ // -w: turn on warnings
+ // -c: check syntax
+ return '-w -c';
}
- private function getMessageCodeSeverity($code) {
+ protected function getDefaultMessageSeverity($code) {
return ArcanistLintSeverity::SEVERITY_ERROR;
}
- public function lintPath($path) {
- $rubyp = $this->getRubyPath();
- $f = new ExecFuture("%s -wc", $rubyp);
- $f->write($this->getData($path));
- list($err, $stdout, $stderr) = $f->resolve();
- if ($err === 0 ) {
- return;
- }
+ protected function parseLinterOutput($path, $err, $stdout, $stderr) {
+ $lines = phutil_split_lines($stderr, $retain_endings = false);
- $lines = explode("\n", $stderr);
$messages = array();
foreach ($lines as $line) {
$matches = null;
+
if (!preg_match("/(.*?):(\d+): (.*?)$/", $line, $matches)) {
continue;
}
+
foreach ($matches as $key => $match) {
$matches[$key] = trim($match);
}
$code = head(explode(',', $matches[3]));
$message = new ArcanistLintMessage();
$message->setPath($path);
$message->setLine($matches[2]);
- $message->setName($this->getLinterName() . " " . $code);
+ $message->setCode($this->getLinterName());
+ $message->setName(pht('Syntax Error'));
$message->setDescription($matches[3]);
- $message->setSeverity($this->getMessageCodeSeverity($code));
- $this->addLintMessage($message);
+ $message->setSeverity($this->getLintMessageSeverity($code));
+
+ $messages[] = $message;
}
+
+ if ($err && !$messages) {
+ return false;
+ }
+
+ return $messages;
}
}
diff --git a/src/lint/linter/ArcanistScalaSBTLinter.php b/src/lint/linter/ArcanistScalaSBTLinter.php
index 8081d645..10aabbb2 100644
--- a/src/lint/linter/ArcanistScalaSBTLinter.php
+++ b/src/lint/linter/ArcanistScalaSBTLinter.php
@@ -1,103 +1,91 @@
<?php
/**
* Uses `sbt compile` to detect various warnings/errors in Scala code.
*
* @group linter
*/
final class ArcanistScalaSBTLinter extends ArcanistLinter {
- public function willLintPaths(array $paths) {
- return;
- }
-
public function getLinterName() {
return 'ScalaSBT';
}
- public function getLintSeverityMap() {
- return array();
- }
-
- public function getLintNameMap() {
- return array();
- }
-
public function canRun() {
// Check if this looks like a SBT project. If it doesn't, throw, because
// we rely fairly heavily on `sbt compile` working, below. We don't want
// to call out to scalac ourselves, because then we'll end up in Class Path
// Hell. We let the build system handle this for us.
if (!Filesystem::pathExists('project/Build.scala') &&
!Filesystem::pathExists('build.sbt')) {
return false;
}
return true;
}
private function getSBTPath() {
$sbt_bin = "sbt";
// Use the SBT prefix specified in the config file
$working_copy = $this->getEngine()->getWorkingCopy();
$prefix = $working_copy->getConfig('lint.scala_sbt.prefix');
if ($prefix !== null) {
$sbt_bin = $prefix . $sbt_bin;
}
if (!Filesystem::pathExists($sbt_bin)) {
list($err) = exec_manual('which %s', $sbt_bin);
if ($err) {
throw new ArcanistUsageException(
"SBT does not appear to be installed on this system. Install it or ".
"add 'lint.scala_sbt.prefix' in your .arcconfig to point to ".
"the directory where it resides.");
}
}
return $sbt_bin;
}
private function getMessageCodeSeverity($type_of_error) {
switch ($type_of_error) {
case 'warn':
return ArcanistLintSeverity::SEVERITY_WARNING;
case 'error':
return ArcanistLintSeverity::SEVERITY_ERROR;
}
}
public function lintPath($path) {
$sbt = $this->getSBTPath();
// Tell SBT to not use color codes so our regex life is easy.
// TODO: Should this be "clean compile" instead of "compile"?
$f = new ExecFuture("%s -Dsbt.log.noformat=true compile", $sbt);
list($err, $stdout, $stderr) = $f->resolve();
$lines = explode("\n", $stdout);
$messages = array();
foreach ($lines as $line) {
$matches = null;
if (!preg_match(
"/\[(warn|error)\] (.*?):(\d+): (.*?)$/",
$line,
$matches)) {
continue;
}
foreach ($matches as $key => $match) {
$matches[$key] = trim($match);
}
$message = new ArcanistLintMessage();
$message->setPath($matches[2]);
$message->setLine($matches[3]);
$message->setCode($this->getLinterName());
$message->setDescription($matches[4]);
$message->setSeverity($this->getMessageCodeSeverity($matches[1]));
$this->addLintMessage($message);
}
}
}
diff --git a/src/lint/linter/ArcanistSpellingLinter.php b/src/lint/linter/ArcanistSpellingLinter.php
index 1898e75d..1f940f04 100644
--- a/src/lint/linter/ArcanistSpellingLinter.php
+++ b/src/lint/linter/ArcanistSpellingLinter.php
@@ -1,163 +1,151 @@
<?php
/**
* Enforces basic spelling. Spelling inside code is actually pretty hard to
* get right without false positives. I take a conservative approach and
* just use a blacklisted set of words that are commonly spelled
* incorrectly.
*
* @group linter
*/
final class ArcanistSpellingLinter extends ArcanistLinter {
const LINT_SPELLING_PICKY = 0;
const LINT_SPELLING_IMPORTANT = 1;
private $partialWordRules;
private $wholeWordRules;
private $severity;
public function __construct($severity = self::LINT_SPELLING_PICKY) {
$this->severity = $severity;
$this->wholeWordRules = ArcanistSpellingDefaultData::getFullWordRules();
$this->partialWordRules =
ArcanistSpellingDefaultData::getPartialWordRules();
}
- public function willLintPaths(array $paths) {
- return;
- }
-
public function getLinterName() {
return 'SPELL';
}
- public function removeLintRule($word) {
- foreach ($this->partialWordRules as $severity=>&$wordlist) {
- unset($wordlist[$word]);
- }
-
- foreach ($this->wholeWordRules as $severity=>&$wordlist) {
- unset($wordlist[$word]);
- }
+ public function getLinterConfigurationName() {
+ return 'spelling';
}
public function addPartialWordRule(
- $incorrect_word,
- $correct_word,
- $severity=self::LINT_SPELLING_IMPORTANT) {
+ $incorrect_word,
+ $correct_word,
+ $severity = self::LINT_SPELLING_IMPORTANT) {
+
$this->partialWordRules[$severity][$incorrect_word] = $correct_word;
}
public function addWholeWordRule(
- $incorrect_word,
- $correct_word,
- $severity=self::LINT_SPELLING_IMPORTANT) {
+ $incorrect_word,
+ $correct_word,
+ $severity = self::LINT_SPELLING_IMPORTANT) {
+
$this->wholeWordRules[$severity][$incorrect_word] = $correct_word;
}
public function getLintSeverityMap() {
return array(
self::LINT_SPELLING_PICKY => ArcanistLintSeverity::SEVERITY_WARNING,
self::LINT_SPELLING_IMPORTANT => ArcanistLintSeverity::SEVERITY_ERROR,
);
}
public function getLintNameMap() {
return array(
- self::LINT_SPELLING_PICKY => 'Possible spelling mistake',
- self::LINT_SPELLING_IMPORTANT => 'Possible spelling mistake',
+ self::LINT_SPELLING_PICKY => pht('Possible Spelling Mistake'),
+ self::LINT_SPELLING_IMPORTANT => pht('Possible Spelling Mistake'),
);
}
public function lintPath($path) {
- if ($this->isBinaryFile($path)) {
- return;
- }
-
foreach ($this->partialWordRules as $severity => $wordlist) {
if ($severity >= $this->severity) {
if (!$this->isCodeEnabled($severity)) {
continue;
}
foreach ($wordlist as $misspell => $correct) {
$this->checkPartialWord($path, $misspell, $correct, $severity);
}
}
}
foreach ($this->wholeWordRules as $severity => $wordlist) {
if ($severity >= $this->severity) {
if (!$this->isCodeEnabled($severity)) {
continue;
}
foreach ($wordlist as $misspell => $correct) {
$this->checkWholeWord($path, $misspell, $correct, $severity);
}
}
}
}
protected function checkPartialWord($path, $word, $correct_word, $severity) {
$text = $this->getData($path);
$pos = 0;
while ($pos < strlen($text)) {
$next = stripos($text, $word, $pos);
if ($next === false) {
return;
}
$original = substr($text, $next, strlen($word));
$replacement = self::fixLetterCase($correct_word, $original);
$this->raiseLintAtOffset(
$next,
$severity,
sprintf(
"Possible spelling error. You wrote '%s', but did you mean '%s'?",
$word,
$correct_word),
$original,
$replacement);
$pos = $next + 1;
}
}
protected function checkWholeWord($path, $word, $correct_word, $severity) {
$text = $this->getData($path);
$matches = array();
$num_matches = preg_match_all(
- '#\b' . preg_quote($word, '#') . '\b#i',
+ '#\b'.preg_quote($word, '#').'\b#i',
$text,
$matches,
PREG_OFFSET_CAPTURE);
if (!$num_matches) {
return;
}
foreach ($matches[0] as $match) {
$original = $match[0];
$replacement = self::fixLetterCase($correct_word, $original);
$this->raiseLintAtOffset(
$match[1],
$severity,
sprintf(
"Possible spelling error. You wrote '%s', but did you mean '%s'?",
$word,
$correct_word),
$original,
$replacement);
}
}
public static function fixLetterCase($string, $case) {
if ($case == strtolower($case)) {
return strtolower($string);
}
if ($case == strtoupper($case)) {
return strtoupper($string);
}
if ($case == ucwords(strtolower($case))) {
return ucwords(strtolower($string));
}
return null;
}
}
diff --git a/src/lint/linter/ArcanistTextLinter.php b/src/lint/linter/ArcanistTextLinter.php
index fc1425fe..4fdbef48 100644
--- a/src/lint/linter/ArcanistTextLinter.php
+++ b/src/lint/linter/ArcanistTextLinter.php
@@ -1,214 +1,214 @@
<?php
/**
* Enforces basic text file rules.
*
* @group linter
*/
final class ArcanistTextLinter extends ArcanistLinter {
const LINT_DOS_NEWLINE = 1;
const LINT_TAB_LITERAL = 2;
const LINT_LINE_WRAP = 3;
const LINT_EOF_NEWLINE = 4;
const LINT_BAD_CHARSET = 5;
const LINT_TRAILING_WHITESPACE = 6;
const LINT_NO_COMMIT = 7;
private $maxLineLength = 80;
+ public function getLinterPriority() {
+ return 0.5;
+ }
+
public function setMaxLineLength($new_length) {
$this->maxLineLength = $new_length;
return $this;
}
- public function willLintPaths(array $paths) {
- return;
- }
-
public function getLinterName() {
return 'TXT';
}
+ public function getLinterConfigurationName() {
+ return 'text';
+ }
+
public function getLintSeverityMap() {
return array(
self::LINT_LINE_WRAP => ArcanistLintSeverity::SEVERITY_WARNING,
self::LINT_TRAILING_WHITESPACE => ArcanistLintSeverity::SEVERITY_AUTOFIX,
);
}
public function getLintNameMap() {
return array(
- self::LINT_DOS_NEWLINE => 'DOS Newlines',
- self::LINT_TAB_LITERAL => 'Tab Literal',
- self::LINT_LINE_WRAP => 'Line Too Long',
- self::LINT_EOF_NEWLINE => 'File Does Not End in Newline',
- self::LINT_BAD_CHARSET => 'Bad Charset',
- self::LINT_TRAILING_WHITESPACE => 'Trailing Whitespace',
- self::LINT_NO_COMMIT => 'Explicit @no'.'commit',
+ self::LINT_DOS_NEWLINE => pht('DOS Newlines'),
+ self::LINT_TAB_LITERAL => pht('Tab Literal'),
+ self::LINT_LINE_WRAP => pht('Line Too Long'),
+ self::LINT_EOF_NEWLINE => pht('File Does Not End in Newline'),
+ self::LINT_BAD_CHARSET => pht('Bad Charset'),
+ self::LINT_TRAILING_WHITESPACE => pht('Trailing Whitespace'),
+ self::LINT_NO_COMMIT => pht('Explicit %s', '@no'.'commit'),
);
}
public function lintPath($path) {
- if ($this->isBinaryFile($path)) {
- return;
- }
-
if (!strlen($this->getData($path))) {
// If the file is empty, don't bother; particularly, don't require
// the user to add a newline.
return;
}
$this->lintNewlines($path);
$this->lintTabs($path);
if ($this->didStopAllLinters()) {
return;
}
$this->lintCharset($path);
if ($this->didStopAllLinters()) {
return;
}
$this->lintLineLength($path);
$this->lintEOFNewline($path);
$this->lintTrailingWhitespace($path);
if ($this->getEngine()->getCommitHookMode()) {
$this->lintNoCommit($path);
}
}
protected function lintNewlines($path) {
$pos = strpos($this->getData($path), "\r");
if ($pos !== false) {
$this->raiseLintAtOffset(
$pos,
self::LINT_DOS_NEWLINE,
'You must use ONLY Unix linebreaks ("\n") in source code.',
"\r");
if ($this->isMessageEnabled(self::LINT_DOS_NEWLINE)) {
$this->stopAllLinters();
}
}
}
protected function lintTabs($path) {
$pos = strpos($this->getData($path), "\t");
if ($pos !== false) {
$this->raiseLintAtOffset(
$pos,
self::LINT_TAB_LITERAL,
'Configure your editor to use spaces for indentation.',
"\t");
}
}
protected function lintLineLength($path) {
$lines = explode("\n", $this->getData($path));
$width = $this->maxLineLength;
foreach ($lines as $line_idx => $line) {
if (strlen($line) > $width) {
$this->raiseLintAtLine(
$line_idx + 1,
1,
self::LINT_LINE_WRAP,
'This line is '.number_format(strlen($line)).' characters long, '.
'but the convention is '.$width.' characters.',
$line);
}
}
}
protected function lintEOFNewline($path) {
$data = $this->getData($path);
if (!strlen($data) || $data[strlen($data) - 1] != "\n") {
$this->raiseLintAtOffset(
strlen($data),
self::LINT_EOF_NEWLINE,
"Files must end in a newline.",
'',
"\n");
}
}
protected function lintCharset($path) {
$data = $this->getData($path);
$matches = null;
$bad = '[^\x09\x0A\x20-\x7E]';
$preg = preg_match_all(
"/{$bad}(.*{$bad})?/",
$data,
$matches,
PREG_OFFSET_CAPTURE);
if (!$preg) {
return;
}
foreach ($matches[0] as $match) {
list($string, $offset) = $match;
$this->raiseLintAtOffset(
$offset,
self::LINT_BAD_CHARSET,
'Source code should contain only ASCII bytes with ordinal decimal '.
'values between 32 and 126 inclusive, plus linefeed. Do not use UTF-8 '.
'or other multibyte charsets.',
$string);
}
if ($this->isMessageEnabled(self::LINT_BAD_CHARSET)) {
$this->stopAllLinters();
}
}
protected function lintTrailingWhitespace($path) {
$data = $this->getData($path);
$matches = null;
$preg = preg_match_all(
'/ +$/m',
$data,
$matches,
PREG_OFFSET_CAPTURE);
if (!$preg) {
return;
}
foreach ($matches[0] as $match) {
list($string, $offset) = $match;
$this->raiseLintAtOffset(
$offset,
self::LINT_TRAILING_WHITESPACE,
'This line contains trailing whitespace. Consider setting up your '.
'editor to automatically remove trailing whitespace, you will save '.
'time.',
$string,
'');
}
}
private function lintNoCommit($path) {
$data = $this->getData($path);
$deadly = '@no'.'commit';
$offset = strpos($data, $deadly);
if ($offset !== false) {
$this->raiseLintAtOffset(
$offset,
self::LINT_NO_COMMIT,
'This file is explicitly marked as "'.$deadly.'", which blocks '.
'commits.',
$deadly);
}
}
}
diff --git a/src/lint/linter/__tests__/ArcanistSpellingLinterTestCase.php b/src/lint/linter/__tests__/ArcanistSpellingLinterTestCase.php
index a8c26970..667058fa 100644
--- a/src/lint/linter/__tests__/ArcanistSpellingLinterTestCase.php
+++ b/src/lint/linter/__tests__/ArcanistSpellingLinterTestCase.php
@@ -1,37 +1,36 @@
<?php
/**
* Test cases for @{class:ArcanistSpellingLinter}.
*
* @group testcase
*/
final class ArcanistSpellingLinterTestCase
extends ArcanistArcanistLinterTestCase {
public function testSpellingLint() {
$linter = new ArcanistSpellingLinter();
- $linter->removeLintRule('acc'.'out');
$linter->addPartialWordRule('supermn', 'superman');
$linter->addWholeWordRule('batmn', 'batman');
return $this->executeTestsInDirectory(
dirname(__FILE__).'/spelling/',
$linter);
}
public function testFixLetterCase() {
$tests = array(
'tst' => 'test',
'Tst' => 'Test',
'TST' => 'TEST',
'tSt' => null,
);
foreach ($tests as $case => $expect) {
foreach (array('test', 'TEST') as $string) {
$result = ArcanistSpellingLinter::fixLetterCase($string, $case);
$this->assertEqual($expect, $result, $case);
}
}
}
}
diff --git a/src/lint/linter/__tests__/spelling/spell.lint-test b/src/lint/linter/__tests__/spelling/spell.lint-test
index bae2ca2d..aa1a5f1f 100644
--- a/src/lint/linter/__tests__/spelling/spell.lint-test
+++ b/src/lint/linter/__tests__/spelling/spell.lint-test
@@ -1,25 +1,25 @@
Hello
teh
word
$y = $x->recieveData();
for (i=0; i<y.lenght; i++) {
}
Check uFT8 ufT8 (<-- repeated)
-removed accout d
+
didn't remove acording
Added ZZZZsupermnZZZZ
Added full batmn batmnZZZZ
~~~~~~~~~~
error:2:1
error:4:10
error:5:15
error:7:7
error:7:12
warning:9:15
error:10:11
error:11:12
~~~~~~~~~~
~~~~~~~~~~
{
"hook" : true
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 07:27 (19 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
556856
Default Alt Text
(88 KB)

Event Timeline