diff --git a/.arclint b/.arclint index 816de021..d6233a0f 100644 --- a/.arclint +++ b/.arclint @@ -1,59 +1,47 @@ { "exclude": [ "(^externals/)" ], "linters": { "chmod": { "type": "chmod" }, "filename": { "type": "filename" }, "generated": { "type": "generated" }, "json": { "type": "json", "include": [ "(^resources/arclint/.*\\.arclint\\.example$)", "(^\\.arcconfig$)", "(^\\.arclint$)", "(\\.json$)" ] }, "merge-conflict": { "type": "merge-conflict" }, "nolint": { "type": "nolint" }, "phutil-library": { "type": "phutil-library", "include": "(\\.php$)" }, - "phutil-xhpast": { - "type": "phutil-xhpast", - "include": "(\\.php$)" - }, "spelling": { "type": "spelling", "exclude": "(^resources/spelling/.*\\.json$)" }, "text": { "type": "text" }, "xhpast": { "type": "xhpast", "include": "(\\.php$)", - "severity": { - "16": "advice", - "34": "error" - }, - "xhpast.blacklisted.function": { - "eval": "The eval() function should be avoided. It is potentially unsafe and makes debugging more difficult." - }, - "xhpast.php-version": "5.2.3", - "xhpast.php-version.windows": "5.3.0" + "standard": "phutil.xhpast" } } } diff --git a/.editorconfig b/.editorconfig index 00f7d7c0..663b0f40 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,53 +1,53 @@ ; http://editorconfig.org/ [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 80 -[src/lint/linter/__tests__/**.lint-test] +[src/lint/linter/**/__tests__/**.lint-test] indent_style = end_of_line = max_line_length = trim_trailing_whitespace = false [src/parser/__tests__/bundle/*] insert_final_newline = false [src/parser/__tests__/diff/*.{git,hg,rcs,svn,u}diff] indent_style = trim_trailing_whitespace = false insert_final_newline = false max_line_length = [src/parser/__tests__/patches/*.{git,hg,rcs,svn,u}patch] indent_style = trim_trailing_whitespace = false max_line_length = [src/parser/__tests__/patches/*.gitpatch] end_of_line = [src/parser/__tests__/**/*.txt] max_line_length = [src/repository/parser/__tests__/mercurial/*.txt] trim_trailing_whitespace = false insert_final_newline = false max_line_length = [src/unit/parser/__tests__/testresults/go.*] indent_style = [src/unit/parser/__tests__/testresults/xunit.*] max_line_length = [externals/**] indent_style = indent_size = trim_trailing_whitespace = false insert_final_newline = false diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ec6a78b5..f0f9a8fd 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,588 +1,766 @@ 2, 'class' => array( 'ArcanistAliasFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php', + 'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAliasFunctionXHPASTLinterRuleTestCase.php', 'ArcanistAliasWorkflow' => 'workflow/ArcanistAliasWorkflow.php', 'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php', 'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php', + 'ArcanistArrayCombineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php', + 'ArcanistArrayCombineXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistArrayCombineXHPASTLinterRuleTestCase.php', 'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayIndexSpacingXHPASTLinterRule.php', + 'ArcanistArrayIndexSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistArrayIndexSpacingXHPASTLinterRuleTestCase.php', 'ArcanistArraySeparatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArraySeparatorXHPASTLinterRule.php', + 'ArcanistArraySeparatorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistArraySeparatorXHPASTLinterRuleTestCase.php', 'ArcanistArrayValueXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayValueXHPASTLinterRule.php', + 'ArcanistArrayValueXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistArrayValueXHPASTLinterRuleTestCase.php', 'ArcanistBackoutWorkflow' => 'workflow/ArcanistBackoutWorkflow.php', 'ArcanistBaseCommitParser' => 'parser/ArcanistBaseCommitParser.php', 'ArcanistBaseCommitParserTestCase' => 'parser/__tests__/ArcanistBaseCommitParserTestCase.php', 'ArcanistBaseXHPASTLinter' => 'lint/linter/ArcanistBaseXHPASTLinter.php', 'ArcanistBinaryExpressionSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistBinaryExpressionSpacingXHPASTLinterRule.php', + 'ArcanistBinaryExpressionSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistBinaryExpressionSpacingXHPASTLinterRuleTestCase.php', 'ArcanistBlacklistedFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistBlacklistedFunctionXHPASTLinterRule.php', + 'ArcanistBlacklistedFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistBlacklistedFunctionXHPASTLinterRuleTestCase.php', 'ArcanistBookmarkWorkflow' => 'workflow/ArcanistBookmarkWorkflow.php', 'ArcanistBraceFormattingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php', + 'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistBraceFormattingXHPASTLinterRuleTestCase.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', 'ArcanistCSharpLinter' => 'lint/linter/ArcanistCSharpLinter.php', 'ArcanistCallConduitWorkflow' => 'workflow/ArcanistCallConduitWorkflow.php', 'ArcanistCallParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCallParenthesesXHPASTLinterRule.php', + 'ArcanistCallParenthesesXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistCallParenthesesXHPASTLinterRuleTestCase.php', 'ArcanistCallTimePassByReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCallTimePassByReferenceXHPASTLinterRule.php', + 'ArcanistCallTimePassByReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistCallTimePassByReferenceXHPASTLinterRuleTestCase.php', 'ArcanistCapabilityNotSupportedException' => 'workflow/exception/ArcanistCapabilityNotSupportedException.php', 'ArcanistCastSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCastSpacingXHPASTLinterRule.php', + 'ArcanistCastSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistCastSpacingXHPASTLinterRuleTestCase.php', 'ArcanistCheckstyleXMLLintRenderer' => 'lint/renderer/ArcanistCheckstyleXMLLintRenderer.php', 'ArcanistChmodLinter' => 'lint/linter/ArcanistChmodLinter.php', 'ArcanistChmodLinterTestCase' => 'lint/linter/__tests__/ArcanistChmodLinterTestCase.php', + 'ArcanistClassExtendsObjectXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassExtendsObjectXHPASTLinterRule.php', + 'ArcanistClassExtendsObjectXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistClassExtendsObjectXHPASTLinterRuleTestCase.php', 'ArcanistClassFilenameMismatchXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassFilenameMismatchXHPASTLinterRule.php', 'ArcanistClassNameLiteralXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassNameLiteralXHPASTLinterRule.php', + 'ArcanistClassNameLiteralXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistClassNameLiteralXHPASTLinterRuleTestCase.php', 'ArcanistCloseRevisionWorkflow' => 'workflow/ArcanistCloseRevisionWorkflow.php', 'ArcanistCloseWorkflow' => 'workflow/ArcanistCloseWorkflow.php', 'ArcanistClosureLinter' => 'lint/linter/ArcanistClosureLinter.php', 'ArcanistClosureLinterTestCase' => 'lint/linter/__tests__/ArcanistClosureLinterTestCase.php', 'ArcanistCoffeeLintLinter' => 'lint/linter/ArcanistCoffeeLintLinter.php', 'ArcanistCoffeeLintLinterTestCase' => 'lint/linter/__tests__/ArcanistCoffeeLintLinterTestCase.php', 'ArcanistCommentRemover' => 'parser/ArcanistCommentRemover.php', 'ArcanistCommentRemoverTestCase' => 'parser/__tests__/ArcanistCommentRemoverTestCase.php', 'ArcanistCommentSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCommentSpacingXHPASTLinterRule.php', 'ArcanistCommentStyleXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCommentStyleXHPASTLinterRule.php', + 'ArcanistCommentStyleXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistCommentStyleXHPASTLinterRuleTestCase.php', 'ArcanistCommitWorkflow' => 'workflow/ArcanistCommitWorkflow.php', 'ArcanistCompilerLintRenderer' => 'lint/renderer/ArcanistCompilerLintRenderer.php', 'ArcanistComposerLinter' => 'lint/linter/ArcanistComposerLinter.php', 'ArcanistComprehensiveLintEngine' => 'lint/engine/ArcanistComprehensiveLintEngine.php', 'ArcanistConcatenationOperatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConcatenationOperatorXHPASTLinterRule.php', + 'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistConcatenationOperatorXHPASTLinterRuleTestCase.php', 'ArcanistConfiguration' => 'configuration/ArcanistConfiguration.php', 'ArcanistConfigurationDrivenLintEngine' => 'lint/engine/ArcanistConfigurationDrivenLintEngine.php', 'ArcanistConfigurationDrivenUnitTestEngine' => 'unit/engine/ArcanistConfigurationDrivenUnitTestEngine.php', 'ArcanistConfigurationManager' => 'configuration/ArcanistConfigurationManager.php', 'ArcanistConsoleLintRenderer' => 'lint/renderer/ArcanistConsoleLintRenderer.php', 'ArcanistConstructorParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConstructorParenthesesXHPASTLinterRule.php', + 'ArcanistConstructorParenthesesXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistConstructorParenthesesXHPASTLinterRuleTestCase.php', 'ArcanistControlStatementSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistControlStatementSpacingXHPASTLinterRule.php', + 'ArcanistControlStatementSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistControlStatementSpacingXHPASTLinterRuleTestCase.php', 'ArcanistCoverWorkflow' => 'workflow/ArcanistCoverWorkflow.php', 'ArcanistCppcheckLinter' => 'lint/linter/ArcanistCppcheckLinter.php', 'ArcanistCppcheckLinterTestCase' => 'lint/linter/__tests__/ArcanistCppcheckLinterTestCase.php', 'ArcanistCpplintLinter' => 'lint/linter/ArcanistCpplintLinter.php', 'ArcanistCpplintLinterTestCase' => 'lint/linter/__tests__/ArcanistCpplintLinterTestCase.php', 'ArcanistDeclarationParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDeclarationParenthesesXHPASTLinterRule.php', + 'ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase.php', 'ArcanistDefaultParametersXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDefaultParametersXHPASTLinterRule.php', + 'ArcanistDefaultParametersXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDefaultParametersXHPASTLinterRuleTestCase.php', + 'ArcanistDeprecationXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php', + 'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDeprecationXHPASTLinterRuleTestCase.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', 'ArcanistDifferentialDependencyGraph' => 'differential/ArcanistDifferentialDependencyGraph.php', 'ArcanistDifferentialRevisionHash' => 'differential/constants/ArcanistDifferentialRevisionHash.php', 'ArcanistDifferentialRevisionStatus' => 'differential/constants/ArcanistDifferentialRevisionStatus.php', 'ArcanistDoubleQuoteXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDoubleQuoteXHPASTLinterRule.php', + 'ArcanistDoubleQuoteXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDoubleQuoteXHPASTLinterRuleTestCase.php', 'ArcanistDownloadWorkflow' => 'workflow/ArcanistDownloadWorkflow.php', 'ArcanistDuplicateKeysInArrayXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDuplicateKeysInArrayXHPASTLinterRule.php', + 'ArcanistDuplicateKeysInArrayXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDuplicateKeysInArrayXHPASTLinterRuleTestCase.php', 'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDuplicateSwitchCaseXHPASTLinterRule.php', + 'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase.php', 'ArcanistDynamicDefineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php', + 'ArcanistDynamicDefineXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDynamicDefineXHPASTLinterRuleTestCase.php', 'ArcanistElseIfUsageXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php', + 'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistElseIfUsageXHPASTLinterRuleTestCase.php', 'ArcanistEmptyFileXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php', 'ArcanistEmptyStatementXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyStatementXHPASTLinterRule.php', + 'ArcanistEmptyStatementXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistEmptyStatementXHPASTLinterRuleTestCase.php', 'ArcanistEventType' => 'events/constant/ArcanistEventType.php', 'ArcanistExitExpressionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistExitExpressionXHPASTLinterRule.php', + 'ArcanistExitExpressionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistExitExpressionXHPASTLinterRuleTestCase.php', 'ArcanistExportWorkflow' => 'workflow/ArcanistExportWorkflow.php', 'ArcanistExternalLinter' => 'lint/linter/ArcanistExternalLinter.php', 'ArcanistExternalLinterTestCase' => 'lint/linter/__tests__/ArcanistExternalLinterTestCase.php', 'ArcanistExtractUseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistExtractUseXHPASTLinterRule.php', + 'ArcanistExtractUseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistExtractUseXHPASTLinterRuleTestCase.php', 'ArcanistFeatureWorkflow' => 'workflow/ArcanistFeatureWorkflow.php', 'ArcanistFileDataRef' => 'upload/ArcanistFileDataRef.php', 'ArcanistFileUploader' => 'upload/ArcanistFileUploader.php', 'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php', 'ArcanistFilenameLinterTestCase' => 'lint/linter/__tests__/ArcanistFilenameLinterTestCase.php', 'ArcanistFlagWorkflow' => 'workflow/ArcanistFlagWorkflow.php', 'ArcanistFlake8Linter' => 'lint/linter/ArcanistFlake8Linter.php', 'ArcanistFlake8LinterTestCase' => 'lint/linter/__tests__/ArcanistFlake8LinterTestCase.php', 'ArcanistFormattedStringXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php', + 'ArcanistFormattedStringXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistFormattedStringXHPASTLinterRuleTestCase.php', 'ArcanistFutureLinter' => 'lint/linter/ArcanistFutureLinter.php', 'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php', 'ArcanistGeneratedLinterTestCase' => 'lint/linter/__tests__/ArcanistGeneratedLinterTestCase.php', 'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php', 'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php', 'ArcanistGitLandEngine' => 'land/ArcanistGitLandEngine.php', 'ArcanistGitUpstreamPath' => 'repository/api/ArcanistGitUpstreamPath.php', 'ArcanistGlobalVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistGlobalVariableXHPASTLinterRule.php', + 'ArcanistGlobalVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistGlobalVariableXHPASTLinterRuleTestCase.php', 'ArcanistGoLintLinter' => 'lint/linter/ArcanistGoLintLinter.php', 'ArcanistGoLintLinterTestCase' => 'lint/linter/__tests__/ArcanistGoLintLinterTestCase.php', 'ArcanistGoTestResultParser' => 'unit/parser/ArcanistGoTestResultParser.php', 'ArcanistGoTestResultParserTestCase' => 'unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php', 'ArcanistHLintLinter' => 'lint/linter/ArcanistHLintLinter.php', 'ArcanistHLintLinterTestCase' => 'lint/linter/__tests__/ArcanistHLintLinterTestCase.php', 'ArcanistHelpWorkflow' => 'workflow/ArcanistHelpWorkflow.php', 'ArcanistHgClientChannel' => 'hgdaemon/ArcanistHgClientChannel.php', 'ArcanistHgProxyClient' => 'hgdaemon/ArcanistHgProxyClient.php', 'ArcanistHgProxyServer' => 'hgdaemon/ArcanistHgProxyServer.php', 'ArcanistHgServerChannel' => 'hgdaemon/ArcanistHgServerChannel.php', 'ArcanistImplicitConstructorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitConstructorXHPASTLinterRule.php', + 'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplicitConstructorXHPASTLinterRuleTestCase.php', 'ArcanistImplicitFallthroughXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitFallthroughXHPASTLinterRule.php', + 'ArcanistImplicitFallthroughXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplicitFallthroughXHPASTLinterRuleTestCase.php', 'ArcanistImplicitVisibilityXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitVisibilityXHPASTLinterRule.php', + 'ArcanistImplicitVisibilityXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplicitVisibilityXHPASTLinterRuleTestCase.php', 'ArcanistInlineHTMLXHPASTLinterRule' => 'lint/linter/ArcanistInlineHTMLXHPASTLinterRule.php', + 'ArcanistInlineHTMLXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistInlineHTMLXHPASTLinterRuleTestCase.php', 'ArcanistInnerFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistInnerFunctionXHPASTLinterRule.php', + 'ArcanistInnerFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistInnerFunctionXHPASTLinterRuleTestCase.php', 'ArcanistInstallCertificateWorkflow' => 'workflow/ArcanistInstallCertificateWorkflow.php', 'ArcanistInstanceOfOperatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistInstanceOfOperatorXHPASTLinterRule.php', + 'ArcanistInstanceofOperatorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistInstanceofOperatorXHPASTLinterRuleTestCase.php', 'ArcanistInvalidDefaultParameterXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistInvalidDefaultParameterXHPASTLinterRule.php', + 'ArcanistInvalidDefaultParameterXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistInvalidDefaultParameterXHPASTLinterRuleTestCase.php', 'ArcanistInvalidModifiersXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistInvalidModifiersXHPASTLinterRule.php', + 'ArcanistInvalidModifiersXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistInvalidModifiersXHPASTLinterRuleTestCase.php', 'ArcanistJSHintLinter' => 'lint/linter/ArcanistJSHintLinter.php', 'ArcanistJSHintLinterTestCase' => 'lint/linter/__tests__/ArcanistJSHintLinterTestCase.php', 'ArcanistJSONLintLinter' => 'lint/linter/ArcanistJSONLintLinter.php', 'ArcanistJSONLintLinterTestCase' => 'lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php', 'ArcanistJSONLintRenderer' => 'lint/renderer/ArcanistJSONLintRenderer.php', 'ArcanistJSONLinter' => 'lint/linter/ArcanistJSONLinter.php', 'ArcanistJSONLinterTestCase' => 'lint/linter/__tests__/ArcanistJSONLinterTestCase.php', 'ArcanistJscsLinter' => 'lint/linter/ArcanistJscsLinter.php', 'ArcanistJscsLinterTestCase' => 'lint/linter/__tests__/ArcanistJscsLinterTestCase.php', 'ArcanistKeywordCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistKeywordCasingXHPASTLinterRule.php', + 'ArcanistKeywordCasingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistKeywordCasingXHPASTLinterRuleTestCase.php', 'ArcanistLambdaFuncFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLambdaFuncFunctionXHPASTLinterRule.php', + 'ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase.php', 'ArcanistLandEngine' => 'land/ArcanistLandEngine.php', 'ArcanistLandWorkflow' => 'workflow/ArcanistLandWorkflow.php', 'ArcanistLanguageConstructParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLanguageConstructParenthesesXHPASTLinterRule.php', + 'ArcanistLanguageConstructParenthesesXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLanguageConstructParenthesesXHPASTLinterRuleTestCase.php', 'ArcanistLesscLinter' => 'lint/linter/ArcanistLesscLinter.php', 'ArcanistLesscLinterTestCase' => 'lint/linter/__tests__/ArcanistLesscLinterTestCase.php', 'ArcanistLiberateWorkflow' => 'workflow/ArcanistLiberateWorkflow.php', 'ArcanistLibraryTestCase' => '__tests__/ArcanistLibraryTestCase.php', 'ArcanistLintEngine' => 'lint/engine/ArcanistLintEngine.php', 'ArcanistLintMessage' => 'lint/ArcanistLintMessage.php', 'ArcanistLintPatcher' => 'lint/ArcanistLintPatcher.php', 'ArcanistLintRenderer' => 'lint/renderer/ArcanistLintRenderer.php', 'ArcanistLintResult' => 'lint/ArcanistLintResult.php', 'ArcanistLintSeverity' => 'lint/ArcanistLintSeverity.php', 'ArcanistLintWorkflow' => 'workflow/ArcanistLintWorkflow.php', 'ArcanistLinter' => 'lint/linter/ArcanistLinter.php', + 'ArcanistLinterStandard' => 'lint/linter/standards/ArcanistLinterStandard.php', + 'ArcanistLinterStandardTestCase' => 'lint/linter/standards/__tests__/ArcanistLinterStandardTestCase.php', 'ArcanistLinterTestCase' => 'lint/linter/__tests__/ArcanistLinterTestCase.php', 'ArcanistLintersWorkflow' => 'workflow/ArcanistLintersWorkflow.php', 'ArcanistListAssignmentXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistListAssignmentXHPASTLinterRule.php', + 'ArcanistListAssignmentXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistListAssignmentXHPASTLinterRuleTestCase.php', 'ArcanistListWorkflow' => 'workflow/ArcanistListWorkflow.php', 'ArcanistLogicalOperatorsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLogicalOperatorsXHPASTLinterRule.php', + 'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLogicalOperatorsXHPASTLinterRuleTestCase.php', 'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLowercaseFunctionsXHPASTLinterRule.php', + 'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase.php', 'ArcanistMercurialAPI' => 'repository/api/ArcanistMercurialAPI.php', 'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php', 'ArcanistMercurialParserTestCase' => 'repository/parser/__tests__/ArcanistMercurialParserTestCase.php', 'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.php', 'ArcanistMergeConflictLinterTestCase' => 'lint/linter/__tests__/ArcanistMergeConflictLinterTestCase.php', 'ArcanistMissingLinterException' => 'lint/linter/exception/ArcanistMissingLinterException.php', 'ArcanistModifierOrderingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistModifierOrderingXHPASTLinterRule.php', + 'ArcanistModifierOrderingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistModifierOrderingXHPASTLinterRuleTestCase.php', 'ArcanistNamingConventionsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNamingConventionsXHPASTLinterRule.php', + 'ArcanistNamingConventionsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistNamingConventionsXHPASTLinterRuleTestCase.php', + 'ArcanistNestedNamespacesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNestedNamespacesXHPASTLinterRule.php', + 'ArcanistNestedNamespacesXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistNestedNamespacesXHPASTLinterRuleTestCase.php', 'ArcanistNewlineAfterOpenTagXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNewlineAfterOpenTagXHPASTLinterRule.php', + 'ArcanistNewlineAfterOpenTagXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistNewlineAfterOpenTagXHPASTLinterRuleTestCase.php', 'ArcanistNoEffectException' => 'exception/usage/ArcanistNoEffectException.php', 'ArcanistNoEngineException' => 'exception/usage/ArcanistNoEngineException.php', 'ArcanistNoLintLinter' => 'lint/linter/ArcanistNoLintLinter.php', 'ArcanistNoLintLinterTestCase' => 'lint/linter/__tests__/ArcanistNoLintLinterTestCase.php', 'ArcanistNoParentScopeXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNoParentScopeXHPASTLinterRule.php', + 'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistNoParentScopeXHPASTLinterRuleTestCase.php', 'ArcanistNoneLintRenderer' => 'lint/renderer/ArcanistNoneLintRenderer.php', 'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistObjectOperatorSpacingXHPASTLinterRule.php', + 'ArcanistObjectOperatorSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistObjectOperatorSpacingXHPASTLinterRuleTestCase.php', 'ArcanistPEP8Linter' => 'lint/linter/ArcanistPEP8Linter.php', 'ArcanistPEP8LinterTestCase' => 'lint/linter/__tests__/ArcanistPEP8LinterTestCase.php', 'ArcanistPHPCloseTagXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPHPCloseTagXHPASTLinterRule.php', 'ArcanistPHPCompatibilityXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php', + 'ArcanistPHPCompatibilityXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPHPCompatibilityXHPASTLinterRuleTestCase.php', 'ArcanistPHPEchoTagXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPHPEchoTagXHPASTLinterRule.php', + 'ArcanistPHPEchoTagXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPHPEchoTagXHPASTLinterRuleTestCase.php', 'ArcanistPHPOpenTagXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPHPOpenTagXHPASTLinterRule.php', + 'ArcanistPHPOpenTagXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPHPOpenTagXHPASTLinterRuleTestCase.php', 'ArcanistPHPShortTagXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPHPShortTagXHPASTLinterRule.php', + 'ArcanistPHPShortTagXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPHPShortTagXHPASTLinterRuleTestCase.php', + 'ArcanistParentMemberReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistParentMemberReferenceXHPASTLinterRule.php', + 'ArcanistParentMemberReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistParentMemberReferenceXHPASTLinterRuleTestCase.php', 'ArcanistParenthesesSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php', + 'ArcanistParenthesesSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistParenthesesSpacingXHPASTLinterRuleTestCase.php', 'ArcanistParseStrUseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistParseStrUseXHPASTLinterRule.php', + 'ArcanistParseStrUseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistParseStrUseXHPASTLinterRuleTestCase.php', 'ArcanistPasteWorkflow' => 'workflow/ArcanistPasteWorkflow.php', 'ArcanistPatchWorkflow' => 'workflow/ArcanistPatchWorkflow.php', 'ArcanistPhpLinter' => 'lint/linter/ArcanistPhpLinter.php', 'ArcanistPhpLinterTestCase' => 'lint/linter/__tests__/ArcanistPhpLinterTestCase.php', 'ArcanistPhpcsLinter' => 'lint/linter/ArcanistPhpcsLinter.php', 'ArcanistPhpcsLinterTestCase' => 'lint/linter/__tests__/ArcanistPhpcsLinterTestCase.php', 'ArcanistPhpunitTestResultParser' => 'unit/parser/ArcanistPhpunitTestResultParser.php', 'ArcanistPhrequentWorkflow' => 'workflow/ArcanistPhrequentWorkflow.php', 'ArcanistPhutilLibraryLinter' => 'lint/linter/ArcanistPhutilLibraryLinter.php', - 'ArcanistPhutilXHPASTLinter' => 'lint/linter/ArcanistPhutilXHPASTLinter.php', - 'ArcanistPhutilXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php', + 'ArcanistPhutilXHPASTLinterStandard' => 'lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php', 'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php', + 'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php', 'ArcanistPregQuoteMisuseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPregQuoteMisuseXHPASTLinterRule.php', + 'ArcanistPregQuoteMisuseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPregQuoteMisuseXHPASTLinterRuleTestCase.php', 'ArcanistPuppetLintLinter' => 'lint/linter/ArcanistPuppetLintLinter.php', 'ArcanistPuppetLintLinterTestCase' => 'lint/linter/__tests__/ArcanistPuppetLintLinterTestCase.php', 'ArcanistPyFlakesLinter' => 'lint/linter/ArcanistPyFlakesLinter.php', 'ArcanistPyFlakesLinterTestCase' => 'lint/linter/__tests__/ArcanistPyFlakesLinterTestCase.php', 'ArcanistPyLintLinter' => 'lint/linter/ArcanistPyLintLinter.php', 'ArcanistPyLintLinterTestCase' => 'lint/linter/__tests__/ArcanistPyLintLinterTestCase.php', + 'ArcanistRaggedClassTreeEdgeXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistRaggedClassTreeEdgeXHPASTLinterRule.php', + 'ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase.php', 'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php', 'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php', 'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php', 'ArcanistReusedAsIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedAsIteratorXHPASTLinterRule.php', + 'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedAsIteratorXHPASTLinterRuleTestCase.php', 'ArcanistReusedIteratorReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedIteratorReferenceXHPASTLinterRule.php', + 'ArcanistReusedIteratorReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorReferenceXHPASTLinterRuleTestCase.php', 'ArcanistReusedIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedIteratorXHPASTLinterRule.php', + 'ArcanistReusedIteratorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorXHPASTLinterRuleTestCase.php', 'ArcanistRevertWorkflow' => 'workflow/ArcanistRevertWorkflow.php', 'ArcanistRuboCopLinter' => 'lint/linter/ArcanistRuboCopLinter.php', 'ArcanistRuboCopLinterTestCase' => 'lint/linter/__tests__/ArcanistRuboCopLinterTestCase.php', 'ArcanistRubyLinter' => 'lint/linter/ArcanistRubyLinter.php', 'ArcanistRubyLinterTestCase' => 'lint/linter/__tests__/ArcanistRubyLinterTestCase.php', 'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php', 'ArcanistSelfMemberReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSelfMemberReferenceXHPASTLinterRule.php', + 'ArcanistSelfMemberReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSelfMemberReferenceXHPASTLinterRuleTestCase.php', 'ArcanistSemicolonSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSemicolonSpacingXHPASTLinterRule.php', + 'ArcanistSemicolonSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSemicolonSpacingXHPASTLinterRuleTestCase.php', 'ArcanistSetConfigWorkflow' => 'workflow/ArcanistSetConfigWorkflow.php', 'ArcanistSettings' => 'configuration/ArcanistSettings.php', 'ArcanistShellCompleteWorkflow' => 'workflow/ArcanistShellCompleteWorkflow.php', 'ArcanistSingleLintEngine' => 'lint/engine/ArcanistSingleLintEngine.php', 'ArcanistSlownessXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSlownessXHPASTLinterRule.php', + 'ArcanistSlownessXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSlownessXHPASTLinterRuleTestCase.php', 'ArcanistSpellingLinter' => 'lint/linter/ArcanistSpellingLinter.php', 'ArcanistSpellingLinterTestCase' => 'lint/linter/__tests__/ArcanistSpellingLinterTestCase.php', 'ArcanistStartWorkflow' => 'workflow/ArcanistStartWorkflow.php', 'ArcanistStaticThisXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistStaticThisXHPASTLinterRule.php', + 'ArcanistStaticThisXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistStaticThisXHPASTLinterRuleTestCase.php', 'ArcanistStopWorkflow' => 'workflow/ArcanistStopWorkflow.php', 'ArcanistSubversionAPI' => 'repository/api/ArcanistSubversionAPI.php', 'ArcanistSummaryLintRenderer' => 'lint/renderer/ArcanistSummaryLintRenderer.php', 'ArcanistSyntaxErrorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSyntaxErrorXHPASTLinterRule.php', 'ArcanistTasksWorkflow' => 'workflow/ArcanistTasksWorkflow.php', 'ArcanistTautologicalExpressionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistTautologicalExpressionXHPASTLinterRule.php', + 'ArcanistTautologicalExpressionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistTautologicalExpressionXHPASTLinterRuleTestCase.php', 'ArcanistTestResultParser' => 'unit/parser/ArcanistTestResultParser.php', 'ArcanistTestXHPASTLintSwitchHook' => 'lint/linter/__tests__/ArcanistTestXHPASTLintSwitchHook.php', 'ArcanistTextLinter' => 'lint/linter/ArcanistTextLinter.php', 'ArcanistTextLinterTestCase' => 'lint/linter/__tests__/ArcanistTextLinterTestCase.php', + 'ArcanistThisReassignmentXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistThisReassignmentXHPASTLinterRule.php', + 'ArcanistThisReassignmentXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistThisReassignmentXHPASTLinterRuleTestCase.php', 'ArcanistTimeWorkflow' => 'workflow/ArcanistTimeWorkflow.php', 'ArcanistToStringExceptionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistToStringExceptionXHPASTLinterRule.php', + 'ArcanistToStringExceptionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistToStringExceptionXHPASTLinterRuleTestCase.php', 'ArcanistTodoCommentXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistTodoCommentXHPASTLinterRule.php', + 'ArcanistTodoCommentXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistTodoCommentXHPASTLinterRuleTestCase.php', 'ArcanistTodoWorkflow' => 'workflow/ArcanistTodoWorkflow.php', 'ArcanistUSEnglishTranslation' => 'internationalization/ArcanistUSEnglishTranslation.php', 'ArcanistUnableToParseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnableToParseXHPASTLinterRule.php', 'ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRule.php', + 'ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRuleTestCase.php', 'ArcanistUnaryPrefixExpressionSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnaryPrefixExpressionSpacingXHPASTLinterRule.php', + 'ArcanistUnaryPrefixExpressionSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUnaryPrefixExpressionSpacingXHPASTLinterRuleTestCase.php', 'ArcanistUndeclaredVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUndeclaredVariableXHPASTLinterRule.php', + 'ArcanistUndeclaredVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUndeclaredVariableXHPASTLinterRuleTestCase.php', + 'ArcanistUnexpectedReturnValueXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnexpectedReturnValueXHPASTLinterRule.php', + 'ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase.php', 'ArcanistUnitConsoleRenderer' => 'unit/renderer/ArcanistUnitConsoleRenderer.php', 'ArcanistUnitRenderer' => 'unit/renderer/ArcanistUnitRenderer.php', 'ArcanistUnitTestEngine' => 'unit/engine/ArcanistUnitTestEngine.php', 'ArcanistUnitTestResult' => 'unit/ArcanistUnitTestResult.php', 'ArcanistUnitTestResultTestCase' => 'unit/__tests__/ArcanistUnitTestResultTestCase.php', 'ArcanistUnitTestableLintEngine' => 'lint/engine/ArcanistUnitTestableLintEngine.php', 'ArcanistUnitWorkflow' => 'workflow/ArcanistUnitWorkflow.php', 'ArcanistUnnecessaryFinalModifierXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnnecessaryFinalModifierXHPASTLinterRule.php', + 'ArcanistUnnecessaryFinalModifierXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUnnecessaryFinalModifierXHPASTLinterRuleTestCase.php', 'ArcanistUnnecessarySemicolonXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnnecessarySemicolonXHPASTLinterRule.php', + 'ArcanistUnsafeDynamicStringXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnsafeDynamicStringXHPASTLinterRule.php', + 'ArcanistUnsafeDynamicStringXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUnsafeDynamicStringXHPASTLinterRuleTestCase.php', 'ArcanistUpgradeWorkflow' => 'workflow/ArcanistUpgradeWorkflow.php', 'ArcanistUploadWorkflow' => 'workflow/ArcanistUploadWorkflow.php', 'ArcanistUsageException' => 'exception/ArcanistUsageException.php', 'ArcanistUselessOverridingMethodXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUselessOverridingMethodXHPASTLinterRule.php', + 'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase.php', 'ArcanistUserAbortException' => 'exception/usage/ArcanistUserAbortException.php', 'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php', + 'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php', 'ArcanistVersionWorkflow' => 'workflow/ArcanistVersionWorkflow.php', 'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php', 'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php', 'ArcanistWorkingCopyIdentity' => 'workingcopyidentity/ArcanistWorkingCopyIdentity.php', 'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/ArcanistXHPASTLintNamingHook.php', 'ArcanistXHPASTLintNamingHookTestCase' => 'lint/linter/xhpast/__tests__/ArcanistXHPASTLintNamingHookTestCase.php', 'ArcanistXHPASTLintSwitchHook' => 'lint/linter/xhpast/ArcanistXHPASTLintSwitchHook.php', 'ArcanistXHPASTLinter' => 'lint/linter/ArcanistXHPASTLinter.php', 'ArcanistXHPASTLinterRule' => 'lint/linter/xhpast/ArcanistXHPASTLinterRule.php', - 'ArcanistXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/__tests__/ArcanistXHPASTLinterRuleTestCase.php', + 'ArcanistXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistXHPASTLinterRuleTestCase.php', 'ArcanistXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php', 'ArcanistXMLLinter' => 'lint/linter/ArcanistXMLLinter.php', 'ArcanistXMLLinterTestCase' => 'lint/linter/__tests__/ArcanistXMLLinterTestCase.php', 'ArcanistXUnitTestResultParser' => 'unit/parser/ArcanistXUnitTestResultParser.php', 'CSharpToolsTestEngine' => 'unit/engine/CSharpToolsTestEngine.php', 'NoseTestEngine' => 'unit/engine/NoseTestEngine.php', 'PhpunitTestEngine' => 'unit/engine/PhpunitTestEngine.php', 'PhpunitTestEngineTestCase' => 'unit/engine/__tests__/PhpunitTestEngineTestCase.php', 'PhutilTestCase' => 'unit/engine/phutil/PhutilTestCase.php', 'PhutilTestCaseTestCase' => 'unit/engine/phutil/testcase/PhutilTestCaseTestCase.php', 'PhutilTestSkippedException' => 'unit/engine/phutil/testcase/PhutilTestSkippedException.php', 'PhutilTestTerminatedException' => 'unit/engine/phutil/testcase/PhutilTestTerminatedException.php', 'PhutilUnitTestEngine' => 'unit/engine/PhutilUnitTestEngine.php', 'PhutilUnitTestEngineTestCase' => 'unit/engine/__tests__/PhutilUnitTestEngineTestCase.php', 'PytestTestEngine' => 'unit/engine/PytestTestEngine.php', 'XUnitTestEngine' => 'unit/engine/XUnitTestEngine.php', 'XUnitTestResultParserTestCase' => 'unit/parser/__tests__/XUnitTestResultParserTestCase.php', ), 'function' => array(), 'xmap' => array( 'ArcanistAliasFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistAliasWorkflow' => 'ArcanistWorkflow', 'ArcanistAmendWorkflow' => 'ArcanistWorkflow', 'ArcanistAnoidWorkflow' => 'ArcanistWorkflow', + 'ArcanistArrayCombineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistArrayCombineXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistArrayIndexSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistArraySeparatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistArraySeparatorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistArrayValueXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistArrayValueXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistBackoutWorkflow' => 'ArcanistWorkflow', 'ArcanistBaseCommitParser' => 'Phobject', 'ArcanistBaseCommitParserTestCase' => 'PhutilTestCase', 'ArcanistBaseXHPASTLinter' => 'ArcanistFutureLinter', 'ArcanistBinaryExpressionSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistBinaryExpressionSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistBlacklistedFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistBlacklistedFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistBookmarkWorkflow' => 'ArcanistFeatureWorkflow', 'ArcanistBraceFormattingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistBranchWorkflow' => 'ArcanistFeatureWorkflow', 'ArcanistBritishTestCase' => 'PhutilTestCase', 'ArcanistBrowseWorkflow' => 'ArcanistWorkflow', 'ArcanistBundle' => 'Phobject', 'ArcanistBundleTestCase' => 'PhutilTestCase', 'ArcanistCSSLintLinter' => 'ArcanistExternalLinter', 'ArcanistCSSLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistCSharpLinter' => 'ArcanistLinter', 'ArcanistCallConduitWorkflow' => 'ArcanistWorkflow', 'ArcanistCallParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistCallParenthesesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistCallTimePassByReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistCallTimePassByReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistCapabilityNotSupportedException' => 'Exception', 'ArcanistCastSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistCastSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistCheckstyleXMLLintRenderer' => 'ArcanistLintRenderer', 'ArcanistChmodLinter' => 'ArcanistLinter', 'ArcanistChmodLinterTestCase' => 'ArcanistLinterTestCase', + 'ArcanistClassExtendsObjectXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistClassExtendsObjectXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistClassFilenameMismatchXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistClassNameLiteralXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistClassNameLiteralXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistCloseRevisionWorkflow' => 'ArcanistWorkflow', 'ArcanistCloseWorkflow' => 'ArcanistWorkflow', 'ArcanistClosureLinter' => 'ArcanistExternalLinter', 'ArcanistClosureLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistCoffeeLintLinter' => 'ArcanistExternalLinter', 'ArcanistCoffeeLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistCommentRemover' => 'Phobject', 'ArcanistCommentRemoverTestCase' => 'PhutilTestCase', 'ArcanistCommentSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistCommentStyleXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistCommentStyleXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistCommitWorkflow' => 'ArcanistWorkflow', 'ArcanistCompilerLintRenderer' => 'ArcanistLintRenderer', 'ArcanistComposerLinter' => 'ArcanistLinter', 'ArcanistComprehensiveLintEngine' => 'ArcanistLintEngine', 'ArcanistConcatenationOperatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistConfiguration' => 'Phobject', 'ArcanistConfigurationDrivenLintEngine' => 'ArcanistLintEngine', 'ArcanistConfigurationDrivenUnitTestEngine' => 'ArcanistUnitTestEngine', 'ArcanistConfigurationManager' => 'Phobject', 'ArcanistConsoleLintRenderer' => 'ArcanistLintRenderer', 'ArcanistConstructorParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistConstructorParenthesesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistControlStatementSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistControlStatementSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistCoverWorkflow' => 'ArcanistWorkflow', 'ArcanistCppcheckLinter' => 'ArcanistExternalLinter', 'ArcanistCppcheckLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistCpplintLinter' => 'ArcanistExternalLinter', 'ArcanistCpplintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistDeclarationParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistDefaultParametersXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistDefaultParametersXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistDeprecationXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistDiffChange' => 'Phobject', 'ArcanistDiffChangeType' => 'Phobject', 'ArcanistDiffHunk' => 'Phobject', 'ArcanistDiffParser' => 'Phobject', 'ArcanistDiffParserTestCase' => 'PhutilTestCase', 'ArcanistDiffUtils' => 'Phobject', 'ArcanistDiffUtilsTestCase' => 'PhutilTestCase', 'ArcanistDiffWorkflow' => 'ArcanistWorkflow', 'ArcanistDifferentialCommitMessage' => 'Phobject', 'ArcanistDifferentialCommitMessageParserException' => 'Exception', 'ArcanistDifferentialDependencyGraph' => 'AbstractDirectedGraph', 'ArcanistDifferentialRevisionHash' => 'Phobject', 'ArcanistDifferentialRevisionStatus' => 'Phobject', 'ArcanistDoubleQuoteXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistDoubleQuoteXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistDownloadWorkflow' => 'ArcanistWorkflow', 'ArcanistDuplicateKeysInArrayXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistDuplicateKeysInArrayXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistDynamicDefineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistDynamicDefineXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistElseIfUsageXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistEmptyFileXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistEmptyStatementXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistEmptyStatementXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistEventType' => 'PhutilEventType', 'ArcanistExitExpressionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistExitExpressionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistExportWorkflow' => 'ArcanistWorkflow', 'ArcanistExternalLinter' => 'ArcanistFutureLinter', 'ArcanistExternalLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistExtractUseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistExtractUseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistFeatureWorkflow' => 'ArcanistWorkflow', 'ArcanistFileDataRef' => 'Phobject', 'ArcanistFileUploader' => 'Phobject', 'ArcanistFilenameLinter' => 'ArcanistLinter', 'ArcanistFilenameLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistFlagWorkflow' => 'ArcanistWorkflow', 'ArcanistFlake8Linter' => 'ArcanistExternalLinter', 'ArcanistFlake8LinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistFormattedStringXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistFormattedStringXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistFutureLinter' => 'ArcanistLinter', 'ArcanistGeneratedLinter' => 'ArcanistLinter', 'ArcanistGeneratedLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistGetConfigWorkflow' => 'ArcanistWorkflow', 'ArcanistGitAPI' => 'ArcanistRepositoryAPI', 'ArcanistGitLandEngine' => 'ArcanistLandEngine', 'ArcanistGitUpstreamPath' => 'Phobject', 'ArcanistGlobalVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistGlobalVariableXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistGoLintLinter' => 'ArcanistExternalLinter', 'ArcanistGoLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistGoTestResultParser' => 'ArcanistTestResultParser', 'ArcanistGoTestResultParserTestCase' => 'PhutilTestCase', 'ArcanistHLintLinter' => 'ArcanistExternalLinter', 'ArcanistHLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistHelpWorkflow' => 'ArcanistWorkflow', 'ArcanistHgClientChannel' => 'PhutilProtocolChannel', 'ArcanistHgProxyClient' => 'Phobject', 'ArcanistHgProxyServer' => 'Phobject', 'ArcanistHgServerChannel' => 'PhutilProtocolChannel', 'ArcanistImplicitConstructorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistImplicitFallthroughXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistImplicitFallthroughXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistImplicitVisibilityXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistImplicitVisibilityXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistInlineHTMLXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistInlineHTMLXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistInnerFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistInnerFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistInstallCertificateWorkflow' => 'ArcanistWorkflow', 'ArcanistInstanceOfOperatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistInstanceofOperatorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistInvalidDefaultParameterXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistInvalidDefaultParameterXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistInvalidModifiersXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistInvalidModifiersXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistJSHintLinter' => 'ArcanistExternalLinter', 'ArcanistJSHintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistJSONLintLinter' => 'ArcanistExternalLinter', 'ArcanistJSONLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistJSONLintRenderer' => 'ArcanistLintRenderer', 'ArcanistJSONLinter' => 'ArcanistLinter', 'ArcanistJSONLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistJscsLinter' => 'ArcanistExternalLinter', 'ArcanistJscsLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistKeywordCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistKeywordCasingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistLambdaFuncFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistLandEngine' => 'Phobject', 'ArcanistLandWorkflow' => 'ArcanistWorkflow', 'ArcanistLanguageConstructParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistLanguageConstructParenthesesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistLesscLinter' => 'ArcanistExternalLinter', 'ArcanistLesscLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistLiberateWorkflow' => 'ArcanistWorkflow', 'ArcanistLibraryTestCase' => 'PhutilLibraryTestCase', 'ArcanistLintEngine' => 'Phobject', 'ArcanistLintMessage' => 'Phobject', 'ArcanistLintPatcher' => 'Phobject', 'ArcanistLintRenderer' => 'Phobject', 'ArcanistLintResult' => 'Phobject', 'ArcanistLintSeverity' => 'Phobject', 'ArcanistLintWorkflow' => 'ArcanistWorkflow', 'ArcanistLinter' => 'Phobject', + 'ArcanistLinterStandard' => 'Phobject', + 'ArcanistLinterStandardTestCase' => 'PhutilTestCase', 'ArcanistLinterTestCase' => 'PhutilTestCase', 'ArcanistLintersWorkflow' => 'ArcanistWorkflow', 'ArcanistListAssignmentXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistListAssignmentXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistListWorkflow' => 'ArcanistWorkflow', 'ArcanistLogicalOperatorsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI', 'ArcanistMercurialParser' => 'Phobject', 'ArcanistMercurialParserTestCase' => 'PhutilTestCase', 'ArcanistMergeConflictLinter' => 'ArcanistLinter', 'ArcanistMergeConflictLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistMissingLinterException' => 'Exception', 'ArcanistModifierOrderingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistModifierOrderingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistNamingConventionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistNamingConventionsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistNestedNamespacesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistNestedNamespacesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistNewlineAfterOpenTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistNewlineAfterOpenTagXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistNoEffectException' => 'ArcanistUsageException', 'ArcanistNoEngineException' => 'ArcanistUsageException', 'ArcanistNoLintLinter' => 'ArcanistLinter', 'ArcanistNoLintLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistNoParentScopeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistNoneLintRenderer' => 'ArcanistLintRenderer', 'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistObjectOperatorSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistPEP8Linter' => 'ArcanistExternalLinter', 'ArcanistPEP8LinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistPHPCloseTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistPHPCompatibilityXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistPHPCompatibilityXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistPHPEchoTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistPHPEchoTagXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistPHPOpenTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistPHPOpenTagXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistPHPShortTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistPHPShortTagXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistParentMemberReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistParentMemberReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistParenthesesSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistParenthesesSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistParseStrUseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistParseStrUseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistPasteWorkflow' => 'ArcanistWorkflow', 'ArcanistPatchWorkflow' => 'ArcanistWorkflow', 'ArcanistPhpLinter' => 'ArcanistExternalLinter', 'ArcanistPhpLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistPhpcsLinter' => 'ArcanistExternalLinter', 'ArcanistPhpcsLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistPhpunitTestResultParser' => 'ArcanistTestResultParser', 'ArcanistPhrequentWorkflow' => 'ArcanistWorkflow', 'ArcanistPhutilLibraryLinter' => 'ArcanistLinter', - 'ArcanistPhutilXHPASTLinter' => 'ArcanistBaseXHPASTLinter', - 'ArcanistPhutilXHPASTLinterTestCase' => 'ArcanistLinterTestCase', + 'ArcanistPhutilXHPASTLinterStandard' => 'ArcanistLinterStandard', 'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistPregQuoteMisuseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistPregQuoteMisuseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistPuppetLintLinter' => 'ArcanistExternalLinter', 'ArcanistPuppetLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistPyFlakesLinter' => 'ArcanistExternalLinter', 'ArcanistPyFlakesLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistPyLintLinter' => 'ArcanistExternalLinter', 'ArcanistPyLintLinterTestCase' => 'ArcanistExternalLinterTestCase', + 'ArcanistRaggedClassTreeEdgeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistRepositoryAPI' => 'Phobject', 'ArcanistRepositoryAPIMiscTestCase' => 'PhutilTestCase', 'ArcanistRepositoryAPIStateTestCase' => 'PhutilTestCase', 'ArcanistReusedAsIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistReusedIteratorReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistReusedIteratorReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistReusedIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistReusedIteratorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistRevertWorkflow' => 'ArcanistWorkflow', 'ArcanistRuboCopLinter' => 'ArcanistExternalLinter', 'ArcanistRuboCopLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistRubyLinter' => 'ArcanistExternalLinter', 'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistScriptAndRegexLinter' => 'ArcanistLinter', 'ArcanistSelfMemberReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistSelfMemberReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistSemicolonSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistSemicolonSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistSetConfigWorkflow' => 'ArcanistWorkflow', 'ArcanistSettings' => 'Phobject', 'ArcanistShellCompleteWorkflow' => 'ArcanistWorkflow', 'ArcanistSingleLintEngine' => 'ArcanistLintEngine', 'ArcanistSlownessXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistSlownessXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistSpellingLinter' => 'ArcanistLinter', 'ArcanistSpellingLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistStartWorkflow' => 'ArcanistPhrequentWorkflow', 'ArcanistStaticThisXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistStaticThisXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistStopWorkflow' => 'ArcanistPhrequentWorkflow', 'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI', 'ArcanistSummaryLintRenderer' => 'ArcanistLintRenderer', 'ArcanistSyntaxErrorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistTasksWorkflow' => 'ArcanistWorkflow', 'ArcanistTautologicalExpressionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistTautologicalExpressionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistTestResultParser' => 'Phobject', 'ArcanistTestXHPASTLintSwitchHook' => 'ArcanistXHPASTLintSwitchHook', 'ArcanistTextLinter' => 'ArcanistLinter', 'ArcanistTextLinterTestCase' => 'ArcanistLinterTestCase', + 'ArcanistThisReassignmentXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistThisReassignmentXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistTimeWorkflow' => 'ArcanistPhrequentWorkflow', 'ArcanistToStringExceptionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistToStringExceptionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistTodoCommentXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistTodoCommentXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistTodoWorkflow' => 'ArcanistWorkflow', 'ArcanistUSEnglishTranslation' => 'PhutilTranslation', 'ArcanistUnableToParseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUnaryPrefixExpressionSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistUnaryPrefixExpressionSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUndeclaredVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistUndeclaredVariableXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistUnexpectedReturnValueXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUnitConsoleRenderer' => 'ArcanistUnitRenderer', 'ArcanistUnitRenderer' => 'Phobject', 'ArcanistUnitTestEngine' => 'Phobject', 'ArcanistUnitTestResult' => 'Phobject', 'ArcanistUnitTestResultTestCase' => 'PhutilTestCase', 'ArcanistUnitTestableLintEngine' => 'ArcanistLintEngine', 'ArcanistUnitWorkflow' => 'ArcanistWorkflow', 'ArcanistUnnecessaryFinalModifierXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistUnnecessaryFinalModifierXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUnnecessarySemicolonXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistUnsafeDynamicStringXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistUnsafeDynamicStringXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUpgradeWorkflow' => 'ArcanistWorkflow', 'ArcanistUploadWorkflow' => 'ArcanistWorkflow', 'ArcanistUsageException' => 'Exception', 'ArcanistUselessOverridingMethodXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUserAbortException' => 'ArcanistUsageException', 'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistVersionWorkflow' => 'ArcanistWorkflow', 'ArcanistWhichWorkflow' => 'ArcanistWorkflow', 'ArcanistWorkflow' => 'Phobject', 'ArcanistWorkingCopyIdentity' => 'Phobject', 'ArcanistXHPASTLintNamingHook' => 'Phobject', 'ArcanistXHPASTLintNamingHookTestCase' => 'PhutilTestCase', 'ArcanistXHPASTLintSwitchHook' => 'Phobject', 'ArcanistXHPASTLinter' => 'ArcanistBaseXHPASTLinter', 'ArcanistXHPASTLinterRule' => 'Phobject', - 'ArcanistXHPASTLinterRuleTestCase' => 'PhutilTestCase', + 'ArcanistXHPASTLinterRuleTestCase' => 'ArcanistLinterTestCase', 'ArcanistXHPASTLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistXMLLinter' => 'ArcanistLinter', 'ArcanistXMLLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistXUnitTestResultParser' => 'Phobject', 'CSharpToolsTestEngine' => 'XUnitTestEngine', 'NoseTestEngine' => 'ArcanistUnitTestEngine', 'PhpunitTestEngine' => 'ArcanistUnitTestEngine', 'PhpunitTestEngineTestCase' => 'PhutilTestCase', 'PhutilTestCase' => 'Phobject', 'PhutilTestCaseTestCase' => 'PhutilTestCase', 'PhutilTestSkippedException' => 'Exception', 'PhutilTestTerminatedException' => 'Exception', 'PhutilUnitTestEngine' => 'ArcanistUnitTestEngine', 'PhutilUnitTestEngineTestCase' => 'PhutilTestCase', 'PytestTestEngine' => 'ArcanistUnitTestEngine', 'XUnitTestEngine' => 'ArcanistUnitTestEngine', 'XUnitTestResultParserTestCase' => 'PhutilTestCase', ), )); diff --git a/src/land/ArcanistGitLandEngine.php b/src/land/ArcanistGitLandEngine.php index 95b33aa1..7256b050 100644 --- a/src/land/ArcanistGitLandEngine.php +++ b/src/land/ArcanistGitLandEngine.php @@ -1,532 +1,545 @@ verifySourceAndTargetExist(); $this->fetchTarget(); $this->printLandingCommits(); if ($this->getShouldPreview()) { $this->writeInfo( pht('PREVIEW'), pht('Completed preview of operation.')); return; } $this->saveLocalState(); try { $this->identifyRevision(); $this->updateWorkingCopy(); if ($this->getShouldHold()) { $this->writeInfo( pht('HOLD'), pht('Holding change locally, it has not been pushed.')); } else { $this->pushChange(); $this->reconcileLocalState(); $api = $this->getRepositoryAPI(); $api->execxLocal('submodule update --init --recursive'); if ($this->getShouldKeep()) { echo tsprintf( "%s\n", pht('Keeping local branch.')); } else { $this->destroyLocalBranch(); } $this->writeOkay( pht('DONE'), pht('Landed changes.')); } $this->restoreWhenDestroyed = false; } catch (Exception $ex) { $this->restoreLocalState(); throw $ex; } } public function __destruct() { if ($this->restoreWhenDestroyed) { $this->writeWARN( pht('INTERRUPTED!'), pht('Restoring working copy to its original state.')); $this->restoreLocalState(); } } protected function getLandingCommits() { $api = $this->getRepositoryAPI(); list($out) = $api->execxLocal( 'log --oneline %s..%s --', $this->getTargetFullRef(), $this->sourceCommit); $out = trim($out); if (!strlen($out)) { return array(); } else { return phutil_split_lines($out, false); } } private function identifyRevision() { $api = $this->getRepositoryAPI(); $api->execxLocal('checkout %s --', $this->getSourceRef()); call_user_func($this->getBuildMessageCallback(), $this); } private function verifySourceAndTargetExist() { $api = $this->getRepositoryAPI(); list($err) = $api->execManualLocal( 'rev-parse --verify %s', $this->getTargetFullRef()); if ($err) { throw new Exception( pht( 'Branch "%s" does not exist in remote "%s".', $this->getTargetOnto(), $this->getTargetRemote())); } list($err, $stdout) = $api->execManualLocal( 'rev-parse --verify %s', $this->getSourceRef()); if ($err) { throw new Exception( pht( 'Branch "%s" does not exist in the local working copy.', $this->getSourceRef())); } $this->sourceCommit = trim($stdout); } private function fetchTarget() { $api = $this->getRepositoryAPI(); $ref = $this->getTargetFullRef(); $this->writeInfo( pht('FETCH'), pht('Fetching %s...', $ref)); $api->execxLocal( 'fetch -- %s %s', $this->getTargetRemote(), $this->getTargetOnto()); } private function updateWorkingCopy() { $api = $this->getRepositoryAPI(); $source = $this->sourceCommit; $api->execxLocal( 'checkout %s --', $this->getTargetFullRef()); list($original_author, $original_date) = $this->getAuthorAndDate($source); try { if ($this->getShouldSquash()) { // NOTE: We're explicitly specifying "--ff" to override the presence // of "merge.ff" options in user configuration. $api->execxLocal( 'merge --no-stat --no-commit --ff --squash -- %s', $source); } else { $api->execxLocal( 'merge --no-stat --no-commit --no-ff -- %s', $source); } } catch (Exception $ex) { $api->execManualLocal('merge --abort'); $api->execManualLocal('reset --hard HEAD --'); throw new Exception( pht( 'Local "%s" does not merge cleanly into "%s". Merge or rebase '. 'local changes so they can merge cleanly.', $this->getSourceRef(), $this->getTargetFullRef())); } list($changes) = $api->execxLocal('diff HEAD --'); $changes = trim($changes); if (!strlen($changes)) { throw new Exception( pht( 'Merging local "%s" into "%s" produces an empty diff. '. 'This usually means these changes have already landed.', $this->getSourceRef(), $this->getTargetFullRef())); } $api->execxLocal( 'commit --author %s --date %s -F %s --', $original_author, $original_date, $this->getCommitMessageFile()); $this->getWorkflow()->didCommitMerge(); list($stdout) = $api->execxLocal( 'rev-parse --verify %s', 'HEAD'); $this->mergedRef = trim($stdout); } private function pushChange() { $api = $this->getRepositoryAPI(); $this->writeInfo( pht('PUSHING'), pht('Pushing changes to "%s".', $this->getTargetFullRef())); $err = $api->execPassthru( 'push -- %s %s:%s', $this->getTargetRemote(), $this->mergedRef, $this->getTargetOnto()); if ($err) { throw new ArcanistUsageException( pht( 'Push failed! Fix the error and run "%s" again.', 'arc land')); } } private function reconcileLocalState() { $api = $this->getRepositoryAPI(); // Try to put the user into the best final state we can. This is very // complicated because users are incredibly creative and their local // branches may have the same names as branches in the remote but no // relationship to them. if ($this->localRef != $this->getSourceRef()) { // The user ran `arc land X` but was on a different branch, so just put // them back wherever they were before. $this->writeInfo( pht('RESTORE'), pht('Switching back to "%s".', $this->localRef)); $this->restoreLocalState(); return; } // We're going to try to find a path to the upstream target branch. We // try in two different ways: // // - follow the source branch directly along tracking branches until // we reach the upstream; or // - follow a local branch with the same name as the target branch until // we reach the upstream. // First, get the path from whatever we landed to wherever it goes. $local_branch = $this->getSourceRef(); $path = $api->getPathToUpstream($local_branch); if ($path->getLength()) { // We may want to discard the thing we landed from the path, if we're // going to delete it. In this case, we don't want to update it or worry // if it's dirty. if ($this->getSourceRef() == $this->getTargetOnto()) { // In this case, we've done something like land "master" onto itself, // so we do want to update the actual branch. We're going to use the // entire path. } else { // Otherwise, we're going to delete the branch at the end of the // workflow, so throw it away the most-local branch that isn't long // for this world. $path->removeUpstream($local_branch); if (!$path->getLength()) { - $this->writeInfo( - pht('UPDATE'), - pht( - 'Local branch "%s" directly tracks remote, staying on '. - 'detached HEAD.', - $local_branch)); - return; + // The local branch tracked upstream directly; however, it + // may not be the only one to do so. If there's a local + // branch of the same name that tracks the remote, try + // switching to that. + $local_branch = $this->getTargetOnto(); + list($err) = $api->execManualLocal( + 'rev-parse --verify %s', + $local_branch); + if (!$err) { + $path = $api->getPathToUpstream($local_branch); + } + if (!$path->isConnectedToRemote()) { + $this->writeInfo( + pht('UPDATE'), + pht( + 'Local branch "%s" directly tracks remote, staying on '. + 'detached HEAD.', + $local_branch)); + return; + } } $local_branch = head($path->getLocalBranches()); } } else { // The source branch has no upstream, so look for a local branch with // the same name as the target branch. This corresponds to the common // case where you have "master" and checkout local branches from it // with "git checkout -b feature", then land onto "master". $local_branch = $this->getTargetOnto(); list($err) = $api->execManualLocal( 'rev-parse --verify %s', $local_branch); if ($err) { $this->writeInfo( pht('UPDATE'), pht( 'Local branch "%s" does not exist, staying on detached HEAD.', $local_branch)); return; } $path = $api->getPathToUpstream($local_branch); } if ($path->getCycle()) { $this->writeWarn( pht('LOCAL CYCLE'), pht( 'Local branch "%s" tracks an upstream but following it leads to '. 'a local cycle, staying on detached HEAD.', $local_branch)); return; } if (!$path->isConnectedToRemote()) { $this->writeInfo( pht('UPDATE'), pht( 'Local branch "%s" is not connected to a remote, staying on '. 'detached HEAD.', $local_branch)); return; } $remote_remote = $path->getRemoteRemoteName(); $remote_branch = $path->getRemoteBranchName(); $remote_actual = $remote_remote.'/'.$remote_branch; $remote_expect = $this->getTargetFullRef(); if ($remote_actual != $remote_expect) { $this->writeInfo( pht('UPDATE'), pht( 'Local branch "%s" is connected to a remote ("%s") other than '. 'the target remote ("%s"), staying on detached HEAD.', $local_branch, $remote_actual, $remote_expect)); return; } // If we get this far, we have a sequence of branches which ultimately // connect to the remote. We're going to try to update them all in reverse // order, from most-upstream to most-local. $cascade_branches = $path->getLocalBranches(); $cascade_branches = array_reverse($cascade_branches); // First, check if any of them are ahead of the remote. $ahead_of_remote = array(); foreach ($cascade_branches as $cascade_branch) { list($stdout) = $api->execxLocal( 'log %s..%s --', $this->mergedRef, $cascade_branch); $stdout = trim($stdout); if (strlen($stdout)) { $ahead_of_remote[$cascade_branch] = $cascade_branch; } } // We're going to handle the last branch (the thing we ultimately intend // to check out) differently. It's OK if it's ahead of the remote, as long // as we just landed it. $local_ahead = isset($ahead_of_remote[$local_branch]); unset($ahead_of_remote[$local_branch]); $land_self = ($this->getTargetOnto() === $this->getSourceRef()); // We aren't going to pull anything if anything upstream from us is ahead // of the remote, or the local is ahead of the remote and we didn't land // it onto itself. $skip_pull = ($ahead_of_remote || ($local_ahead && !$land_self)); if ($skip_pull) { $this->writeInfo( pht('UPDATE'), pht( 'Local "%s" is ahead of remote "%s". Checking out "%s" but '. 'not pulling changes.', nonempty(head($ahead_of_remote), $local_branch), $this->getTargetFullRef(), $local_branch)); $this->writeInfo( pht('CHECKOUT'), pht( 'Checking out "%s".', $local_branch)); $api->execxLocal('checkout %s --', $local_branch); return; } // If nothing upstream from our nearest branch is ahead of the remote, // pull it all. $cascade_targets = array(); if (!$ahead_of_remote) { foreach ($cascade_branches as $cascade_branch) { if ($local_ahead && ($local_branch == $cascade_branch)) { continue; } $cascade_targets[] = $cascade_branch; } } if ($cascade_targets) { $this->writeInfo( pht('UPDATE'), pht( 'Local "%s" tracks target remote "%s", checking out and '. 'pulling changes.', $local_branch, $this->getTargetFullRef())); foreach ($cascade_targets as $cascade_branch) { $this->writeInfo( pht('PULL'), pht( 'Checking out and pulling "%s".', $cascade_branch)); $api->execxLocal('checkout %s --', $cascade_branch); $api->execxLocal('pull --'); } if (!$local_ahead) { return; } } // In this case, the user did something like land a branch onto itself, // and the branch is tracking the correct remote. We're going to discard // the local state and reset it to the state we just pushed. $this->writeInfo( pht('RESET'), pht( 'Local "%s" landed into remote "%s", resetting local branch to '. 'remote state.', $this->getTargetOnto(), $this->getTargetFullRef())); $api->execxLocal('checkout %s --', $local_branch); $api->execxLocal('reset --hard %s --', $this->getTargetFullRef()); return; } private function destroyLocalBranch() { $api = $this->getRepositoryAPI(); if ($this->getSourceRef() == $this->getTargetOnto()) { // If we landed a branch into a branch with the same name, so don't // destroy it. This prevents us from cleaning up "master" if you're // landing master into itself. return; } // TODO: Maybe this should also recover the proper upstream? $recovery_command = csprintf( 'git checkout -b %R %R', $this->getSourceRef(), $this->sourceCommit); echo tsprintf( "%s\n", pht('Cleaning up branch "%s"...', $this->getSourceRef())); echo tsprintf( "%s\n", pht('(Use `%s` if you want it back.)', $recovery_command)); $api->execxLocal('branch -D -- %s', $this->getSourceRef()); } /** * Save the local working copy state so we can restore it later. */ private function saveLocalState() { $api = $this->getRepositoryAPI(); $this->localCommit = $api->getWorkingCopyRevision(); list($ref) = $api->execxLocal('rev-parse --abbrev-ref HEAD'); $ref = trim($ref); if ($ref === 'HEAD') { $ref = $this->localCommit; } $this->localRef = $ref; $this->restoreWhenDestroyed = true; } /** * Restore the working copy to the state it was in before we started * performing writes. */ private function restoreLocalState() { $api = $this->getRepositoryAPI(); $api->execxLocal('checkout %s --', $this->localRef); $api->execxLocal('reset --hard %s --', $this->localCommit); $api->execxLocal('submodule update --init --recursive'); $this->restoreWhenDestroyed = false; } private function getTargetFullRef() { return $this->getTargetRemote().'/'.$this->getTargetOnto(); } private function getAuthorAndDate($commit) { $api = $this->getRepositoryAPI(); // TODO: This is working around Windows escaping problems, see T8298. list($info) = $api->execxLocal( 'log -n1 --format=%C %s --', '%aD%n%an%n%ae', $commit); $info = trim($info); list($date, $author, $email) = explode("\n", $info, 3); return array( "$author <{$email}>", $date, ); } } diff --git a/src/lint/linter/ArcanistCSSLintLinter.php b/src/lint/linter/ArcanistCSSLintLinter.php index a7583959..e8fd3546 100644 --- a/src/lint/linter/ArcanistCSSLintLinter.php +++ b/src/lint/linter/ArcanistCSSLintLinter.php @@ -1,116 +1,127 @@ getExecutableCommand()); $matches = array(); if (preg_match('/^v(?P\d+\.\d+\.\d+)$/', $stdout, $matches)) { return $matches['version']; } else { return false; } } public function getInstallInstructions() { return pht( 'Install %s using `%s`.', 'CSSLint', 'npm install -g csslint'); } protected function parseLinterOutput($path, $err, $stdout, $stderr) { $report_dom = new DOMDocument(); $ok = @$report_dom->loadXML($stdout); if (!$ok) { return false; } $files = $report_dom->getElementsByTagName('file'); $messages = array(); foreach ($files as $file) { foreach ($file->childNodes as $child) { + $line = $child->getAttribute('line'); + $char = $child->getAttribute('char'); + $original_text = $child->getAttribute('evidence'); + + if ($line === '') { + $line = null; + } + + if ($char === '') { + $char = null; + } else { + $original_text = substr($original_text, $char - 1); + } + $message = id(new ArcanistLintMessage()) ->setPath($path) - ->setLine($child->getAttribute('line')) - ->setChar($child->getAttribute('char')) + ->setLine($line) + ->setChar($char) ->setCode($this->getLinterName()) ->setName($this->getLinterName()) ->setDescription($child->getAttribute('reason')) - ->setOriginalText( - substr( - $child->getAttribute('evidence'), - $child->getAttribute('char') - 1)); + ->setOriginalText($original_text); switch ($child->getAttribute('severity')) { case 'error': $message->setSeverity(ArcanistLintSeverity::SEVERITY_ERROR); break; case 'warning': $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); break; default: $message->setSeverity(ArcanistLintSeverity::SEVERITY_ERROR); break; } $messages[] = $message; } } return $messages; } protected function getLintCodeFromLinterConfigurationKey($code) { // NOTE: We can't figure out which rule generated each message, so we // can not customize severities. I opened a pull request to add this // ability; see: // // https://github.com/stubbornella/csslint/pull/409 throw new Exception( pht( "%s does not currently support custom severity levels, because ". "rules can't be identified from messages in output.", 'CSSLint')); } } diff --git a/src/lint/linter/ArcanistLinter.php b/src/lint/linter/ArcanistLinter.php index d4ccb0d6..292d4438 100644 --- a/src/lint/linter/ArcanistLinter.php +++ b/src/lint/linter/ArcanistLinter.php @@ -1,590 +1,614 @@ getLinterName(), $this->getLinterConfigurationName(), get_class($this)); } /* -( Runtime State )------------------------------------------------------ */ /** * @task state */ final public function getActivePath() { return $this->activePath; } /** * @task state */ final public function setActivePath($path) { $this->stopAllLinters = false; $this->activePath = $path; return $this; } /** * @task state */ final public function setEngine(ArcanistLintEngine $engine) { $this->engine = $engine; return $this; } /** * @task state */ final protected function getEngine() { return $this->engine; } /** * Set the internal ID for this linter. * * This ID is assigned automatically by the @{class:ArcanistLintEngine}. * * @param string Unique linter ID. * @return this * @task state */ final public function setLinterID($id) { $this->id = $id; return $this; } /** * Get the internal ID for this linter. * * Retrieves an internal linter ID managed by the @{class:ArcanistLintEngine}. * This ID is a unique scalar which distinguishes linters in a list. * * @return string Unique linter ID. * @task state */ final public function getLinterID() { return $this->id; } /* -( Executing Linters )-------------------------------------------------- */ /** * Hook called before a list of paths are linted. * * Parallelizable linters can start multiple requests in parallel here, * to improve performance. They can implement @{method:didLintPaths} to * collect results. * * Linters which are not parallelizable should normally ignore this callback * and implement @{method:lintPath} instead. * * @param list A list of paths to be linted * @return void * @task exec */ public function willLintPaths(array $paths) { return; } /** * Hook called for each path to be linted. * * Linters which are not parallelizable can do work here. * * Linters which are parallelizable may want to ignore this callback and * implement @{method:willLintPaths} and @{method:didLintPaths} instead. * * @param string Path to lint. * @return void * @task exec */ public function lintPath($path) { return; } /** * Hook called after a list of paths are linted. * * Parallelizable linters can collect results here. * * Linters which are not paralleizable should normally ignore this callback * and implement @{method:lintPath} instead. * * @param list A list of paths which were linted. * @return void * @task exec */ public function didLintPaths(array $paths) { return; } - public function getLinterPriority() { return 1.0; } /** * TODO: This should be `final`. */ public function setCustomSeverityMap(array $map) { $this->customSeverityMap = $map; return $this; } + public function addCustomSeverityMap(array $map) { + $this->customSeverityMap = $this->customSeverityMap + $map; + return $this; + } + final public function setCustomSeverityRules(array $rules) { $this->customSeverityRules = $rules; return $this; } final public function getProjectRoot() { $engine = $this->getEngine(); if (!$engine) { throw new Exception( pht( 'You must call %s before you can call %s.', 'setEngine()', __FUNCTION__.'()')); } $working_copy = $engine->getWorkingCopy(); if (!$working_copy) { return null; } return $working_copy->getProjectRoot(); } final 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, ); } final public function stopAllLinters() { $this->stopAllLinters = true; return $this; } final public function didStopAllLinters() { return $this->stopAllLinters; } final public function addPath($path) { $this->paths[$path] = $path; $this->filteredPaths = null; return $this; } final public function setPaths(array $paths) { $this->paths = $paths; $this->filteredPaths = null; 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). * * @param list * @return list */ final private function filterPaths(array $paths) { $engine = $this->getEngine(); $keep = array(); foreach ($paths as $path) { if (!$this->shouldLintDeletedFiles() && !$engine->pathExists($path)) { continue; } if (!$this->shouldLintDirectories() && $engine->isDirectory($path)) { continue; } if (!$this->shouldLintBinaryFiles() && $engine->isBinaryFile($path)) { continue; } if (!$this->shouldLintSymbolicLinks() && $engine->isSymbolicLink($path)) { continue; } $keep[] = $path; } return $keep; } final public function getPaths() { if ($this->filteredPaths === null) { $this->filteredPaths = $this->filterPaths(array_values($this->paths)); } return $this->filteredPaths; } final public function addData($path, $data) { $this->data[$path] = $data; return $this; } final protected function getData($path) { if (!array_key_exists($path, $this->data)) { $this->data[$path] = $this->getEngine()->loadData($path); } return $this->data[$path]; } public function getCacheVersion() { return 0; } final public function getLintMessageFullCode($short_code) { return $this->getLinterName().$short_code; } final public function getLintMessageSeverity($code) { $map = $this->customSeverityMap; if (isset($map[$code])) { return $map[$code]; } foreach ($this->customSeverityRules as $rule => $severity) { if (preg_match($rule, $code)) { return $severity; } } $map = $this->getLintSeverityMap(); if (isset($map[$code])) { return $map[$code]; } return $this->getDefaultMessageSeverity($code); } protected function getDefaultMessageSeverity($code) { return ArcanistLintSeverity::SEVERITY_ERROR; } final public function isMessageEnabled($code) { return ($this->getLintMessageSeverity($code) !== ArcanistLintSeverity::SEVERITY_DISABLED); } final public function getLintMessageName($code) { $map = $this->getLintNameMap(); if (isset($map[$code])) { return $map[$code]; } return pht('Unknown lint message!'); } final protected function addLintMessage(ArcanistLintMessage $message) { $root = $this->getProjectRoot(); $path = Filesystem::resolvePath($message->getPath(), $root); $message->setPath(Filesystem::readablePath($path, $root)); $this->messages[] = $message; return $message; } final public function getLintMessages() { return $this->messages; } final public function raiseLintAtLine( $line, $char, $code, - $desc, + $description, $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) + ->setDescription($description) ->setOriginalText($original) ->setReplacementText($replacement); return $this->addLintMessage($message); } final public function raiseLintAtPath($code, $desc) { return $this->raiseLintAtLine(null, null, $code, $desc, null, null); } final public function raiseLintAtOffset( $offset, $code, - $desc, + $description, $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, + $description, $original, $replacement); } public function canRun() { return true; } abstract public function getLinterName(); public function getVersion() { return null; } final 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; } /** * 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() { if (!$this->canCustomizeLintSeverities()) { return array(); } return array( 'severity' => array( 'type' => 'optional map', 'help' => pht( 'Provide a map from lint codes to adjusted severity levels: error, '. 'warning, advice, autofix or disabled.'), ), 'severity.rules' => array( 'type' => 'optional map', 'help' => pht( 'Provide a map of regular expressions to severity levels. All '. 'matching codes have their severity adjusted.'), ), + 'standard' => array( + 'type' => 'optional string | list', + 'help' => pht('The coding standard(s) to apply.'), + ), ); } public function setLinterConfigurationValue($key, $value) { $sev_map = array( 'error' => ArcanistLintSeverity::SEVERITY_ERROR, 'warning' => ArcanistLintSeverity::SEVERITY_WARNING, 'autofix' => ArcanistLintSeverity::SEVERITY_AUTOFIX, 'advice' => ArcanistLintSeverity::SEVERITY_ADVICE, 'disabled' => ArcanistLintSeverity::SEVERITY_DISABLED, ); switch ($key) { case 'severity': if (!$this->canCustomizeLintSeverities()) { break; } $custom = array(); foreach ($value as $code => $severity) { if (empty($sev_map[$severity])) { $valid = implode(', ', array_keys($sev_map)); throw new Exception( pht( 'Unknown lint severity "%s". Valid severities are: %s.', $severity, $valid)); } $code = $this->getLintCodeFromLinterConfigurationKey($code); $custom[$code] = $severity; } $this->setCustomSeverityMap($custom); return; case 'severity.rules': if (!$this->canCustomizeLintSeverities()) { break; } foreach ($value as $rule => $severity) { if (@preg_match($rule, '') === false) { throw new Exception( pht( 'Severity rule "%s" is not a valid regular expression.', $rule)); } if (empty($sev_map[$severity])) { $valid = implode(', ', array_keys($sev_map)); throw new Exception( pht( 'Unknown lint severity "%s". Valid severities are: %s.', $severity, $valid)); } } $this->setCustomSeverityRules($value); return; + + case 'standard': + $standards = (array)$value; + + foreach ($standards as $standard) { + $standard = ArcanistLinterStandard::getStandard($value, $this); + + foreach ($standard->getLinterConfiguration() as $k => $v) { + $this->setLinterConfigurationValue($k, $v); + } + $this->addCustomSeverityMap($standard->getLinterSeverityMap()); + } + + return; + + } throw new Exception(pht('Incomplete implementation: %s!', $key)); } protected function canCustomizeLintSeverities() { return true; } protected function shouldLintBinaryFiles() { return false; } protected function shouldLintDeletedFiles() { return false; } protected function shouldLintDirectories() { return false; } protected function shouldLintSymbolicLinks() { return false; } /** * Map a configuration lint code to an `arc` lint code. Primarily, this is * intended for validation, but can also be used to normalize case or * otherwise be more permissive in accepted inputs. * * If the code is not recognized, you should throw an exception. * * @param string Code specified in configuration. * @return string Normalized code to use in severity map. */ protected function getLintCodeFromLinterConfigurationKey($code) { return $code; } } diff --git a/src/lint/linter/ArcanistPhutilXHPASTLinter.php b/src/lint/linter/ArcanistPhutilXHPASTLinter.php deleted file mode 100644 index 7607b5f2..00000000 --- a/src/lint/linter/ArcanistPhutilXHPASTLinter.php +++ /dev/null @@ -1,336 +0,0 @@ - - pht('%s Unreliable', 'array_combine()'), - self::LINT_DEPRECATED_FUNCTION => - pht('Use of Deprecated Function'), - self::LINT_UNSAFE_DYNAMIC_STRING => - pht('Unsafe Usage of Dynamic String'), - self::LINT_RAGGED_CLASSTREE_EDGE => - pht('Class Not %s Or %s', 'abstract', 'final'), - self::LINT_EXTENDS_PHOBJECT => - pht('Class Not Extending %s', 'Phobject'), - ); - } - - public function getLinterName() { - return 'PHLXHP'; - } - - public function getLinterConfigurationName() { - return 'phutil-xhpast'; - } - - public function getLintSeverityMap() { - $advice = ArcanistLintSeverity::SEVERITY_ADVICE; - $warning = ArcanistLintSeverity::SEVERITY_WARNING; - - return array( - self::LINT_ARRAY_COMBINE => $warning, - self::LINT_DEPRECATED_FUNCTION => $warning, - self::LINT_UNSAFE_DYNAMIC_STRING => $warning, - self::LINT_RAGGED_CLASSTREE_EDGE => $warning, - self::LINT_EXTENDS_PHOBJECT => $advice, - ); - } - - public function getLinterConfigurationOptions() { - $options = array( - 'phutil-xhpast.deprecated.functions' => array( - 'type' => 'optional map', - 'help' => pht( - 'Functions which should should be considered deprecated.'), - ), - 'phutil-xhpast.dynamic-string.functions' => array( - 'type' => 'optional map', - 'help' => pht( - 'Functions which should should not be used because they represent '. - 'the unsafe usage of dynamic strings.'), - ), - 'phutil-xhpast.dynamic-string.classes' => array( - 'type' => 'optional map', - 'help' => pht( - 'Classes which should should not be used because they represent the '. - 'unsafe usage of dynamic strings.'), - ), - ); - - return $options + parent::getLinterConfigurationOptions(); - } - - public function setLinterConfigurationValue($key, $value) { - switch ($key) { - case 'phutil-xhpast.deprecated.functions': - $this->setDeprecatedFunctions($value); - return; - case 'phutil-xhpast.dynamic-string.functions': - $this->setDynamicStringFunctions($value); - return; - case 'phutil-xhpast.dynamic-string.classes': - $this->setDynamicStringClasses($value); - return; - default: - parent::setLinterConfigurationValue($key, $value); - return; - } - } - - public function getVersion() { - // The version number should be incremented whenever a new rule is added. - return '3'; - } - - protected function resolveFuture($path, Future $future) { - $tree = $this->getXHPASTLinter()->getXHPASTTreeForPath($path); - if (!$tree) { - return; - } - - $root = $tree->getRootNode(); - - $method_codes = array( - 'lintArrayCombine' => self::LINT_ARRAY_COMBINE, - 'lintUnsafeDynamicString' => self::LINT_UNSAFE_DYNAMIC_STRING, - 'lintDeprecatedFunctions' => self::LINT_DEPRECATED_FUNCTION, - 'lintRaggedClasstreeEdges' => self::LINT_RAGGED_CLASSTREE_EDGE, - 'lintClassExtendsPhobject' => self::LINT_EXTENDS_PHOBJECT, - ); - - foreach ($method_codes as $method => $codes) { - foreach ((array)$codes as $code) { - if ($this->isCodeEnabled($code)) { - call_user_func(array($this, $method), $root); - break; - } - } - } - } - - -/* -( Setters )------------------------------------------------------------ */ - - public function setDeprecatedFunctions(array $map) { - $this->deprecatedFunctions = $map; - return $this; - } - - public function setDynamicStringClasses(array $map) { - $this->dynamicStringClasses = $map; - return $this; - } - - public function setDynamicStringFunctions(array $map) { - $this->dynamicStringFunctions = $map; - return $this; - } - - -/* -( Linter Rules )------------------------------------------------------- */ - - private function lintUnsafeDynamicString(XHPASTNode $root) { - $safe = $this->dynamicStringFunctions + array( - 'pht' => 0, - - 'hsprintf' => 0, - 'jsprintf' => 0, - - 'hgsprintf' => 0, - - 'csprintf' => 0, - 'vcsprintf' => 0, - 'execx' => 0, - 'exec_manual' => 0, - 'phutil_passthru' => 0, - - 'qsprintf' => 1, - 'vqsprintf' => 1, - 'queryfx' => 1, - 'queryfx_all' => 1, - 'queryfx_one' => 1, - ); - - $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); - $this->lintUnsafeDynamicStringCall($calls, $safe); - - $safe = $this->dynamicStringClasses + array( - 'ExecFuture' => 0, - ); - - $news = $root->selectDescendantsOfType('n_NEW'); - $this->lintUnsafeDynamicStringCall($news, $safe); - } - - private function lintUnsafeDynamicStringCall( - AASTNodeList $calls, - array $safe) { - - $safe = array_combine( - array_map('strtolower', array_keys($safe)), - $safe); - - foreach ($calls as $call) { - $name = $call->getChildByIndex(0)->getConcreteString(); - $param = idx($safe, strtolower($name)); - - if ($param === null) { - continue; - } - - $parameters = $call->getChildByIndex(1); - if (count($parameters->getChildren()) <= $param) { - continue; - } - - $identifier = $parameters->getChildByIndex($param); - if (!$identifier->isConstantString()) { - $this->raiseLintAtNode( - $call, - self::LINT_UNSAFE_DYNAMIC_STRING, - pht( - "Parameter %d of %s should be a scalar string, ". - "otherwise it's not safe.", - $param + 1, - $name.'()')); - } - } - } - - private function lintArrayCombine(XHPASTNode $root) { - $function_calls = $this->getFunctionCalls($root, array('array_combine')); - - foreach ($function_calls as $call) { - $name = $call->getChildByIndex(0)->getConcreteString(); - $parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); - - if (count($parameter_list->getChildren()) !== 2) { - // Wrong number of parameters, but raise that elsewhere if we want. - continue; - } - - $first = $parameter_list->getChildByIndex(0); - $second = $parameter_list->getChildByIndex(1); - - if ($first->getConcreteString() == $second->getConcreteString()) { - $this->raiseLintAtNode( - $call, - self::LINT_ARRAY_COMBINE, - pht( - 'Prior to PHP 5.4, `%s` fails when given empty arrays. '. - 'Prefer to write `%s` as `%s`.', - 'array_combine()', - 'array_combine(x, x)', - 'array_fuse(x)')); - } - } - } - - private function lintDeprecatedFunctions(XHPASTNode $root) { - $map = $this->deprecatedFunctions; - $function_calls = $this->getFunctionCalls($root, array_keys($map)); - - foreach ($function_calls as $call) { - $name = $call - ->getChildByIndex(0) - ->getConcreteString(); - - $name = strtolower($name); - if (empty($map[$name])) { - continue; - } - - $this->raiseLintAtNode( - $call, - self::LINT_DEPRECATED_FUNCTION, - $map[$name]); - } - } - - private function lintRaggedClasstreeEdges(XHPASTNode $root) { - $parser = new PhutilDocblockParser(); - - $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); - foreach ($classes as $class) { - $is_final = false; - $is_abstract = false; - $is_concrete_extensible = false; - - $attributes = $class->getChildOfType(0, 'n_CLASS_ATTRIBUTES'); - foreach ($attributes->getChildren() as $child) { - if ($child->getConcreteString() == 'final') { - $is_final = true; - } - if ($child->getConcreteString() == 'abstract') { - $is_abstract = true; - } - } - - $docblock = $class->getDocblockToken(); - if ($docblock) { - list($text, $specials) = $parser->parse($docblock->getValue()); - $is_concrete_extensible = idx($specials, 'concrete-extensible'); - } - - if (!$is_final && !$is_abstract && !$is_concrete_extensible) { - $this->raiseLintAtNode( - $class->getChildOfType(1, 'n_CLASS_NAME'), - self::LINT_RAGGED_CLASSTREE_EDGE, - pht( - "This class is neither '%s' nor '%s', and does not have ". - "a docblock marking it '%s'.", - 'final', - 'abstract', - '@concrete-extensible')); - } - } - } - - private function lintClassExtendsPhobject(XHPASTNode $root) { - $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); - - foreach ($classes as $class) { - // TODO: This doesn't quite work for namespaced classes (see T8534). - $name = $class->getChildOfType(1, 'n_CLASS_NAME'); - $extends = $class->getChildByIndex(2); - - if ($name->getConcreteString() == 'Phobject') { - continue; - } - - if ($extends->getTypeName() == 'n_EMPTY') { - $this->raiseLintAtNode( - $class, - self::LINT_EXTENDS_PHOBJECT, - pht( - 'Classes should extend from %s or from some other class. '. - 'All classes (except for %s itself) should have a base class.', - 'Phobject', - 'Phobject')); - } - } - } - -} diff --git a/src/lint/linter/ArcanistRubyLinter.php b/src/lint/linter/ArcanistRubyLinter.php index bb29f381..df4b0503 100644 --- a/src/lint/linter/ArcanistRubyLinter.php +++ b/src/lint/linter/ArcanistRubyLinter.php @@ -1,87 +1,87 @@ getExecutableCommand()); $matches = array(); - $regex = '/^ruby (?P\d+\.\d+\.\d+)p\d+/'; + $regex = '/^ruby (?P\d+\.\d+\.\d+)+/'; if (preg_match($regex, $stdout, $matches)) { return $matches['version']; } else { return false; } } public function getInstallInstructions() { return pht('Install `%s` from <%s>.', 'ruby', 'http://www.ruby-lang.org/'); } protected function getMandatoryFlags() { // -w: turn on warnings // -c: check syntax return array('-w', '-c'); } protected function parseLinterOutput($path, $err, $stdout, $stderr) { $lines = phutil_split_lines($stderr, false); $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->setCode($this->getLinterName()); $message->setName(pht('Syntax Error')); $message->setDescription($matches[3]); $message->setSeverity($this->getLintMessageSeverity($code)); $messages[] = $message; } return $messages; } } diff --git a/src/lint/linter/ArcanistSpellingLinter.php b/src/lint/linter/ArcanistSpellingLinter.php index f27bc906..afbdbf8f 100644 --- a/src/lint/linter/ArcanistSpellingLinter.php +++ b/src/lint/linter/ArcanistSpellingLinter.php @@ -1,182 +1,184 @@ array( 'type' => 'optional list', 'help' => pht('Pass in custom dictionaries.'), ), ); return $options + parent::getLinterConfigurationOptions(); } public function setLinterConfigurationValue($key, $value) { switch ($key) { case 'spelling.dictionaries': foreach ($value as $dictionary) { $this->loadDictionary($dictionary); } return; } return parent::setLinterConfigurationValue($key, $value); } public function loadDictionary($path) { $root = $this->getProjectRoot(); $path = Filesystem::resolvePath($path, $root); $dict = phutil_json_decode(Filesystem::readFile($path)); PhutilTypeSpec::checkMap( $dict, array( 'rules' => 'map>', )); $rules = $dict['rules']; $this->dictionaries[] = $path; $this->exactWordRules = array_merge( $this->exactWordRules, idx($rules, 'exact', array())); $this->partialWordRules = array_merge( $this->partialWordRules, idx($rules, 'partial', array())); } public function addExactWordRule($misspelling, $correction) { $this->exactWordRules = array_merge( $this->exactWordRules, array($misspelling => $correction)); + return $this; } public function addPartialWordRule($misspelling, $correction) { $this->partialWordRules = array_merge( $this->partialWordRules, array($misspelling => $correction)); + return $this; } public function getLintSeverityMap() { return array( self::LINT_SPELLING_EXACT => ArcanistLintSeverity::SEVERITY_WARNING, self::LINT_SPELLING_PARTIAL => ArcanistLintSeverity::SEVERITY_WARNING, ); } public function getLintNameMap() { return array( self::LINT_SPELLING_EXACT => pht('Possible Spelling Mistake'), self::LINT_SPELLING_PARTIAL => pht('Possible Spelling Mistake'), ); } public function lintPath($path) { // TODO: This is a bit hacky. If no dictionaries were specified, then add // the default dictionary. if (!$this->dictionaries) { $root = dirname(phutil_get_library_root('arcanist')); $this->loadDictionary($root.'/resources/spelling/english.json'); } foreach ($this->exactWordRules as $misspelling => $correction) { $this->checkExactWord($path, $misspelling, $correction); } foreach ($this->partialWordRules as $misspelling => $correction) { $this->checkPartialWord($path, $misspelling, $correction); } } private function checkExactWord($path, $word, $correction) { $text = $this->getData($path); $matches = array(); $num_matches = preg_match_all( '#\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($correction, $original); $this->raiseLintAtOffset( $match[1], self::LINT_SPELLING_EXACT, pht( "Possible spelling error. You wrote '%s', but did you mean '%s'?", $word, $correction), $original, $replacement); } } private function checkPartialWord($path, $word, $correction) { $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($correction, $original); $this->raiseLintAtOffset( $next, self::LINT_SPELLING_PARTIAL, pht( "Possible spelling error. You wrote '%s', but did you mean '%s'?", $word, $correction), $original, $replacement); $pos = $next + 1; } } public static function fixLetterCase($string, $case) { switch ($case) { case strtolower($case): return strtolower($string); case strtoupper($case): return strtoupper($string); case ucwords(strtolower($case)): return ucwords(strtolower($string)); default: return null; } } } diff --git a/src/lint/linter/ArcanistXHPASTLinter.php b/src/lint/linter/ArcanistXHPASTLinter.php index 985f0950..aa3a472b 100644 --- a/src/lint/linter/ArcanistXHPASTLinter.php +++ b/src/lint/linter/ArcanistXHPASTLinter.php @@ -1,123 +1,138 @@ rules = ArcanistXHPASTLinterRule::loadAllRules(); + $this->setRules(ArcanistXHPASTLinterRule::loadAllRules()); } public function __clone() { $rules = $this->rules; $this->rules = array(); foreach ($rules as $rule) { $this->rules[] = clone $rule; } } + /** + * Set the XHPAST linter rules which are enforced by this linter. + * + * This is primarily useful for unit tests in which it is desirable to test + * linter rules in isolation. By default, all linter rules will be enabled. + * + * @param list + * @return this + */ + public function setRules(array $rules) { + assert_instances_of($rules, 'ArcanistXHPASTLinterRule'); + $this->rules = $rules; + return $this; + } + public function getInfoName() { return pht('XHPAST Lint'); } public function getInfoDescription() { return pht('Use XHPAST to enforce coding conventions on PHP source files.'); } public function getLinterName() { return 'XHP'; } public function getLinterConfigurationName() { return 'xhpast'; } public function getLintNameMap() { if ($this->lintNameMap === null) { $this->lintNameMap = mpull( $this->rules, 'getLintName', 'getLintID'); } return $this->lintNameMap; } public function getLintSeverityMap() { if ($this->lintSeverityMap === null) { $this->lintSeverityMap = mpull( $this->rules, 'getLintSeverity', 'getLintID'); } return $this->lintSeverityMap; } public function getLinterConfigurationOptions() { return parent::getLinterConfigurationOptions() + array_mergev( mpull($this->rules, 'getLinterConfigurationOptions')); } public function setLinterConfigurationValue($key, $value) { $matched = false; foreach ($this->rules as $rule) { foreach ($rule->getLinterConfigurationOptions() as $k => $spec) { if ($k == $key) { $matched = true; $rule->setLinterConfigurationValue($key, $value); } } } if ($matched) { return; } return parent::setLinterConfigurationValue($key, $value); } public function getVersion() { // TODO: Improve this. return count($this->rules); } protected function resolveFuture($path, Future $future) { $tree = $this->getXHPASTTreeForPath($path); if (!$tree) { $ex = $this->getXHPASTExceptionForPath($path); if ($ex instanceof XHPASTSyntaxErrorException) { $this->raiseLintAtLine( $ex->getErrorLine(), 1, ArcanistSyntaxErrorXHPASTLinterRule::ID, pht( 'This file contains a syntax error: %s', $ex->getMessage())); } else if ($ex instanceof Exception) { $this->raiseLintAtPath( ArcanistUnableToParseXHPASTLinterRule::ID, $ex->getMessage()); } return; } $root = $tree->getRootNode(); foreach ($this->rules as $rule) { if ($this->isCodeEnabled($rule->getLintID())) { $rule->setLinter($this); $rule->process($root); } } } } diff --git a/src/lint/linter/__tests__/ArcanistLinterTestCase.php b/src/lint/linter/__tests__/ArcanistLinterTestCase.php index d4007d2e..a6eba567 100644 --- a/src/lint/linter/__tests__/ArcanistLinterTestCase.php +++ b/src/lint/linter/__tests__/ArcanistLinterTestCase.php @@ -1,266 +1,261 @@ getLinter(); - } + protected function executeTestsInDirectory($root) { + $linter = $this->getLinter(); $files = id(new FileFinder($root)) ->withType('f') ->withSuffix('lint-test') ->find(); $test_count = 0; foreach ($files as $file) { $this->lintFile($root.$file, $linter); $test_count++; } $this->assertTrue( ($test_count > 0), pht( 'Expected to find some %s tests in directory %s!', '.lint-test', $root)); } private function lintFile($file, ArcanistLinter $linter) { $linter = clone $linter; $contents = Filesystem::readFile($file); $contents = preg_split('/^~{4,}\n/m', $contents); if (count($contents) < 2) { throw new Exception( pht( "Expected '%s' separating test case and results.", '~~~~~~~~~~')); } list($data, $expect, $xform, $config) = array_merge( $contents, array(null, null)); $basename = basename($file); if ($config) { $config = phutil_json_decode($config); } else { $config = array(); } PhutilTypeSpec::checkMap( $config, array( 'config' => 'optional map', 'path' => 'optional string', 'mode' => 'optional string', 'stopped' => 'optional bool', )); $exception = null; $after_lint = null; $messages = null; $exception_message = false; $caught_exception = false; try { $tmp = new TempFile($basename); Filesystem::writeFile($tmp, $data); $full_path = (string)$tmp; $mode = idx($config, 'mode'); if ($mode) { Filesystem::changePermissions($tmp, octdec($mode)); } $dir = dirname($full_path); $path = basename($full_path); $working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile( $dir, null, pht('Unit Test')); $configuration_manager = new ArcanistConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $engine = new ArcanistUnitTestableLintEngine(); $engine->setWorkingCopy($working_copy); $engine->setConfigurationManager($configuration_manager); $path_name = idx($config, 'path', $path); $engine->setPaths(array($path_name)); $linter->addPath($path_name); $linter->addData($path_name, $data); foreach (idx($config, 'config', array()) as $key => $value) { $linter->setLinterConfigurationValue($key, $value); } $engine->addLinter($linter); $engine->addFileData($path_name, $data); $results = $engine->run(); $this->assertEqual( 1, count($results), pht('Expect one result returned by linter.')); $assert_stopped = idx($config, 'stopped'); if ($assert_stopped !== null) { $this->assertEqual( $assert_stopped, $linter->didStopAllLinters(), $assert_stopped ? pht('Expect linter to be stopped.') : pht('Expect linter to not be stopped.')); } $result = reset($results); $patcher = ArcanistLintPatcher::newFromArcanistLintResult($result); $after_lint = $patcher->getModifiedFileContent(); } catch (PhutilTestTerminatedException $ex) { throw $ex; } catch (Exception $exception) { $caught_exception = true; if ($exception instanceof PhutilAggregateException) { $caught_exception = false; foreach ($exception->getExceptions() as $ex) { if ($ex instanceof ArcanistUsageException || $ex instanceof ArcanistMissingLinterException) { $this->assertSkipped($ex->getMessage()); } else { $caught_exception = true; } } } else if ($exception instanceof ArcanistUsageException || $exception instanceof ArcanistMissingLinterException) { $this->assertSkipped($exception->getMessage()); } $exception_message = $exception->getMessage()."\n\n". $exception->getTraceAsString(); } $this->assertEqual(false, $caught_exception, $exception_message); $this->compareLint($basename, $expect, $result); $this->compareTransform($xform, $after_lint); } private function compareLint($file, $expect, ArcanistLintResult $result) { $seen = array(); $raised = array(); $message_map = array(); foreach ($result->getMessages() as $message) { $sev = $message->getSeverity(); $line = $message->getLine(); $char = $message->getChar(); $code = $message->getCode(); $name = $message->getName(); $message_key = $sev.':'.$line.':'.$char; $message_map[$message_key] = $message; $seen[] = $message_key; $raised[] = sprintf( ' %s: %s %s', pht('%s at line %d, char %d', $sev, $line, $char), $code, $name); } $expect = trim($expect); if ($expect) { $expect = explode("\n", $expect); } else { $expect = array(); } foreach ($expect as $key => $expected) { $expect[$key] = head(explode(' ', $expected)); } $expect = array_fill_keys($expect, true); $seen = array_fill_keys($seen, true); if (!$raised) { $raised = array(pht('No messages.')); } $raised = sprintf( "%s:\n%s", pht('Actually raised'), implode("\n", $raised)); foreach (array_diff_key($expect, $seen) as $missing => $ignored) { $missing = explode(':', $missing); $sev = array_shift($missing); $pos = $missing; $this->assertFailure( pht( "In '%s', expected lint to raise %s on line %d at char %d, ". "but no %s was raised. %s", $file, $sev, idx($pos, 0), idx($pos, 1), $sev, $raised)); } foreach (array_diff_key($seen, $expect) as $surprising => $ignored) { $message = $message_map[$surprising]; $message_info = $message->getDescription(); list($sev, $line, $char) = explode(':', $surprising); $this->assertFailure( sprintf( "%s:\n\n%s\n\n%s", pht( "In '%s', lint raised %s on line %d at char %d, ". "but nothing was expected", $file, $sev, $line, $char), $message_info, $raised)); } } private function compareTransform($expected, $actual) { if (!strlen($expected)) { return; } $this->assertEqual( $expected, $actual, pht('File as patched by lint did not match the expected patched file.')); } } diff --git a/src/lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php b/src/lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php deleted file mode 100644 index d773e1f5..00000000 --- a/src/lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php +++ /dev/null @@ -1,14 +0,0 @@ -setDeprecatedFunctions(array( - 'deprecated_function' => pht('This function is most likely deprecated.'), - )); - - $this->executeTestsInDirectory(dirname(__FILE__).'/phlxhp/', $linter); - } - -} diff --git a/src/lint/linter/__tests__/ArcanistSpellingLinterTestCase.php b/src/lint/linter/__tests__/ArcanistSpellingLinterTestCase.php index 059c338a..7114487d 100644 --- a/src/lint/linter/__tests__/ArcanistSpellingLinterTestCase.php +++ b/src/lint/linter/__tests__/ArcanistSpellingLinterTestCase.php @@ -1,30 +1,30 @@ addPartialWordRule('supermn', 'superman'); - $linter->addExactWordRule('batmn', 'batman'); + protected function getLinter() { + return parent::getLinter() + ->addPartialWordRule('supermn', 'superman') + ->addExactWordRule('batmn', 'batman'); + } - $this->executeTestsInDirectory( - dirname(__FILE__).'/spelling/', - $linter); + public function testLinter() { + $this->executeTestsInDirectory(dirname(__FILE__).'/spelling/'); } 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__/cppcheck/zblair.lint-test b/src/lint/linter/__tests__/cppcheck/zblair.lint-test index efe85a9a..0b2f875a 100644 --- a/src/lint/linter/__tests__/cppcheck/zblair.lint-test +++ b/src/lint/linter/__tests__/cppcheck/zblair.lint-test @@ -1,20 +1,19 @@ /** * Taken from http://www.slideshare.net/zblair/cppcheck-10316379 */ void foo(char* str) { char* buf = new char[8]; strcpy(buf, str); FILE* file = fopen("out.txt", "w"); if (!file) return; for (char* c = buf; *c; ++c) fputc((int)*c, file); delete buf; } ~~~~~~~~~~ error:10: -error:15: error:16: diff --git a/src/lint/linter/__tests__/csslint/no-line-related-issue.lint-test b/src/lint/linter/__tests__/csslint/no-line-related-issue.lint-test new file mode 100644 index 00000000..409e2a05 --- /dev/null +++ b/src/lint/linter/__tests__/csslint/no-line-related-issue.lint-test @@ -0,0 +1,9 @@ +h1 { + font-weight: bold; +} +h1 { + font-weight: bold; +} +~~~~~~~~~~ +warning:: +warning:4:1 diff --git a/src/lint/linter/__tests__/phlxhp/deprecated-function.lint-test b/src/lint/linter/__tests__/phlxhp/deprecated-function.lint-test deleted file mode 100644 index 25ce3c96..00000000 --- a/src/lint/linter/__tests__/phlxhp/deprecated-function.lint-test +++ /dev/null @@ -1,6 +0,0 @@ -500) of string concatenations. We emit n_CONCATENATION_LIST instead of // n_BINARY_EXPRESSION to avoid various call-depth traps in PHP, HPHP, and the // builtin JSON decoder. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. ''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''; ~~~~~~~~~~ -~~~~~~~~~~ -500) of string concatenations. We emit n_CONCATENATION_LIST instead of -// n_BINARY_EXPRESSION to avoid various call-depth traps in PHP, HPHP, and the -// builtin JSON decoder. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''. -''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''.''; diff --git a/src/lint/linter/standards/ArcanistLinterStandard.php b/src/lint/linter/standards/ArcanistLinterStandard.php new file mode 100644 index 00000000..77b0ec79 --- /dev/null +++ b/src/lint/linter/standards/ArcanistLinterStandard.php @@ -0,0 +1,121 @@ + + */ + public function getLinterConfiguration() { + return array(); + } + + /** + * Get linter severities. + * + * Returns linter severities which are passed to + * @{method:ArcanistLinter::addCustomSeverityMap}. + * + * @return map + */ + public function getLinterSeverityMap() { + return array(); + } + + /** + * Load a linter standard by key. + * + * @param string + * @param ArcanistLinter + * @return ArcanistLinterStandard + */ + final public static function getStandard($key, ArcanistLinter $linter) { + $standards = self::loadAllStandardsForLinter($linter); + + if (empty($standards[$key])) { + throw new ArcanistUsageException( + pht( + 'No such linter standard. Available standards are: %s.', + implode(', ', array_keys($standards)))); + } + + return $standards[$key]; + } + + /** + * Load all linter standards. + * + * @return list + */ + final public static function loadAllStandards() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getKey') + ->execute(); + } + + /** + * Load all linter standards which support a specified linter. + * + * @param ArcanistLinter + * @return list + */ + final public static function loadAllStandardsForLinter( + ArcanistLinter $linter) { + + $all_standards = self::loadAllStandards(); + $standards = array(); + + foreach ($all_standards as $standard) { + if ($standard->supportsLinter($linter)) { + $standards[$standard->getKey()] = $standard; + } + } + + return $standards; + } + +} diff --git a/src/lint/linter/standards/__tests__/ArcanistLinterStandardTestCase.php b/src/lint/linter/standards/__tests__/ArcanistLinterStandardTestCase.php new file mode 100644 index 00000000..d48b3d00 --- /dev/null +++ b/src/lint/linter/standards/__tests__/ArcanistLinterStandardTestCase.php @@ -0,0 +1,10 @@ +assertTrue(true); + } + +} diff --git a/src/lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php b/src/lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php new file mode 100644 index 00000000..17547476 --- /dev/null +++ b/src/lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php @@ -0,0 +1,68 @@ + array( + 'eval' => pht( + 'The `%s` function should be avoided. It is potentially unsafe '. + 'and makes debugging more difficult.', + 'eval'), + ), + 'xhpast.php-version' => '5.2.3', + 'xhpast.php-version.windows' => '5.3.0', + 'xhpast.dynamic-string.classes' => array( + 'ExecFuture' => 0, + ), + 'xhpast.dynamic-string.functions' => array( + 'pht' => 0, + + 'hsprintf' => 0, + 'jsprintf' => 0, + + 'hgsprintf' => 0, + + 'csprintf' => 0, + 'vcsprintf' => 0, + 'execx' => 0, + 'exec_manual' => 0, + 'phutil_passthru' => 0, + + 'qsprintf' => 1, + 'vqsprintf' => 1, + 'queryfx' => 1, + 'queryfx_all' => 1, + 'queryfx_one' => 1, + ), + ); + } + + public function getLinterSeverityMap() { + $advice = ArcanistLintSeverity::SEVERITY_ADVICE; + $error = ArcanistLintSeverity::SEVERITY_ERROR; + + return array( + ArcanistTodoCommentXHPASTLinterRule::ID => $advice, + ArcanistCommentSpacingXHPASTLinterRule::ID => $error, + ); + } + +} diff --git a/src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php b/src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php index aa0cad38..8c40e5c3 100644 --- a/src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php +++ b/src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php @@ -1,229 +1,247 @@ setAncestorClass(__CLASS__) ->setUniqueMethod('getLintID') ->execute(); } final public function getLintID() { if ($this->lintID === null) { $class = new ReflectionClass($this); $const = $class->getConstant('ID'); if ($const === false) { throw new Exception( pht( '`%s` class `%s` must define an ID constant.', __CLASS__, get_class($this))); } if (!is_int($const)) { throw new Exception( pht( '`%s` class `%s` has an invalid ID constant. '. 'ID must be an integer.', __CLASS__, get_class($this))); } $this->lintID = $const; } return $this->lintID; } abstract public function getLintName(); public function getLintSeverity() { return ArcanistLintSeverity::SEVERITY_ERROR; } public function getLinterConfigurationOptions() { return array( 'xhpast.php-version' => array( 'type' => 'optional string', 'help' => pht('PHP version to target.'), ), 'xhpast.php-version.windows' => array( 'type' => 'optional string', 'help' => pht('PHP version to target on Windows.'), ), ); } public function setLinterConfigurationValue($key, $value) { switch ($key) { case 'xhpast.php-version': $this->version = $value; return; case 'xhpast.php-version.windows': $this->windowsVersion = $value; return; } } abstract public function process(XHPASTNode $root); final public function setLinter(ArcanistXHPASTLinter $linter) { $this->linter = $linter; return $this; } - /** - * Statically evaluate a boolean value from an XHP tree. - * - * TODO: Improve this and move it to XHPAST proper? - * - * @param string The "semantic string" of a single value. - * @return mixed `true` or `false` if the value could be evaluated - * statically; `null` if static evaluation was not possible. - */ - protected function evaluateStaticBoolean($string) { - switch (strtolower($string)) { - case '0': - case 'null': - case 'false': - return false; - case '1': - case 'true': - return true; - } - return null; - } - protected function getConcreteVariableString(XHPASTNode $var) { - $concrete = $var->getConcreteString(); - // Strip off curly braces as in `$obj->{$property}`. - $concrete = trim($concrete, '{}'); - return $concrete; - } +/* -( Proxied Methods )---------------------------------------------------- */ - // These methods are proxied to the @{class:ArcanistLinter}. final public function getActivePath() { return $this->linter->getActivePath(); } final public function getOtherLocation($offset, $path = null) { return $this->linter->getOtherLocation($offset, $path); } - final protected function raiseLintAtNode( - XHPASTNode $node, - $desc, - $replace = null) { - - return $this->linter->raiseLintAtNode( - $node, - $this->getLintID(), - $desc, - $replace); + final protected function raiseLintAtPath($desc) { + return $this->linter->raiseLintAtPath($this->getLintID(), $desc); } final public function raiseLintAtOffset( $offset, - $desc, - $text = null, - $replace = null) { + $description, + $original = null, + $replacement = null) { - return $this->linter->raiseLintAtOffset( + $this->linter->raiseLintAtOffset( $offset, $this->getLintID(), - $desc, - $text, - $replace); - } - - final protected function raiseLintAtPath($desc) { - return $this->linter->raiseLintAtPath($this->getLintID(), $desc); + $description, + $original, + $replacement); } final protected function raiseLintAtToken( XHPASTToken $token, - $desc, + $description, $replace = null) { return $this->linter->raiseLintAtToken( $token, $this->getLintID(), - $desc, + $description, + $replace); + } + + final protected function raiseLintAtNode( + XHPASTNode $node, + $description, + $replace = null) { + + return $this->linter->raiseLintAtNode( + $node, + $this->getLintID(), + $description, $replace); } + /* -( Utility )------------------------------------------------------------ */ + + /** + * Statically evaluate a boolean value from an XHP tree. + * + * TODO: Improve this and move it to XHPAST proper? + * + * @param string The "semantic string" of a single value. + * @return mixed `true` or `false` if the value could be evaluated + * statically; `null` if static evaluation was not possible. + */ + protected function evaluateStaticBoolean($string) { + switch (strtolower($string)) { + case '0': + case 'null': + case 'false': + return false; + case '1': + case 'true': + return true; + default: + return null; + } + } + /** * Retrieve all anonymous closure(s). * * Returns all descendant nodes which represent an anonymous function * declaration. * * @param XHPASTNode Root node. * @return AASTNodeList */ protected function getAnonymousClosures(XHPASTNode $root) { $func_decls = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION'); $nodes = array(); foreach ($func_decls as $func_decl) { if ($func_decl->getChildByIndex(2)->getTypeName() == 'n_EMPTY') { $nodes[] = $func_decl; } } return AASTNodeList::newFromTreeAndNodes($root->getTree(), $nodes); } + /** + * TODO + * + * @param XHPASTNode + * @return string + */ + protected function getConcreteVariableString(XHPASTNode $variable) { + $concrete = $variable->getConcreteString(); + + // Strip off curly braces as in `$obj->{$property}`. + $concrete = trim($concrete, '{}'); + + return $concrete; + } + /** * Retrieve all calls to some specified function(s). * * Returns all descendant nodes which represent a function call to one of the * specified functions. * * @param XHPASTNode Root node. * @param list Function names. * @return AASTNodeList */ protected function getFunctionCalls(XHPASTNode $root, array $function_names) { $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); $nodes = array(); foreach ($calls as $call) { $node = $call->getChildByIndex(0); $name = strtolower($node->getConcreteString()); if (in_array($name, $function_names)) { $nodes[] = $call; } } return AASTNodeList::newFromTreeAndNodes($root->getTree(), $nodes); } + /** + * Get PHP superglobals. + * + * @return list + */ public function getSuperGlobalNames() { return array( '$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV', ); } } diff --git a/src/lint/linter/xhpast/__tests__/ArcanistXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/__tests__/ArcanistXHPASTLinterRuleTestCase.php deleted file mode 100644 index fcb49f7c..00000000 --- a/src/lint/linter/xhpast/__tests__/ArcanistXHPASTLinterRuleTestCase.php +++ /dev/null @@ -1,10 +0,0 @@ -assertTrue(true); - } - -} diff --git a/src/lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php new file mode 100644 index 00000000..659bd30b --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php @@ -0,0 +1,44 @@ +getFunctionCalls($root, array('array_combine')); + + foreach ($function_calls as $call) { + $name = $call->getChildByIndex(0)->getConcreteString(); + $parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); + + if (count($parameter_list->getChildren()) !== 2) { + // Wrong number of parameters, but raise that elsewhere if we want. + continue; + } + + $first = $parameter_list->getChildByIndex(0); + $second = $parameter_list->getChildByIndex(1); + + if ($first->getConcreteString() == $second->getConcreteString()) { + $this->raiseLintAtNode( + $call, + pht( + 'Prior to PHP 5.4, `%s` fails when given empty arrays. '. + 'Prefer to write `%s` as `%s`.', + 'array_combine()', + 'array_combine(x, x)', + 'array_fuse(x)')); + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php index d2908154..0a8c33b7 100644 --- a/src/lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php +++ b/src/lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php @@ -1,105 +1,112 @@ selectDescendantsOfType('n_STATEMENT_LIST') as $list) { $tokens = $list->getTokens(); if (!$tokens || head($tokens)->getValue() != '{') { continue; } list($before, $after) = $list->getSurroundingNonsemanticTokens(); if (!$before) { $first = head($tokens); // Only insert the space if we're after a closing parenthesis. If // we're in a construct like "else{}", other rules will insert space // after the 'else' correctly. $prev = $first->getPrevToken(); if (!$prev || $prev->getValue() !== ')') { continue; } $this->raiseLintAtToken( $first, pht( 'Put opening braces on the same line as control statements and '. 'declarations, with a single space before them.'), ' '.$first->getValue()); } else if (count($before) === 1) { $before = reset($before); if ($before->getValue() !== ' ') { $this->raiseLintAtToken( $before, pht( 'Put opening braces on the same line as control statements and '. 'declarations, with a single space before them.'), ' '); } } } $nodes = $root->selectDescendantsOfType('n_STATEMENT'); foreach ($nodes as $node) { $parent = $node->getParentNode(); if (!$parent) { continue; } $type = $parent->getTypeName(); - if ($type != 'n_STATEMENT_LIST' && $type != 'n_DECLARE') { - $this->raiseLintAtNode( - $node, - pht('Use braces to surround a statement block.')); + switch ($type) { + case 'n_DECLARE': + case 'n_NAMESPACE': + case 'n_STATEMENT_LIST': + break; + + default: + $this->raiseLintAtNode( + $node, + pht('Use braces to surround a statement block.')); + break; } } $nodes = $root->selectDescendantsOfTypes(array( 'n_DO_WHILE', 'n_ELSE', 'n_ELSEIF', )); foreach ($nodes as $list) { $tokens = $list->getTokens(); if (!$tokens || last($tokens)->getValue() != '}') { continue; } list($before, $after) = $list->getSurroundingNonsemanticTokens(); if (!$before) { $first = head($tokens); $this->raiseLintAtToken( $first, pht( 'Put opening braces on the same line as control statements and '. 'declarations, with a single space before them.'), ' '.$first->getValue()); } else if (count($before) === 1) { $before = reset($before); if ($before->getValue() !== ' ') { $this->raiseLintAtToken( $before, pht( 'Put opening braces on the same line as control statements and '. 'declarations, with a single space before them.'), ' '); } } } } } diff --git a/src/lint/linter/xhpast/rules/ArcanistClassExtendsObjectXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistClassExtendsObjectXHPASTLinterRule.php new file mode 100644 index 00000000..d712b3fe --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistClassExtendsObjectXHPASTLinterRule.php @@ -0,0 +1,40 @@ +selectDescendantsOfType('n_CLASS_DECLARATION'); + + foreach ($classes as $class) { + // TODO: This doesn't quite work for namespaced classes (see T8534). + $name = $class->getChildOfType(1, 'n_CLASS_NAME'); + $extends = $class->getChildByIndex(2); + + if ($name->getConcreteString() == 'Phobject') { + continue; + } + + if ($extends->getTypeName() == 'n_EMPTY') { + $this->raiseLintAtNode( + $class, + pht( + 'Classes should extend from %s or from some other class. '. + 'All classes (except for %s itself) should have a base class.', + 'Phobject', + 'Phobject')); + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php new file mode 100644 index 00000000..bc57bb2d --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php @@ -0,0 +1,57 @@ + array( + 'type' => 'optional map', + 'help' => pht( + 'Functions which should should be considered deprecated.'), + ), + ); + } + + public function setLinterConfigurationValue($key, $value) { + switch ($key) { + case 'xhpast.deprecated.functions': + $this->deprecatedFunctions = $value; + return; + + default: + return parent::getLinterConfigurationOptions(); + } + } + + public function process(XHPASTNode $root) { + $map = $this->deprecatedFunctions; + $function_calls = $this->getFunctionCalls($root, array_keys($map)); + + foreach ($function_calls as $call) { + $name = $call + ->getChildByIndex(0) + ->getConcreteString(); + + $name = strtolower($name); + if (empty($map[$name])) { + continue; + } + + $this->raiseLintAtNode($call, $map[$name]); + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistNestedNamespacesXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistNestedNamespacesXHPASTLinterRule.php new file mode 100644 index 00000000..ec42064b --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistNestedNamespacesXHPASTLinterRule.php @@ -0,0 +1,29 @@ +selectDescendantsOfType('n_NAMESPACE'); + + foreach ($namespaces as $namespace) { + $nested_namespaces = $namespace->selectDescendantsOfType('n_NAMESPACE'); + + foreach ($nested_namespaces as $nested_namespace) { + $this->raiseLintAtNode( + $nested_namespace, + pht( + '`%s` declarations cannot be nested. '. + 'This construct will cause a PHP fatal error.', + 'namespace')); + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php index e2f627a3..b91fc560 100644 --- a/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php +++ b/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php @@ -1,450 +1,447 @@ version) { return; } if ($compat_info === null) { $target = phutil_get_library_root('phutil'). '/../resources/php_compat_info.json'; $compat_info = phutil_json_decode(Filesystem::readFile($target)); } // Create a whitelist for symbols which are being used conditionally. $whitelist = array( 'class' => array(), 'function' => array(), ); $conditionals = $root->selectDescendantsOfType('n_IF'); foreach ($conditionals as $conditional) { $condition = $conditional->getChildOfType(0, 'n_CONTROL_CONDITION'); $function = $condition->getChildByIndex(0); if ($function->getTypeName() != 'n_FUNCTION_CALL') { continue; } $function_token = $function ->getChildByIndex(0); if ($function_token->getTypeName() != 'n_SYMBOL_NAME') { // This may be `Class::method(...)` or `$var(...)`. continue; } $function_name = $function_token->getConcreteString(); switch ($function_name) { case 'class_exists': case 'function_exists': case 'interface_exists': $type = null; switch ($function_name) { case 'class_exists': $type = 'class'; break; case 'function_exists': $type = 'function'; break; case 'interface_exists': $type = 'interface'; break; } $params = $function->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); $symbol = $params->getChildByIndex(0); if (!$symbol->isStaticScalar()) { continue; } $symbol_name = $symbol->evalStatic(); if (!idx($whitelist[$type], $symbol_name)) { $whitelist[$type][$symbol_name] = array(); } $span = $conditional ->getChildByIndex(1) ->getTokens(); $whitelist[$type][$symbol_name][] = range( head_key($span), last_key($span)); break; } } $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); foreach ($calls as $call) { $node = $call->getChildByIndex(0); $name = $node->getConcreteString(); $version = idx($compat_info['functions'], $name, array()); $min = idx($version, 'php.min'); $max = idx($version, 'php.max'); // Check if whitelisted. $whitelisted = false; foreach (idx($whitelist['function'], $name, array()) as $range) { if (array_intersect($range, array_keys($node->getTokens()))) { $whitelisted = true; break; } } if ($whitelisted) { continue; } if ($min && version_compare($min, $this->version, '>')) { $this->raiseLintAtNode( $node, pht( 'This codebase targets PHP %s, but `%s()` was not '. 'introduced until PHP %s.', $this->version, $name, $min)); } else if ($max && version_compare($max, $this->version, '<')) { $this->raiseLintAtNode( $node, pht( 'This codebase targets PHP %s, but `%s()` was '. 'removed in PHP %s.', $this->version, $name, $max)); } else if (array_key_exists($name, $compat_info['params'])) { $params = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); foreach (array_values($params->getChildren()) as $i => $param) { $version = idx($compat_info['params'][$name], $i); if ($version && version_compare($version, $this->version, '>')) { $this->raiseLintAtNode( $param, pht( 'This codebase targets PHP %s, but parameter %d '. 'of `%s()` was not introduced until PHP %s.', $this->version, $i + 1, $name, $version)); } } } if ($this->windowsVersion) { $windows = idx($compat_info['functions_windows'], $name); if ($windows === false) { $this->raiseLintAtNode( $node, pht( 'This codebase targets PHP %s on Windows, '. 'but `%s()` is not available there.', $this->windowsVersion, $name)); } else if (version_compare($windows, $this->windowsVersion, '>')) { $this->raiseLintAtNode( $node, pht( 'This codebase targets PHP %s on Windows, '. 'but `%s()` is not available there until PHP %s.', $this->windowsVersion, $name, $windows)); } } } $classes = $root->selectDescendantsOfType('n_CLASS_NAME'); foreach ($classes as $node) { $name = $node->getConcreteString(); $version = idx($compat_info['interfaces'], $name, array()); $version = idx($compat_info['classes'], $name, $version); $min = idx($version, 'php.min'); $max = idx($version, 'php.max'); // Check if whitelisted. $whitelisted = false; foreach (idx($whitelist['class'], $name, array()) as $range) { if (array_intersect($range, array_keys($node->getTokens()))) { $whitelisted = true; break; } } if ($whitelisted) { continue; } if ($min && version_compare($min, $this->version, '>')) { $this->raiseLintAtNode( $node, pht( 'This codebase targets PHP %s, but `%s` was not '. 'introduced until PHP %s.', $this->version, $name, $min)); } else if ($max && version_compare($max, $this->version, '<')) { $this->raiseLintAtNode( $node, pht( 'This codebase targets PHP %s, but `%s` was '. 'removed in PHP %s.', $this->version, $name, $max)); } } // TODO: Technically, this will include function names. This is unlikely to // cause any issues (unless, of course, there existed a function that had // the same name as some constant). $constants = $root->selectDescendantsOfTypes(array( 'n_SYMBOL_NAME', 'n_MAGIC_SCALAR', )); foreach ($constants as $node) { $name = $node->getConcreteString(); $version = idx($compat_info['constants'], $name, array()); $min = idx($version, 'php.min'); $max = idx($version, 'php.max'); if ($min && version_compare($min, $this->version, '>')) { $this->raiseLintAtNode( $node, pht( 'This codebase targets PHP %s, but `%s` was not '. 'introduced until PHP %s.', $this->version, $name, $min)); } else if ($max && version_compare($max, $this->version, '<')) { $this->raiseLintAtNode( $node, pht( 'This codebase targets PHP %s, but `%s` was '. 'removed in PHP %s.', $this->version, $name, $max)); } } if (version_compare($this->version, '5.3.0') < 0) { $this->lintPHP53Features($root); } else { $this->lintPHP53Incompatibilities($root); } if (version_compare($this->version, '5.4.0') < 0) { $this->lintPHP54Features($root); } else { $this->lintPHP54Incompatibilities($root); } } private function lintPHP53Features(XHPASTNode $root) { $functions = $root->selectTokensOfType('T_FUNCTION'); foreach ($functions as $function) { $next = $function->getNextToken(); while ($next) { if ($next->isSemantic()) { break; } $next = $next->getNextToken(); } if ($next) { if ($next->getTypeName() === '(') { $this->raiseLintAtToken( $function, pht( 'This codebase targets PHP %s, but anonymous '. 'functions were not introduced until PHP 5.3.', $this->version)); } } } $namespaces = $root->selectTokensOfType('T_NAMESPACE'); foreach ($namespaces as $namespace) { $this->raiseLintAtToken( $namespace, pht( 'This codebase targets PHP %s, but namespaces were not '. 'introduced until PHP 5.3.', $this->version)); } // NOTE: This is only "use x;", in anonymous functions the node type is // n_LEXICAL_VARIABLE_LIST even though both tokens are T_USE. - // TODO: We parse n_USE in a slightly crazy way right now; that would be - // a better selector once it's fixed. - $uses = $root->selectDescendantsOfType('n_USE_LIST'); foreach ($uses as $use) { $this->raiseLintAtNode( $use, pht( 'This codebase targets PHP %s, but namespaces were not '. 'introduced until PHP 5.3.', $this->version)); } $statics = $root->selectDescendantsOfType('n_CLASS_STATIC_ACCESS'); foreach ($statics as $static) { $name = $static->getChildByIndex(0); if ($name->getTypeName() != 'n_CLASS_NAME') { continue; } if ($name->getConcreteString() === 'static') { $this->raiseLintAtNode( $name, pht( 'This codebase targets PHP %s, but `%s` was not '. 'introduced until PHP 5.3.', $this->version, 'static::')); } } $ternaries = $root->selectDescendantsOfType('n_TERNARY_EXPRESSION'); foreach ($ternaries as $ternary) { $yes = $ternary->getChildByIndex(2); if ($yes->getTypeName() === 'n_EMPTY') { $this->raiseLintAtNode( $ternary, pht( 'This codebase targets PHP %s, but short ternary was '. 'not introduced until PHP 5.3.', $this->version)); } } $heredocs = $root->selectDescendantsOfType('n_HEREDOC'); foreach ($heredocs as $heredoc) { if (preg_match('/^<<<[\'"]/', $heredoc->getConcreteString())) { $this->raiseLintAtNode( $heredoc, pht( 'This codebase targets PHP %s, but nowdoc was not '. 'introduced until PHP 5.3.', $this->version)); } } } private function lintPHP53Incompatibilities(XHPASTNode $root) {} private function lintPHP54Features(XHPASTNode $root) { $indexes = $root->selectDescendantsOfType('n_INDEX_ACCESS'); foreach ($indexes as $index) { switch ($index->getChildByIndex(0)->getTypeName()) { case 'n_FUNCTION_CALL': case 'n_METHOD_CALL': $this->raiseLintAtNode( $index->getChildByIndex(1), pht( 'The `%s` syntax was not introduced until PHP 5.4, but this '. 'codebase targets an earlier version of PHP. You can rewrite '. 'this expression using `%s`.', 'f()[...]', 'idx()')); break; } } $closures = $this->getAnonymousClosures($root); foreach ($closures as $closure) { $static_accesses = $closure ->selectDescendantsOfType('n_CLASS_STATIC_ACCESS'); foreach ($static_accesses as $static_access) { $class = $static_access->getChildByIndex(0); if ($class->getTypeName() != 'n_CLASS_NAME') { continue; } if (strtolower($class->getConcreteString()) != 'self') { continue; } $this->raiseLintAtNode( $class, pht( 'The use of `%s` in an anonymous closure is not '. 'available before PHP 5.4.', 'self')); } $property_accesses = $closure ->selectDescendantsOfType('n_OBJECT_PROPERTY_ACCESS'); foreach ($property_accesses as $property_access) { $variable = $property_access->getChildByIndex(0); if ($variable->getTypeName() != 'n_VARIABLE') { continue; } if ($variable->getConcreteString() != '$this') { continue; } $this->raiseLintAtNode( $variable, pht( 'The use of `%s` in an anonymous closure is not '. 'available before PHP 5.4.', '$this')); } } } private function lintPHP54Incompatibilities(XHPASTNode $root) { $breaks = $root->selectDescendantsOfTypes(array('n_BREAK', 'n_CONTINUE')); foreach ($breaks as $break) { $arg = $break->getChildByIndex(0); switch ($arg->getTypeName()) { case 'n_EMPTY': break; case 'n_NUMERIC_SCALAR': if ($arg->getConcreteString() != '0') { break; } default: $this->raiseLintAtNode( $break->getChildByIndex(0), pht( 'The `%s` and `%s` statements no longer accept '. 'variable arguments.', 'break', 'continue')); break; } } } } diff --git a/src/lint/linter/xhpast/rules/ArcanistParentMemberReferenceXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistParentMemberReferenceXHPASTLinterRule.php new file mode 100644 index 00000000..0183461b --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistParentMemberReferenceXHPASTLinterRule.php @@ -0,0 +1,71 @@ +selectDescendantsOfType('n_CLASS_DECLARATION'); + + foreach ($class_declarations as $class_declaration) { + $extends_list = $class_declaration + ->getChildByIndex(2); + $parent_class = null; + + if ($extends_list->getTypeName() == 'n_EXTENDS_LIST') { + $parent_class = $extends_list + ->getChildOfType(0, 'n_CLASS_NAME') + ->getConcreteString(); + } + + if (!$parent_class) { + continue; + } + + $class_static_accesses = $class_declaration + ->selectDescendantsOfType('n_CLASS_STATIC_ACCESS'); + $closures = $this->getAnonymousClosures($class_declaration); + + foreach ($class_static_accesses as $class_static_access) { + $double_colons = $class_static_access + ->selectTokensOfType('T_PAAMAYIM_NEKUDOTAYIM'); + $class_ref = $class_static_access->getChildByIndex(0); + + if ($class_ref->getTypeName() != 'n_CLASS_NAME') { + continue; + } + $class_ref_name = $class_ref->getConcreteString(); + + if (strtolower($parent_class) == strtolower($class_ref_name)) { + $in_closure = false; + + foreach ($closures as $closure) { + if ($class_ref->isDescendantOf($closure)) { + $in_closure = true; + break; + } + } + + if (version_compare($this->version, '5.4.0', '>=') || !$in_closure) { + $this->raiseLintAtNode( + $class_ref, + pht( + 'Use `%s` to call parent method.', + 'parent::'), + 'parent'); + } + } + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistRaggedClassTreeEdgeXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistRaggedClassTreeEdgeXHPASTLinterRule.php new file mode 100644 index 00000000..64bfd87f --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistRaggedClassTreeEdgeXHPASTLinterRule.php @@ -0,0 +1,54 @@ +selectDescendantsOfType('n_CLASS_DECLARATION'); + foreach ($classes as $class) { + $is_final = false; + $is_abstract = false; + $is_concrete_extensible = false; + + $attributes = $class->getChildOfType(0, 'n_CLASS_ATTRIBUTES'); + foreach ($attributes->getChildren() as $child) { + if ($child->getConcreteString() == 'final') { + $is_final = true; + } + if ($child->getConcreteString() == 'abstract') { + $is_abstract = true; + } + } + + $docblock = $class->getDocblockToken(); + if ($docblock) { + list($text, $specials) = $parser->parse($docblock->getValue()); + $is_concrete_extensible = idx($specials, 'concrete-extensible'); + } + + if (!$is_final && !$is_abstract && !$is_concrete_extensible) { + $this->raiseLintAtNode( + $class->getChildOfType(1, 'n_CLASS_NAME'), + pht( + "This class is neither '%s' nor '%s', and does not have ". + "a docblock marking it '%s'.", + 'final', + 'abstract', + '@concrete-extensible')); + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistThisReassignmentXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistThisReassignmentXHPASTLinterRule.php new file mode 100644 index 00000000..eade67f9 --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistThisReassignmentXHPASTLinterRule.php @@ -0,0 +1,39 @@ +selectDescendantsOfType('n_BINARY_EXPRESSION'); + + foreach ($binary_expressions as $binary_expression) { + $operator = $binary_expression->getChildOfType(1, 'n_OPERATOR'); + + if ($operator->getConcreteString() != '=') { + continue; + } + + $variable = $binary_expression->getChildByIndex(0); + + if ($variable->getTypeName() != 'n_VARIABLE') { + continue; + } + + if ($variable->getConcreteString() == '$this') { + $this->raiseLintAtNode( + $binary_expression, + pht( + '`%s` cannot be re-assigned. '. + 'This construct will cause a PHP fatal error.', + '$this')); + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistUnexpectedReturnValueXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistUnexpectedReturnValueXHPASTLinterRule.php new file mode 100644 index 00000000..b73ea0a5 --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistUnexpectedReturnValueXHPASTLinterRule.php @@ -0,0 +1,48 @@ +selectDescendantsOfType('n_METHOD_DECLARATION'); + + foreach ($methods as $method) { + $method_name = $method + ->getChildOfType(2, 'n_STRING') + ->getConcreteString(); + + switch (strtolower($method_name)) { + case '__construct': + case '__destruct': + $returns = $method->selectDescendantsOfType('n_RETURN'); + + foreach ($returns as $return) { + $return_value = $return->getChildByIndex(0); + + if ($return_value->getTypeName() == 'n_EMPTY') { + continue; + } + + $this->raiseLintAtNode( + $return, + pht( + 'Unexpected `%s` value in `%s` method.', + 'return', + $method_name)); + } + break; + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/ArcanistUnsafeDynamicStringXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistUnsafeDynamicStringXHPASTLinterRule.php new file mode 100644 index 00000000..be483f3c --- /dev/null +++ b/src/lint/linter/xhpast/rules/ArcanistUnsafeDynamicStringXHPASTLinterRule.php @@ -0,0 +1,103 @@ + array( + 'type' => 'optional map', + 'help' => pht( + 'Classes which should should not be used because they represent the '. + 'unsafe usage of dynamic strings.'), + ), + 'xhpast.dynamic-string.functions' => array( + 'type' => 'optional map', + 'help' => pht( + 'Functions which should should not be used because they represent '. + 'the unsafe usage of dynamic strings.'), + ), + ); + + return $options + parent::getLinterConfigurationOptions(); + } + + public function setLinterConfigurationValue($key, $value) { + switch ($key) { + case 'xhpast.dynamic-string.classes': + $this->dynamicStringClasses = $value; + return; + + case 'xhpast.dynamic-string.functions': + $this->dynamicStringFunctions = $value; + return; + + default: + parent::setLinterConfigurationValue($key, $value); + return; + } + } + + public function process(XHPASTNode $root) { + $this->lintUnsafeDynamicStringClasses($root); + $this->lintUnsafeDynamicStringFunctions($root); + } + + private function lintUnsafeDynamicStringClasses(XHPASTNode $root) { + $news = $root->selectDescendantsOfType('n_NEW'); + $this->lintUnsafeDynamicStringCall($news, $this->dynamicStringClasses); + } + + private function lintUnsafeDynamicStringFunctions(XHPASTNode $root) { + $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); + $this->lintUnsafeDynamicStringCall($calls, $this->dynamicStringFunctions); + } + + private function lintUnsafeDynamicStringCall( + AASTNodeList $calls, + array $safe) { + + $safe = array_combine( + array_map('strtolower', array_keys($safe)), + $safe); + + foreach ($calls as $call) { + $name = $call->getChildByIndex(0)->getConcreteString(); + $param = idx($safe, strtolower($name)); + + if ($param === null) { + continue; + } + + $parameters = $call->getChildByIndex(1); + if (count($parameters->getChildren()) <= $param) { + continue; + } + + $identifier = $parameters->getChildByIndex($param); + if (!$identifier->isConstantString()) { + $this->raiseLintAtNode( + $call, + pht( + "Parameter %d of %s should be a scalar string, ". + "otherwise it's not safe.", + $param + 1, + $name.'()')); + } + } + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistAliasFunctionXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistAliasFunctionXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..7ad10327 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistAliasFunctionXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/alias-functions/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistArrayCombineXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistArrayCombineXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..8457027f --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistArrayCombineXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/array-combine/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistArrayIndexSpacingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistArrayIndexSpacingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..00d407f6 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistArrayIndexSpacingXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/array-index-spacing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistArraySeparatorXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistArraySeparatorXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..f08d29f2 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistArraySeparatorXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/array-separator/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistArrayValueXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistArrayValueXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..4cef3283 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistArrayValueXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/array-value/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistBinaryExpressionSpacingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistBinaryExpressionSpacingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..a9f5ce02 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistBinaryExpressionSpacingXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/binary-expression-spacing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistBlacklistedFunctionXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistBlacklistedFunctionXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..4659f29e --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistBlacklistedFunctionXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/blacklisted-function/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistBraceFormattingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistBraceFormattingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..e702821b --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistBraceFormattingXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/brace-formatting/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistCallParenthesesXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistCallParenthesesXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..1059a83c --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistCallParenthesesXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/call-parentheses/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistCallTimePassByReferenceXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistCallTimePassByReferenceXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..d2f37395 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistCallTimePassByReferenceXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/call-time-pass-by-reference/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistCastSpacingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistCastSpacingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..2d3aa793 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistCastSpacingXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/cast-spacing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistClassExtendsObjectXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistClassExtendsObjectXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..bf8704ed --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistClassExtendsObjectXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/class-extends-object/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistClassNameLiteralXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistClassNameLiteralXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..212490f6 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistClassNameLiteralXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/class-name-literal/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistCommentStyleXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistCommentStyleXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..02a49421 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistCommentStyleXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/comment-style/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistConcatenationOperatorXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistConcatenationOperatorXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..17fe90fc --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistConcatenationOperatorXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/concatenation-operator/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistConstructorParenthesesXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistConstructorParenthesesXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..724323af --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistConstructorParenthesesXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/constructor-parentheses/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistControlStatementSpacingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistControlStatementSpacingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..0c180371 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistControlStatementSpacingXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/control-statement-spacing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..d3eb2859 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/declaration-parentheses/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistDefaultParametersXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistDefaultParametersXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..34d462c3 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistDefaultParametersXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/default-parameters/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistDeprecationXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistDeprecationXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..9592ac12 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistDeprecationXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/deprecation/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistDoubleQuoteXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistDoubleQuoteXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..c0e6a4f2 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistDoubleQuoteXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/double-quote/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistDuplicateKeysInArrayXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistDuplicateKeysInArrayXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..ed85186b --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistDuplicateKeysInArrayXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/duplicate-keys-in-array/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..11f5f21c --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/duplicate-switch-case/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistDynamicDefineXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistDynamicDefineXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..23fda4f2 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistDynamicDefineXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/dynamic-define/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistElseIfUsageXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistElseIfUsageXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..9df4c303 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistElseIfUsageXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/elseif-usage/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistEmptyStatementXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistEmptyStatementXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..7b63d91e --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistEmptyStatementXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/empty-statement/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistExitExpressionXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistExitExpressionXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..778be494 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistExitExpressionXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/exit-expression/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistExtractUseXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistExtractUseXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..8b44a012 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistExtractUseXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/extract-use/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistFormattedStringXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistFormattedStringXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..306b4542 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistFormattedStringXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/formatted-string/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistGlobalVariableXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistGlobalVariableXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..b31e744c --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistGlobalVariableXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/global-variable/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistImplicitConstructorXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistImplicitConstructorXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..7e330bca --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistImplicitConstructorXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/implicit-constructor/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistImplicitFallthroughXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistImplicitFallthroughXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..3e5248a8 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistImplicitFallthroughXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/implicit-fallthrough/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistImplicitVisibilityXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistImplicitVisibilityXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..53fb877c --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistImplicitVisibilityXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/implicit-visibility/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistInlineHTMLXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistInlineHTMLXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..fca79f51 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistInlineHTMLXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/inline-html/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistInnerFunctionXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistInnerFunctionXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..c33db39b --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistInnerFunctionXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/inner-function/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistInstanceofOperatorXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistInstanceofOperatorXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..44eedbd2 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistInstanceofOperatorXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/instanceof-operator/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistInvalidDefaultParameterXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistInvalidDefaultParameterXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..e0e55c3d --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistInvalidDefaultParameterXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/invalid-default-parameter/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistInvalidModifiersXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistInvalidModifiersXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..97ab4655 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistInvalidModifiersXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/invalid-modifiers/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistKeywordCasingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistKeywordCasingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..15e1d02d --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistKeywordCasingXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/keyword-casing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..57984f60 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/__lambda_func-function/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistLanguageConstructParenthesesXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistLanguageConstructParenthesesXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..8aff191b --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistLanguageConstructParenthesesXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/language-construct-parentheses/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistListAssignmentXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistListAssignmentXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..8af5cf0b --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistListAssignmentXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/list-assignment/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistLogicalOperatorsXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistLogicalOperatorsXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..bfb70b36 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistLogicalOperatorsXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/logical-operators/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..26cead9e --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/lowercase-functions/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistModifierOrderingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistModifierOrderingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..bca28cf9 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistModifierOrderingXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/modifier-ordering/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistNamingConventionsXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistNamingConventionsXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..4efd983b --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistNamingConventionsXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/naming-conventions/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistNestedNamespacesXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistNestedNamespacesXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..ba48ce3b --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistNestedNamespacesXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/nested-namespaces/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistNewlineAfterOpenTagXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistNewlineAfterOpenTagXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..92e5a7cf --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistNewlineAfterOpenTagXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/newline-after-open-tag/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistNoParentScopeXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistNoParentScopeXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..3fa369ff --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistNoParentScopeXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/no-parent-scope/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistObjectOperatorSpacingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistObjectOperatorSpacingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..8af87531 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistObjectOperatorSpacingXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/object-operator-spacing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPCompatibilityXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPCompatibilityXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..1596d205 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPCompatibilityXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/php-compatibility/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPEchoTagXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPEchoTagXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..5a611261 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPEchoTagXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/php-echo-tag/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPOpenTagXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPOpenTagXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..96ee7edd --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPOpenTagXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/php-open-tag/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPShortTagXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPShortTagXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..b2da6f84 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistPHPShortTagXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/php-short-tag/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistParentMemberReferenceXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistParentMemberReferenceXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..47106a18 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistParentMemberReferenceXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/parent-member-references/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistParenthesesSpacingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistParenthesesSpacingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..3a51c3c5 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistParenthesesSpacingXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/parentheses-spacing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistParseStrUseXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistParseStrUseXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..e8a1a49a --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistParseStrUseXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/parse_str-use/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..7f1ae94f --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/plus-operator-on-strings/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistPregQuoteMisuseXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistPregQuoteMisuseXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..47838fdd --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistPregQuoteMisuseXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/preg_quote-misuse/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..951eb4d5 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/ragged-classtree-edge/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistReusedAsIteratorXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistReusedAsIteratorXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..1fc8e637 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistReusedAsIteratorXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/reused-as-iterator/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorReferenceXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorReferenceXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..29c0d667 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorReferenceXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/reused-iterator-reference/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..9ce45e0d --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/reused-iterator/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistSelfMemberReferenceXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistSelfMemberReferenceXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..dcc136a6 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistSelfMemberReferenceXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/self-member-reference/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistSemicolonSpacingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistSemicolonSpacingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..3f18524f --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistSemicolonSpacingXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/semicolon-spacing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistSlownessXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistSlownessXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..f44be82a --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistSlownessXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/slowness/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistStaticThisXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistStaticThisXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..901ce548 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistStaticThisXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/static-this/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistTautologicalExpressionXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistTautologicalExpressionXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..af4f5c56 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistTautologicalExpressionXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/tautological-expression/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistThisReassignmentXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistThisReassignmentXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..1eef25c4 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistThisReassignmentXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/this-reassignment/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistToStringExceptionXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistToStringExceptionXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..472e4dcb --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistToStringExceptionXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/__toString-exception/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistTodoCommentXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistTodoCommentXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..1a056c65 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistTodoCommentXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/todo-comment/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..87653ff0 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/unary-postfix-expression-spacing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistUnaryPrefixExpressionSpacingXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnaryPrefixExpressionSpacingXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..c80b3bac --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnaryPrefixExpressionSpacingXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/unary-prefix-expression-spacing/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistUndeclaredVariableXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistUndeclaredVariableXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..c5c5a219 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistUndeclaredVariableXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/undeclared-variable/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..462857cb --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/unexpected-return-value/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistUnnecessaryFinalModifierXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnnecessaryFinalModifierXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..206a590f --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnnecessaryFinalModifierXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/unnecessary-final-modifier/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistUnsafeDynamicStringXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnsafeDynamicStringXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..1a6d8dcb --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistUnsafeDynamicStringXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/unsafe-dynamic-string/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..0f7d1cd9 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase.php @@ -0,0 +1,11 @@ +executeTestsInDirectory( + dirname(__FILE__).'/useless-overriding-method/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..46e9a140 --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/variable-variable/'); + } + +} diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistXHPASTLinterRuleTestCase.php new file mode 100644 index 00000000..342d059b --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistXHPASTLinterRuleTestCase.php @@ -0,0 +1,32 @@ +setRules(array($this->getLinterRule())); + } + + /** + * Returns an instance of the linter rule being tested. + * + * @return ArcanistXHPASTLinterRule + */ + protected function getLinterRule() { + $class = get_class($this); + $matches = null; + + if (!preg_match('/^(\w+XHPASTLinterRule)TestCase$/', $class, $matches) || + !is_subclass_of($matches[1], 'ArcanistXHPASTLinterRule')) { + throw new Exception(pht('Unable to infer linter rule class name.')); + } + + return newv($matches[1], array()); + } + +} diff --git a/src/lint/linter/__tests__/xhpast/lamba-func-function.lint-test b/src/lint/linter/xhpast/rules/__tests__/__lambda_func-function/lamba-func-function.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/lamba-func-function.lint-test rename to src/lint/linter/xhpast/rules/__tests__/__lambda_func-function/lamba-func-function.lint-test diff --git a/src/lint/linter/__tests__/xhpast/tostring-exception.lint-test b/src/lint/linter/xhpast/rules/__tests__/__toString-exception/__toString-exception.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/tostring-exception.lint-test rename to src/lint/linter/xhpast/rules/__tests__/__toString-exception/__toString-exception.lint-test diff --git a/src/lint/linter/__tests__/xhpast/alias-functions.lint-test b/src/lint/linter/xhpast/rules/__tests__/alias-functions/alias-functions.lint-test similarity index 76% rename from src/lint/linter/__tests__/xhpast/alias-functions.lint-test rename to src/lint/linter/xhpast/rules/__tests__/alias-functions/alias-functions.lint-test index ae3a4cf9..d2834524 100644 --- a/src/lint/linter/__tests__/xhpast/alias-functions.lint-test +++ b/src/lint/linter/xhpast/rules/__tests__/alias-functions/alias-functions.lint-test @@ -1,17 +1,17 @@ $y); array($x=>$y); array($x =>$y); array($x=> $y); array( $x => $y, ); array( $x => $y, ); array( $x=>$y, ); ~~~~~~~~~~ -warning:4:3 -warning:4:5 -warning:5:4 -warning:6:3 -warning:13:9 -warning:14:10 -warning:15:9 -warning:25:5 +warning:4:9 +warning:5:10 +warning:6:9 +warning:16:5 ~~~~~~~~~~ $y); array($x => $y); array($x => $y); array($x => $y); array( $x => $y, ); array( $x => $y, ); array( $x => $y, ); diff --git a/src/lint/linter/__tests__/xhpast/space-around-operators.lint-test b/src/lint/linter/xhpast/rules/__tests__/binary-expression-spacing/binary-expression-spacing.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/space-around-operators.lint-test rename to src/lint/linter/xhpast/rules/__tests__/binary-expression-spacing/binary-expression-spacing.lint-test diff --git a/src/lint/linter/__tests__/xhpast/blacklisted.lint-test b/src/lint/linter/xhpast/rules/__tests__/blacklisted-function/blacklisted-function.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/blacklisted.lint-test rename to src/lint/linter/xhpast/rules/__tests__/blacklisted-function/blacklisted-function.lint-test diff --git a/src/lint/linter/__tests__/xhpast/creative-brace-use.lint-test b/src/lint/linter/xhpast/rules/__tests__/brace-formatting/brace-formatting.lint-test similarity index 93% rename from src/lint/linter/__tests__/xhpast/creative-brace-use.lint-test rename to src/lint/linter/xhpast/rules/__tests__/brace-formatting/brace-formatting.lint-test index 1832ec1e..725773f3 100644 --- a/src/lint/linter/__tests__/xhpast/creative-brace-use.lint-test +++ b/src/lint/linter/xhpast/rules/__tests__/brace-formatting/brace-formatting.lint-test @@ -1,85 +1,87 @@ myfunc(&$myvar); $this->myfunc($myvar); MyClass::myfunc(&$myvar); MyClass::myfunc($myvar); while (testfunc($var1, &$var2, $var3, &$var4) === false) {} sprintf('0%o', 0777 & $p); $foo(&$myvar); array_walk(array(), function () use (&$x) {}); MyClass::myfunc(array(&$x, &$y)); ~~~~~~~~~~ -error:3:7 XHP19 error:10:8 error:13:15 error:16:17 error:19:24 error:19:39 error:23:6 diff --git a/src/lint/linter/__tests__/xhpast/cast-spacing.lint-test b/src/lint/linter/xhpast/rules/__tests__/cast-spacing/cast-spacing.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/cast-spacing.lint-test rename to src/lint/linter/xhpast/rules/__tests__/cast-spacing/cast-spacing.lint-test diff --git a/src/lint/linter/__tests__/phlxhp/extends-phobject.lint-test b/src/lint/linter/xhpast/rules/__tests__/class-extends-object/extends-phobject.lint-test similarity index 88% rename from src/lint/linter/__tests__/phlxhp/extends-phobject.lint-test rename to src/lint/linter/xhpast/rules/__tests__/class-extends-object/extends-phobject.lint-test index 0cd492b3..3856360f 100644 --- a/src/lint/linter/__tests__/phlxhp/extends-phobject.lint-test +++ b/src/lint/linter/xhpast/rules/__tests__/class-extends-object/extends-phobject.lint-test @@ -1,7 +1,7 @@ m( $x, $y, $z); for ( $ii = 0; $ii < 1; $ii++ ) {} foreach ( $x as $y ) {} function q( $x ) {} final class X { public function f( $y ) {} } foreach ( $z as $k => $v ) {} some_call( /* respect authorial intent */ $b, $a // if comments are present ); ~~~~~~~~~~ warning:3:5 warning:3:8 warning:4:3 warning:5:3 warning:7:21 warning:7:24 warning:13:6 warning:13:30 warning:14:10 warning:14:19 warning:15:12 warning:15:15 -error:16:13 XHP19 Class-Filename Mismatch warning:17:21 warning:17:24 warning:19:10 warning:19:25 ~~~~~~~~~~ m( $x, $y, $z); for ($ii = 0; $ii < 1; $ii++) {} foreach ($x as $y) {} function q($x) {} final class X { public function f($y) {} } foreach ($z as $k => $v) {} some_call( /* respect authorial intent */ $b, $a // if comments are present ); diff --git a/src/lint/linter/__tests__/xhpast/parse_str-use.lint-test b/src/lint/linter/xhpast/rules/__tests__/parse_str-use/parse_str-use.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/parse_str-use.lint-test rename to src/lint/linter/xhpast/rules/__tests__/parse_str-use/parse_str-use.lint-test diff --git a/src/lint/linter/__tests__/xhpast/conditional-usage.lint-test b/src/lint/linter/xhpast/rules/__tests__/php-compatibility/conditional-usage.lint-test similarity index 97% rename from src/lint/linter/__tests__/xhpast/conditional-usage.lint-test rename to src/lint/linter/xhpast/rules/__tests__/php-compatibility/conditional-usage.lint-test index 3c0fdf24..6b8a50de 100644 --- a/src/lint/linter/__tests__/xhpast/conditional-usage.lint-test +++ b/src/lint/linter/xhpast/rules/__tests__/php-compatibility/conditional-usage.lint-test @@ -1,33 +1,32 @@ m()[0]; final class SomeClass extends Phobject { public function someMethod() { return function () { $this->someOtherMethod(); }; } public static function someStaticMethod() { return function () { self::someOtherMethod(); }; } } ~~~~~~~~~~ error:3:5 error:4:9 -error:9:13 error:12:7 error:18:7 ~~~~~~~~~~ ~~~~~~~~~~ { "config": { "xhpast.php-version": "5.3.0" } } diff --git a/src/lint/linter/__tests__/xhpast/php54-incompat.lint-test b/src/lint/linter/xhpast/rules/__tests__/php-compatibility/php54-incompat.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/php54-incompat.lint-test rename to src/lint/linter/xhpast/rules/__tests__/php-compatibility/php54-incompat.lint-test diff --git a/src/lint/linter/__tests__/xhpast/windows.lint-test b/src/lint/linter/xhpast/rules/__tests__/php-compatibility/windows.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/windows.lint-test rename to src/lint/linter/xhpast/rules/__tests__/php-compatibility/windows.lint-test diff --git a/src/lint/linter/__tests__/xhpast/php-tags-echo-form.lint-test b/src/lint/linter/xhpast/rules/__tests__/php-echo-tag/php-echo-tag.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/php-tags-echo-form.lint-test rename to src/lint/linter/xhpast/rules/__tests__/php-echo-tag/php-echo-tag.lint-test diff --git a/src/lint/linter/__tests__/xhpast/php-tags-bad.lint-test b/src/lint/linter/xhpast/rules/__tests__/php-open-tag/php-open-tag.lint-test similarity index 86% rename from src/lint/linter/__tests__/xhpast/php-tags-bad.lint-test rename to src/lint/linter/xhpast/rules/__tests__/php-open-tag/php-open-tag.lint-test index dbe6aa14..7fb52e73 100644 --- a/src/lint/linter/__tests__/xhpast/php-tags-bad.lint-test +++ b/src/lint/linter/xhpast/rules/__tests__/php-open-tag/php-open-tag.lint-test @@ -1,12 +1,11 @@ garbage garbage ~~~~~~~~~~ -disabled:1:1 error:1:1 ~~~~~~~~~~ garbage garbage diff --git a/src/lint/linter/__tests__/xhpast/php-tags-good.lint-test b/src/lint/linter/xhpast/rules/__tests__/php-open-tag/php-tags-good.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/php-tags-good.lint-test rename to src/lint/linter/xhpast/rules/__tests__/php-open-tag/php-tags-good.lint-test diff --git a/src/lint/linter/__tests__/xhpast/php-tags-script.lint-test b/src/lint/linter/xhpast/rules/__tests__/php-open-tag/php-tags-script.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/php-tags-script.lint-test rename to src/lint/linter/xhpast/rules/__tests__/php-open-tag/php-tags-script.lint-test diff --git a/src/lint/linter/__tests__/xhpast/php-tags-short.lint-test b/src/lint/linter/xhpast/rules/__tests__/php-short-tag/php-short-tag.lint-test similarity index 77% rename from src/lint/linter/__tests__/xhpast/php-tags-short.lint-test rename to src/lint/linter/xhpast/rules/__tests__/php-short-tag/php-short-tag.lint-test index 11d82b5c..9c5fb0c5 100644 --- a/src/lint/linter/__tests__/xhpast/php-tags-short.lint-test +++ b/src/lint/linter/xhpast/rules/__tests__/php-short-tag/php-short-tag.lint-test @@ -1,9 +1,6 @@ &$v) {} $v++; // Reuse of $v } function key_value2() { $ar = array(); foreach ($ar as $k => $v) {} $v++; } function unset() { $ar = array(); foreach ($ar as &$a) {} unset($a); $a++; } function unset2() { $ar = array(); foreach ($ar as &$a) {} $a++; // Reuse of $a unset($a); } function twice_ref() { $ar1 = array(); $ar2 = array(); foreach ($ar1 as &$b) {} foreach ($ar2 as &$b) {} } function assign_ref(&$a) { $ar = array(); foreach ($ar as &$b) {} $b = &$a; } function assign_ref2(&$a) { $ar = array(); foreach ($ar as &$b) {} $b = &$a; $c = $b; } function use_inside() { $ar = array(); foreach ($ar as &$a) { $a++; } } function variable_variable() { $ar = array(); foreach ($ar as &$$a) {} $a++; $$a++; } function closure1() { $ar = array(); foreach ($ar as &$a) {} function ($a) { $a++; }; } function closure2() { function () { $ar = array(); foreach ($ar as &$a) {} }; $a++; } function closure3() { function () { $ar = array(); foreach ($ar as &$a) {} $a++; // Reuse of $a }; } function closure4() { $ar = array(); foreach ($ar as &$a) {} function ($a) { unset($a); }; $a++; // Reuse of $a } ~~~~~~~~~~ warning:6:3 warning:12:8 warning:18:10 warning:27:20 warning:33:3 warning:52:3 -error:85:20 -error:87:3 warning:110:5 warning:120:3 diff --git a/src/lint/linter/__tests__/xhpast/reused-iterators.lint-test b/src/lint/linter/xhpast/rules/__tests__/reused-iterator/reused-iterator.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/reused-iterators.lint-test rename to src/lint/linter/xhpast/rules/__tests__/reused-iterator/reused-iterator.lint-test diff --git a/src/lint/linter/__tests__/xhpast/self-member-references-php53.lint-test b/src/lint/linter/xhpast/rules/__tests__/self-member-reference/php53.lint-test similarity index 96% rename from src/lint/linter/__tests__/xhpast/self-member-references-php53.lint-test rename to src/lint/linter/xhpast/rules/__tests__/self-member-reference/php53.lint-test index 5d06f638..f785f825 100644 --- a/src/lint/linter/__tests__/xhpast/self-member-references-php53.lint-test +++ b/src/lint/linter/xhpast/rules/__tests__/self-member-reference/php53.lint-test @@ -1,19 +1,18 @@ f(); } public static function v() { $this->f(); } } ~~~~~~~~~~ -error:3:13 XHP19 Class-Filename Mismatch -error:8:5 Use of $this in a static method. +error:8:5 diff --git a/src/lint/linter/__tests__/xhpast/tautological-expressions.lint-test b/src/lint/linter/xhpast/rules/__tests__/tautological-expression/tautological-expression.lint-test similarity index 100% rename from src/lint/linter/__tests__/xhpast/tautological-expressions.lint-test rename to src/lint/linter/xhpast/rules/__tests__/tautological-expression/tautological-expression.lint-test diff --git a/src/lint/linter/xhpast/rules/__tests__/this-reassignment/class.lint-test b/src/lint/linter/xhpast/rules/__tests__/this-reassignment/class.lint-test new file mode 100644 index 00000000..e4081b0d --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/this-reassignment/class.lint-test @@ -0,0 +1,8 @@ + $m) {} $a++; $b++; $c++; $d++; $e++; $f++; $g++; $h++; $i++; $j++; $k++; $l++; $m++; $this++; $n++; // Only one that isn't declared. extract(z()); $o++; } function g($q) { $$q = x(); $r = y(); } final class C { public function m() { $a++; x($b); $c[] = 3; $d->v = 4; $a = $f; } } function worst() { global $$x; $y++; } function superglobals() { $GLOBALS[$_FILES[$_POST[$this]]]++; } function ref_foreach($x) { foreach ($x as &$z) {} $z++; } function has_default($x = 0) { $x++; } function declparse( $a, Q $b, Q &$c, Q $d = null, Q &$e = null, $f = null, $g = null, &$h = null, &$i = null) { $a++; $b++; $c++; $d++; $e++; $f++; $g++; $h++; $i++; $j++; } function declparse_a(Q $a) { $a++; } function declparse_b(Q &$a) { $a++; } function declparse_c(Q $a = null) { $a++; } function declparse_d(Q &$a = null) { $a++; } function declparse_e($a) { $a++; } function declparse_f(&$a) { $a++; } function declparse_g($a = null) { $a++; } function declparse_h(&$a = null) { $a++; } function static_class() { SomeClass::$x; } function instance_class() { $a = $this->$x; } function exception_vars() { try { // This is intentionally left blank. } catch (Exception $y) { $y++; } } function nonuse() { isset($x); empty($y); $x++; } function twice() { $y++; $y++; } function more_exceptions() { try { // This is intentionally left blank. } catch (Exception $a) { $a++; } catch (Exception $b) { $b++; } } abstract class P { abstract public function q(); } function x() { $lib = $_SERVER['PHP_ROOT'].'/lib/titan/display/read/init.php'; require_once $lib; f(((($lib)))); // Tests for paren expressions. f(((($lub)))); } final class A { public function foo($a) { $im_service = foo($a); if ($im_servce === false) { return 1; } return 2; } } function arrow($o, $x) { echo $o->{$x->{$x->{$x.$x->{$x}}.$x}}; } function strings() { $a = 1; echo "$a"; echo "$b"; } function catchy() { try { dangerous(); } catch (Exception $ex) { $y->z(); } } function some_func($x, $y) { $func = function ($z) use ($x) { echo $x; echo $y; echo $z; }; } function some_func($x, $y) { $func = function ($z) use ($x) { echo "$x/$y/$z"; }; } ~~~~~~~~~~ -warning:9:3 error:28:3 -error:30:3 -error:36:3 error:42:5 error:43:7 error:44:5 error:45:5 error:46:10 -warning:51:3 -error:51:10 worst ever -warning:61:3 error:87:3 This stuff is basically testing the lexer/parser for function decls. error:104:15 Variables in instance derefs should be checked, static should not. error:118:3 isset() and empty() should not trigger errors. error:122:3 Should only warn once in this function. error:144:8 error:150:9 error:164:9 error:171:5 error:178:10 error:185:14 diff --git a/src/lint/linter/xhpast/rules/__tests__/unexpected-return-value/construct.lint-test b/src/lint/linter/xhpast/rules/__tests__/unexpected-return-value/construct.lint-test new file mode 100644 index 00000000..894c3f6b --- /dev/null +++ b/src/lint/linter/xhpast/rules/__tests__/unexpected-return-value/construct.lint-test @@ -0,0 +1,8 @@ +buildTestEngines(); foreach ($engines as $engine) { if ($engine->supportsRunAllTests()) { return true; } } return false; } public function buildTestEngines() { $working_copy = $this->getWorkingCopy(); $config_path = $working_copy->getProjectPath('.arcunit'); if (!Filesystem::pathExists($config_path)) { throw new ArcanistUsageException( pht( "Unable to find '%s' file to configure test engines. Create an ". "'%s' file in the root directory of the working copy.", '.arcunit', '.arcunit')); } $data = Filesystem::readFile($config_path); $config = null; try { $config = phutil_json_decode($data); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException( pht( "Expected '%s' file to be a valid JSON file, but ". "failed to decode '%s'.", '.arcunit', $config_path), $ex); } $test_engines = $this->loadAvailableTestEngines(); try { PhutilTypeSpec::checkMap( $config, array( 'engines' => 'map>', )); } catch (PhutilTypeCheckException $ex) { throw new PhutilProxyException( pht("Error in parsing '%s' file.", $config_path), $ex); } $built_test_engines = array(); $all_paths = $this->getPaths(); foreach ($config['engines'] as $name => $spec) { $type = idx($spec, 'type'); if ($type !== null) { if (empty($test_engines[$type])) { throw new ArcanistUsageException( pht( "Test engine '%s' specifies invalid type '%s'. ". "Available test engines are: %s.", $name, $type, implode(', ', array_keys($test_engines)))); } $test_engine = clone $test_engines[$type]; } else { // We'll raise an error below about the invalid "type" key. // TODO: Can we just do the type check first, and simplify this a bit? $test_engine = null; } try { PhutilTypeSpec::checkMap( $spec, array( 'type' => 'string', 'include' => 'optional regex | list', 'exclude' => 'optional regex | list', )); } catch (PhutilTypeCheckException $ex) { throw new PhutilProxyException( pht( "Error in parsing '%s' file, for test engine '%s'.", '.arcunit', $name), $ex); } if ($all_paths) { $include = (array)idx($spec, 'include', array()); $exclude = (array)idx($spec, 'exclude', array()); $paths = $this->matchPaths( $all_paths, $include, $exclude); $test_engine->setPaths($paths); } $built_test_engines[] = $test_engine; } return $built_test_engines; } public function run() { + $renderer = $this->renderer; + $this->setRenderer(null); + $paths = $this->getPaths(); // If we are running with `--everything` then `$paths` will be `null`. if (!$paths) { $paths = array(); } - $engines = $this->buildTestEngines(); - $results = array(); - $exceptions = array(); + $engines = $this->buildTestEngines(); + $all_results = array(); + $exceptions = array(); foreach ($engines as $engine) { $engine ->setWorkingCopy($this->getWorkingCopy()) - ->setEnableCoverage($this->getEnableCoverage()); + ->setEnableCoverage($this->getEnableCoverage()) + ->setRenderer($renderer); // TODO: At some point, maybe we should emit a warning here if an engine // doesn't support `--everything`, to reduce surprise when `--everything` // does not really mean `--everything`. if ($engine->supportsRunAllTests()) { $engine->setRunAllTests($this->getRunAllTests()); } try { // TODO: Type check the results. - $results[] = $engine->run(); + $results = $engine->run(); + $all_results[] = $results; + + foreach ($results as $result) { + // If the proxied engine renders its own test results then there + // is no need to render them again here. + if (!$engine->shouldEchoTestResults()) { + echo $renderer->renderUnitResult($result); + } + } } catch (ArcanistNoEffectException $ex) { $exceptions[] = $ex; } } - if (!$results) { + if (!$all_results) { // If all engines throw an `ArcanistNoEffectException`, then we should // preserve this behavior. throw new ArcanistNoEffectException(pht('No tests to run.')); } - return array_mergev($results); + return array_mergev($all_results); + } + + public function shouldEchoTestResults() { + return false; } private function loadAvailableTestEngines() { return id(new PhutilClassMapQuery()) ->setAncestorClass('ArcanistUnitTestEngine') ->setUniqueMethod('getEngineConfigurationName', true) ->execute(); } /** * TODO: This is copied from @{class:ArcanistConfigurationDrivenLintEngine}. */ private function matchPaths(array $paths, array $include, array $exclude) { $match = array(); foreach ($paths as $path) { $keep = false; if (!$include) { $keep = true; } else { foreach ($include as $rule) { if (preg_match($rule, $path)) { $keep = true; break; } } } if (!$keep) { continue; } if ($exclude) { foreach ($exclude as $rule) { if (preg_match($rule, $path)) { continue 2; } } } $match[] = $path; } return $match; } } diff --git a/src/unit/engine/ArcanistUnitTestEngine.php b/src/unit/engine/ArcanistUnitTestEngine.php index e105f216..b54bafea 100644 --- a/src/unit/engine/ArcanistUnitTestEngine.php +++ b/src/unit/engine/ArcanistUnitTestEngine.php @@ -1,118 +1,98 @@ supportsRunAllTests() && $run_all_tests) { throw new Exception( pht( "Engine '%s' does not support %s.", get_class($this), '--everything')); } $this->runAllTests = $run_all_tests; return $this; } final public function getRunAllTests() { return $this->runAllTests; } protected function supportsRunAllTests() { return false; } final public function setConfigurationManager( ArcanistConfigurationManager $configuration_manager) { $this->configurationManager = $configuration_manager; return $this; } final public function getConfigurationManager() { return $this->configurationManager; } final public function setWorkingCopy( ArcanistWorkingCopyIdentity $working_copy) { $this->workingCopy = $working_copy; return $this; } final public function getWorkingCopy() { return $this->workingCopy; } final public function setPaths(array $paths) { $this->paths = $paths; return $this; } final public function getPaths() { return $this->paths; } - final public function setArguments(array $arguments) { - $this->arguments = $arguments; - return $this; - } - - final public function getArgument($key, $default = null) { - return idx($this->arguments, $key, $default); - } - - final public function setEnableAsyncTests($enable_async_tests) { - $this->enableAsyncTests = $enable_async_tests; - return $this; - } - - final public function getEnableAsyncTests() { - return $this->enableAsyncTests; - } - final public function setEnableCoverage($enable_coverage) { $this->enableCoverage = $enable_coverage; return $this; } final public function getEnableCoverage() { return $this->enableCoverage; } - final public function setRenderer(ArcanistUnitRenderer $renderer) { + final public function setRenderer(ArcanistUnitRenderer $renderer = null) { $this->renderer = $renderer; return $this; } abstract public function run(); /** * Modify the return value of this function in the child class, if you do * not need to echo the test results after all the tests have been run. This * is the case for example when the child class prints the tests results * while the tests are running. */ public function shouldEchoTestResults() { return true; } } diff --git a/src/unit/engine/PhutilUnitTestEngine.php b/src/unit/engine/PhutilUnitTestEngine.php index 110f8e35..42481434 100644 --- a/src/unit/engine/PhutilUnitTestEngine.php +++ b/src/unit/engine/PhutilUnitTestEngine.php @@ -1,228 +1,224 @@ getRunAllTests()) { $run_tests = $this->getAllTests(); } else { $run_tests = $this->getTestsForPaths(); } if (!$run_tests) { throw new ArcanistNoEffectException(pht('No tests to run.')); } $enable_coverage = $this->getEnableCoverage(); if ($enable_coverage !== false) { if (!function_exists('xdebug_start_code_coverage')) { if ($enable_coverage === true) { throw new ArcanistUsageException( pht( 'You specified %s but %s is not available, so '. 'coverage can not be enabled for %s.', '--coverage', 'XDebug', __CLASS__)); } } else { $enable_coverage = true; } } $test_cases = array(); foreach ($run_tests as $test_class) { $test_case = newv($test_class, array()) ->setEnableCoverage($enable_coverage) ->setWorkingCopy($this->getWorkingCopy()); if ($this->getPaths()) { $test_case->setPaths($this->getPaths()); } if ($this->renderer) { $test_case->setRenderer($this->renderer); } $test_cases[] = $test_case; } foreach ($test_cases as $test_case) { $test_case->willRunTestCases($test_cases); } $results = array(); foreach ($test_cases as $test_case) { $results[] = $test_case->run(); } $results = array_mergev($results); foreach ($test_cases as $test_case) { $test_case->didRunTestCases($test_cases); } return $results; } private function getAllTests() { $project_root = $this->getWorkingCopy()->getProjectRoot(); $symbols = id(new PhutilSymbolLoader()) ->setType('class') ->setAncestorClass('PhutilTestCase') ->setConcreteOnly(true) ->selectSymbolsWithoutLoading(); $in_working_copy = array(); $run_tests = array(); foreach ($symbols as $symbol) { if (!preg_match('@(?:^|/)__tests__/@', $symbol['where'])) { continue; } $library = $symbol['library']; if (!isset($in_working_copy[$library])) { $library_root = phutil_get_library_root($library); $in_working_copy[$library] = Filesystem::isDescendant( $library_root, $project_root); } if ($in_working_copy[$library]) { $run_tests[] = $symbol['name']; } } return $run_tests; } /** * Retrieve all relevant test cases. * * Looks for any class that extends @{class:PhutilTestCase} inside a * `__tests__` directory in any parent directory of every affected file. * * The idea is that "infrastructure/__tests__/" tests defines general tests * for all of "infrastructure/", and those tests run for any change in * "infrastructure/". However, "infrastructure/concrete/rebar/__tests__/" * defines more specific tests that run only when "rebar/" (or some * subdirectory) changes. * * @return list The names of the test case classes to be executed. */ private function getTestsForPaths() { $look_here = $this->getTestPaths(); $run_tests = array(); foreach ($look_here as $path_info) { $library = $path_info['library']; $path = $path_info['path']; $symbols = id(new PhutilSymbolLoader()) ->setType('class') ->setLibrary($library) ->setPathPrefix($path) ->setAncestorClass('PhutilTestCase') ->setConcreteOnly(true) ->selectAndLoadSymbols(); foreach ($symbols as $symbol) { $run_tests[$symbol['name']] = true; } } return array_keys($run_tests); } /** * Returns the paths in which we should look for tests to execute. * * @return list A list of paths in which to search for test cases. */ public function getTestPaths() { $root = $this->getWorkingCopy()->getProjectRoot(); $paths = array(); foreach ($this->getPaths() as $path) { $library_root = phutil_get_library_root_for_path($path); if (!$library_root) { continue; } $library_name = phutil_get_library_name_for_root($library_root); if (!$library_name) { throw new Exception( pht( "Attempting to run unit tests on a libphutil library which has ". "not been loaded, at:\n\n". " %s\n\n". "This probably means one of two things:\n\n". " - You may need to add this library to %s.\n". " - You may be running tests on a copy of libphutil or ". "arcanist using a different copy of libphutil or arcanist. ". "This operation is not supported.\n", $library_root, '.arcconfig.')); } $path = Filesystem::resolvePath($path, $root); $library_path = Filesystem::readablePath($path, $library_root); if (!Filesystem::isDescendant($path, $library_root)) { // We have encountered some kind of symlink maze -- for instance, $path // is some symlink living outside the library that links into some file // inside the library. Just ignore these cases, since the affected file // does not actually lie within the library. continue; } if (is_file($path) && preg_match('@(?:^|/)__tests__/@', $path)) { $paths[$library_name.':'.$library_path] = array( 'library' => $library_name, 'path' => $library_path, ); continue; } foreach (Filesystem::walkToRoot($path, $library_root) as $subpath) { if ($subpath == $library_root) { $paths[$library_name.':.'] = array( 'library' => $library_name, 'path' => '__tests__/', ); } else { $library_subpath = Filesystem::readablePath($subpath, $library_root); $paths[$library_name.':'.$library_subpath] = array( 'library' => $library_name, 'path' => $library_subpath.'/__tests__/', ); } } } return $paths; } - public function shouldEchoTestResults() { - return !$this->renderer; - } - } diff --git a/src/workflow/ArcanistUnitWorkflow.php b/src/workflow/ArcanistUnitWorkflow.php index 04d8c8e0..a1aadd3b 100644 --- a/src/workflow/ArcanistUnitWorkflow.php +++ b/src/workflow/ArcanistUnitWorkflow.php @@ -1,435 +1,427 @@ array( 'param' => 'revision', 'help' => pht( 'Run unit tests covering changes since a specific revision.'), 'supports' => array( 'git', 'hg', ), 'nosupport' => array( 'svn' => pht( 'Arc unit does not currently support %s in SVN.', '--rev'), ), ), 'engine' => array( 'param' => 'classname', 'help' => pht('Override configured unit engine for this project.'), ), 'coverage' => array( 'help' => pht('Always enable coverage information.'), 'conflicts' => array( 'no-coverage' => null, ), ), 'no-coverage' => array( 'help' => pht('Always disable coverage information.'), ), 'detailed-coverage' => array( 'help' => pht( 'Show a detailed coverage report on the CLI. Implies %s.', '--coverage'), ), 'json' => array( 'help' => pht('Report results in JSON format.'), ), 'output' => array( 'param' => 'format', 'help' => pht( "With 'full', show full pretty report (Default). ". "With 'json', report results in JSON format. ". "With 'ugly', use uglier (but more efficient) JSON formatting. ". "With 'none', don't print results."), 'conflicts' => array( 'json' => pht('Only one output format allowed'), 'ugly' => pht('Only one output format allowed'), ), ), 'target' => array( 'param' => 'phid', 'help' => pht( '(PROTOTYPE) Record a copy of the test results on the specified '. 'Harbormaster build target.'), ), 'everything' => array( 'help' => pht('Run every test.'), 'conflicts' => array( 'rev' => pht('%s runs all tests.', '--everything'), ), ), 'ugly' => array( 'help' => pht( 'With %s, use uglier (but more efficient) formatting.', '--json'), ), '*' => 'paths', ); } public function requiresWorkingCopy() { return true; } public function requiresRepositoryAPI() { return true; } public function requiresConduit() { return $this->shouldUploadResults(); } public function requiresAuthentication() { return $this->shouldUploadResults(); } public function getEngine() { return $this->engine; } public function run() { $working_copy = $this->getWorkingCopy(); $paths = $this->getArgument('paths'); $rev = $this->getArgument('rev'); $everything = $this->getArgument('everything'); if ($everything && $paths) { throw new ArcanistUsageException( pht( 'You can not specify paths with %s. The %s flag runs every test.', '--everything', '--everything')); } if ($everything) { $paths = iterator_to_array($this->getRepositoryApi()->getAllFiles()); } else { $paths = $this->selectPathsForWorkflow($paths, $rev); } $this->engine = $this->newUnitTestEngine($this->getArgument('engine')); if ($everything) { $this->engine->setRunAllTests(true); } else { $this->engine->setPaths($paths); } - $this->engine->setArguments($this->getPassthruArgumentsAsMap('unit')); $renderer = new ArcanistUnitConsoleRenderer(); $this->engine->setRenderer($renderer); $enable_coverage = null; // Means "default". if ($this->getArgument('coverage') || $this->getArgument('detailed-coverage')) { $enable_coverage = true; } else if ($this->getArgument('no-coverage')) { $enable_coverage = false; } $this->engine->setEnableCoverage($enable_coverage); - // Enable possible async tests only for 'arc diff' not 'arc unit' - if ($this->getParentWorkflow()) { - $this->engine->setEnableAsyncTests(true); - } else { - $this->engine->setEnableAsyncTests(false); - } - $results = $this->engine->run(); $this->validateUnitEngineResults($this->engine, $results); $this->testResults = $results; $console = PhutilConsole::getConsole(); $output_format = $this->getOutputFormat(); if ($output_format !== 'full') { $console->disableOut(); } $unresolved = array(); $coverage = array(); foreach ($results as $result) { $result_code = $result->getResult(); if ($this->engine->shouldEchoTestResults()) { $console->writeOut('%s', $renderer->renderUnitResult($result)); } if ($result_code != ArcanistUnitTestResult::RESULT_PASS) { $unresolved[] = $result; } if ($result->getCoverage()) { foreach ($result->getCoverage() as $file => $report) { $coverage[$file][] = $report; } } } if ($coverage) { $file_coverage = array_fill_keys( $paths, 0); $file_reports = array(); foreach ($coverage as $file => $reports) { $report = ArcanistUnitTestResult::mergeCoverage($reports); $cov = substr_count($report, 'C'); $uncov = substr_count($report, 'U'); if ($cov + $uncov) { $coverage = $cov / ($cov + $uncov); } else { $coverage = 0; } $file_coverage[$file] = $coverage; $file_reports[$file] = $report; } $console->writeOut("\n__%s__\n", pht('COVERAGE REPORT')); asort($file_coverage); foreach ($file_coverage as $file => $coverage) { $console->writeOut( " **%s%%** %s\n", sprintf('% 3d', (int)(100 * $coverage)), $file); $full_path = $working_copy->getProjectRoot().'/'.$file; if ($this->getArgument('detailed-coverage') && Filesystem::pathExists($full_path) && is_file($full_path) && array_key_exists($file, $file_reports)) { $console->writeOut( '%s', $this->renderDetailedCoverageReport( Filesystem::readFile($full_path), $file_reports[$file])); } } } $this->unresolvedTests = $unresolved; $overall_result = self::RESULT_OKAY; foreach ($results as $result) { $result_code = $result->getResult(); if ($result_code == ArcanistUnitTestResult::RESULT_FAIL || $result_code == ArcanistUnitTestResult::RESULT_BROKEN) { $overall_result = self::RESULT_FAIL; break; } else if ($result_code == ArcanistUnitTestResult::RESULT_UNSOUND) { $overall_result = self::RESULT_UNSOUND; } } if ($output_format !== 'full') { $console->enableOut(); } $data = array_values(mpull($results, 'toDictionary')); switch ($output_format) { case 'ugly': $console->writeOut('%s', json_encode($data)); break; case 'json': $json = new PhutilJSON(); $console->writeOut('%s', $json->encodeFormatted($data)); break; case 'full': // already printed break; case 'none': // do nothing break; } $target_phid = $this->getArgument('target'); if ($target_phid) { $this->uploadTestResults($target_phid, $overall_result, $results); } return $overall_result; } public function getUnresolvedTests() { return $this->unresolvedTests; } public function getTestResults() { return $this->testResults; } private function renderDetailedCoverageReport($data, $report) { $data = explode("\n", $data); $out = ''; $n = 0; foreach ($data as $line) { $out .= sprintf('% 5d ', $n + 1); $line = str_pad($line, 80, ' '); if (empty($report[$n])) { $c = 'N'; } else { $c = $report[$n]; } switch ($c) { case 'C': $out .= phutil_console_format( ' %s ', $line); break; case 'U': $out .= phutil_console_format( ' %s ', $line); break; case 'X': $out .= phutil_console_format( ' %s ', $line); break; default: $out .= ' '.$line.' '; break; } $out .= "\n"; $n++; } return $out; } private function getOutputFormat() { if ($this->getArgument('ugly')) { return 'ugly'; } if ($this->getArgument('json')) { return 'json'; } $format = $this->getArgument('output'); $known_formats = array( 'none' => 'none', 'json' => 'json', 'ugly' => 'ugly', 'full' => 'full', ); return idx($known_formats, $format, 'full'); } /** * Raise a tailored error when a unit test engine returns results in an * invalid format. * * @param ArcanistUnitTestEngine The engine. * @param wild Results from the engine. */ private function validateUnitEngineResults( ArcanistUnitTestEngine $engine, $results) { if (!is_array($results)) { throw new Exception( pht( 'Unit test engine (of class "%s") returned invalid results when '. 'run (with method "%s"). Expected a list of "%s" objects as results.', get_class($engine), 'run()', 'ArcanistUnitTestResult')); } foreach ($results as $key => $result) { if (!($result instanceof ArcanistUnitTestResult)) { throw new Exception( pht( 'Unit test engine (of class "%s") returned invalid results when '. 'run (with method "%s"). Expected a list of "%s" objects as '. 'results, but value with key "%s" is not valid.', get_class($engine), 'run()', 'ArcanistUnitTestResult', $key)); } } } public static function getHarbormasterTypeFromResult($unit_result) { switch ($unit_result) { case self::RESULT_OKAY: case self::RESULT_SKIP: $type = 'pass'; break; default: $type = 'fail'; break; } return $type; } private function shouldUploadResults() { return ($this->getArgument('target') !== null); } private function uploadTestResults( $target_phid, $unit_result, array $unit) { // TODO: It would eventually be nice to stream test results up to the // server as we go, but just get things working for now. $message_type = self::getHarbormasterTypeFromResult($unit_result); foreach ($unit as $key => $result) { $dictionary = $result->toDictionary(); $unit[$key] = $this->getModernUnitDictionary($dictionary); } $this->getConduit()->callMethodSynchronous( 'harbormaster.sendmessage', array( 'buildTargetPHID' => $target_phid, 'unit' => array_values($unit), 'type' => $message_type, )); } }