mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-25 16:22:42 +01:00
Split the ArcanistXHPASTLinter
into modular rules
Summary: The `ArcanistXHPASTLinter` class is becoming quite bloated. This diff separates the class into one-class-per-rule, which makes everything much more modular. One downside to this decoupling is that code reuse between linter rules is much more difficult, although this only affects a very small number of linter rules. There is still some further work that could be done here, but I defer this until a later diff: - Rewrite `ArcanistPhutilXHPASTLinter` using `ArcanistXHPASTLinterRule`. - Change the unit tests so that they are truly only testing a single linter rule. - Maybe improve the way in which linter configuration options are handled. - Make it easier to keep track of the linter rule IDs (see T6859). Test Plan: `arc unit` Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: johnny-bit, epriestley, Korvin Differential Revision: https://secure.phabricator.com/D10541
This commit is contained in:
parent
cdaa0e32e4
commit
0b1acf0dc0
80 changed files with 5678 additions and 4424 deletions
|
@ -9,14 +9,20 @@
|
|||
phutil_register_library_map(array(
|
||||
'__library_version__' => 2,
|
||||
'class' => array(
|
||||
'ArcanistAliasFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php',
|
||||
'ArcanistAliasWorkflow' => 'workflow/ArcanistAliasWorkflow.php',
|
||||
'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php',
|
||||
'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php',
|
||||
'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayIndexSpacingXHPASTLinterRule.php',
|
||||
'ArcanistArraySeparatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArraySeparatorXHPASTLinterRule.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',
|
||||
'ArcanistBlacklistedFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistBlacklistedFunctionXHPASTLinterRule.php',
|
||||
'ArcanistBookmarkWorkflow' => 'workflow/ArcanistBookmarkWorkflow.php',
|
||||
'ArcanistBraceFormattingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php',
|
||||
'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php',
|
||||
'ArcanistBritishTestCase' => 'configuration/__tests__/ArcanistBritishTestCase.php',
|
||||
'ArcanistBrowseWorkflow' => 'workflow/ArcanistBrowseWorkflow.php',
|
||||
|
@ -26,30 +32,42 @@ phutil_register_library_map(array(
|
|||
'ArcanistCSSLintLinterTestCase' => 'lint/linter/__tests__/ArcanistCSSLintLinterTestCase.php',
|
||||
'ArcanistCSharpLinter' => 'lint/linter/ArcanistCSharpLinter.php',
|
||||
'ArcanistCallConduitWorkflow' => 'workflow/ArcanistCallConduitWorkflow.php',
|
||||
'ArcanistCallTimePassByReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCallTimePassByReferenceXHPASTLinterRule.php',
|
||||
'ArcanistCapabilityNotSupportedException' => 'workflow/exception/ArcanistCapabilityNotSupportedException.php',
|
||||
'ArcanistCastSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCastSpacingXHPASTLinterRule.php',
|
||||
'ArcanistCheckstyleXMLLintRenderer' => 'lint/renderer/ArcanistCheckstyleXMLLintRenderer.php',
|
||||
'ArcanistChmodLinter' => 'lint/linter/ArcanistChmodLinter.php',
|
||||
'ArcanistChmodLinterTestCase' => 'lint/linter/__tests__/ArcanistChmodLinterTestCase.php',
|
||||
'ArcanistClassFilenameMismatchXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassFilenameMismatchXHPASTLinterRule.php',
|
||||
'ArcanistClassNameLiteralXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassNameLiteralXHPASTLinterRule.php',
|
||||
'ArcanistCloseRevisionWorkflow' => 'workflow/ArcanistCloseRevisionWorkflow.php',
|
||||
'ArcanistCloseWorkflow' => 'workflow/ArcanistCloseWorkflow.php',
|
||||
'ArcanistClosingCallParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClosingCallParenthesesXHPASTLinterRule.php',
|
||||
'ArcanistClosingDeclarationParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClosingDeclarationParenthesesXHPASTLinterRule.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',
|
||||
'ArcanistCommitWorkflow' => 'workflow/ArcanistCommitWorkflow.php',
|
||||
'ArcanistCompilerLintRenderer' => 'lint/renderer/ArcanistCompilerLintRenderer.php',
|
||||
'ArcanistComprehensiveLintEngine' => 'lint/engine/ArcanistComprehensiveLintEngine.php',
|
||||
'ArcanistConcatenationOperatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConcatenationOperatorXHPASTLinterRule.php',
|
||||
'ArcanistConfiguration' => 'configuration/ArcanistConfiguration.php',
|
||||
'ArcanistConfigurationDrivenLintEngine' => 'lint/engine/ArcanistConfigurationDrivenLintEngine.php',
|
||||
'ArcanistConfigurationManager' => 'configuration/ArcanistConfigurationManager.php',
|
||||
'ArcanistConsoleLintRenderer' => 'lint/renderer/ArcanistConsoleLintRenderer.php',
|
||||
'ArcanistConstructorParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConstructorParenthesesXHPASTLinterRule.php',
|
||||
'ArcanistControlStatementSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistControlStatementSpacingXHPASTLinterRule.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',
|
||||
'ArcanistDefaultParametersXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDefaultParametersXHPASTLinterRule.php',
|
||||
'ArcanistDiffChange' => 'parser/diff/ArcanistDiffChange.php',
|
||||
'ArcanistDiffChangeType' => 'parser/diff/ArcanistDiffChangeType.php',
|
||||
'ArcanistDiffHunk' => 'parser/diff/ArcanistDiffHunk.php',
|
||||
|
@ -63,11 +81,19 @@ phutil_register_library_map(array(
|
|||
'ArcanistDifferentialDependencyGraph' => 'differential/ArcanistDifferentialDependencyGraph.php',
|
||||
'ArcanistDifferentialRevisionHash' => 'differential/constants/ArcanistDifferentialRevisionHash.php',
|
||||
'ArcanistDifferentialRevisionStatus' => 'differential/constants/ArcanistDifferentialRevisionStatus.php',
|
||||
'ArcanistDoubleQuoteXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDoubleQuoteXHPASTLinterRule.php',
|
||||
'ArcanistDownloadWorkflow' => 'workflow/ArcanistDownloadWorkflow.php',
|
||||
'ArcanistDuplicateKeysInArrayXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDuplicateKeysInArrayXHPASTLinterRule.php',
|
||||
'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDuplicateSwitchCaseXHPASTLinterRule.php',
|
||||
'ArcanistDynamicDefineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php',
|
||||
'ArcanistElseIfUsageXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php',
|
||||
'ArcanistEmptyStatementXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyStatementXHPASTLinterRule.php',
|
||||
'ArcanistEventType' => 'events/constant/ArcanistEventType.php',
|
||||
'ArcanistExitExpressionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistExitExpressionXHPASTLinterRule.php',
|
||||
'ArcanistExportWorkflow' => 'workflow/ArcanistExportWorkflow.php',
|
||||
'ArcanistExternalLinter' => 'lint/linter/ArcanistExternalLinter.php',
|
||||
'ArcanistExternalLinterTestCase' => 'lint/linter/__tests__/ArcanistExternalLinterTestCase.php',
|
||||
'ArcanistExtractUseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistExtractUseXHPASTLinterRule.php',
|
||||
'ArcanistFeatureWorkflow' => 'workflow/ArcanistFeatureWorkflow.php',
|
||||
'ArcanistFileDataRef' => 'upload/ArcanistFileDataRef.php',
|
||||
'ArcanistFileUploader' => 'upload/ArcanistFileUploader.php',
|
||||
|
@ -76,6 +102,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistFlagWorkflow' => 'workflow/ArcanistFlagWorkflow.php',
|
||||
'ArcanistFlake8Linter' => 'lint/linter/ArcanistFlake8Linter.php',
|
||||
'ArcanistFlake8LinterTestCase' => 'lint/linter/__tests__/ArcanistFlake8LinterTestCase.php',
|
||||
'ArcanistFormattedStringXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php',
|
||||
'ArcanistFutureLinter' => 'lint/linter/ArcanistFutureLinter.php',
|
||||
'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php',
|
||||
'ArcanistGeneratedLinterTestCase' => 'lint/linter/__tests__/ArcanistGeneratedLinterTestCase.php',
|
||||
|
@ -92,7 +119,14 @@ phutil_register_library_map(array(
|
|||
'ArcanistHgProxyClient' => 'hgdaemon/ArcanistHgProxyClient.php',
|
||||
'ArcanistHgProxyServer' => 'hgdaemon/ArcanistHgProxyServer.php',
|
||||
'ArcanistHgServerChannel' => 'hgdaemon/ArcanistHgServerChannel.php',
|
||||
'ArcanistImplicitConstructorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitConstructorXHPASTLinterRule.php',
|
||||
'ArcanistImplicitFallthroughXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitFallthroughXHPASTLinterRule.php',
|
||||
'ArcanistImplicitVisibilityXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitVisibilityXHPASTLinterRule.php',
|
||||
'ArcanistInnerFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistInnerFunctionXHPASTLinterRule.php',
|
||||
'ArcanistInstallCertificateWorkflow' => 'workflow/ArcanistInstallCertificateWorkflow.php',
|
||||
'ArcanistInstanceOfOperatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistInstanceOfOperatorXHPASTLinterRule.php',
|
||||
'ArcanistInvalidDefaultParameterXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistInvalidDefaultParameterXHPASTLinterRule.php',
|
||||
'ArcanistInvalidModifiersXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistInvalidModifiersXHPASTLinterRule.php',
|
||||
'ArcanistJSHintLinter' => 'lint/linter/ArcanistJSHintLinter.php',
|
||||
'ArcanistJSHintLinterTestCase' => 'lint/linter/__tests__/ArcanistJSHintLinterTestCase.php',
|
||||
'ArcanistJSONLintLinter' => 'lint/linter/ArcanistJSONLintLinter.php',
|
||||
|
@ -102,7 +136,10 @@ phutil_register_library_map(array(
|
|||
'ArcanistJSONLinterTestCase' => 'lint/linter/__tests__/ArcanistJSONLinterTestCase.php',
|
||||
'ArcanistJscsLinter' => 'lint/linter/ArcanistJscsLinter.php',
|
||||
'ArcanistJscsLinterTestCase' => 'lint/linter/__tests__/ArcanistJscsLinterTestCase.php',
|
||||
'ArcanistKeywordCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistKeywordCasingXHPASTLinterRule.php',
|
||||
'ArcanistLambdaFuncFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLambdaFuncFunctionXHPASTLinterRule.php',
|
||||
'ArcanistLandWorkflow' => 'workflow/ArcanistLandWorkflow.php',
|
||||
'ArcanistLanguageConstructParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLanguageConstructParenthesesXHPASTLinterRule.php',
|
||||
'ArcanistLesscLinter' => 'lint/linter/ArcanistLesscLinter.php',
|
||||
'ArcanistLesscLinterTestCase' => 'lint/linter/__tests__/ArcanistLesscLinterTestCase.php',
|
||||
'ArcanistLiberateWorkflow' => 'workflow/ArcanistLiberateWorkflow.php',
|
||||
|
@ -118,19 +155,30 @@ phutil_register_library_map(array(
|
|||
'ArcanistLinterTestCase' => 'lint/linter/__tests__/ArcanistLinterTestCase.php',
|
||||
'ArcanistLintersWorkflow' => 'workflow/ArcanistLintersWorkflow.php',
|
||||
'ArcanistListWorkflow' => 'workflow/ArcanistListWorkflow.php',
|
||||
'ArcanistLogicalOperatorsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLogicalOperatorsXHPASTLinterRule.php',
|
||||
'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLowercaseFunctionsXHPASTLinterRule.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',
|
||||
'ArcanistNamingConventionsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNamingConventionsXHPASTLinterRule.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',
|
||||
'ArcanistNoneLintRenderer' => 'lint/renderer/ArcanistNoneLintRenderer.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',
|
||||
'ArcanistPHPEchoTagXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPHPEchoTagXHPASTLinterRule.php',
|
||||
'ArcanistPHPOpenTagXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPHPOpenTagXHPASTLinterRule.php',
|
||||
'ArcanistPHPShortTagXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPHPShortTagXHPASTLinterRule.php',
|
||||
'ArcanistParenthesesSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php',
|
||||
'ArcanistPasteWorkflow' => 'workflow/ArcanistPasteWorkflow.php',
|
||||
'ArcanistPatchWorkflow' => 'workflow/ArcanistPatchWorkflow.php',
|
||||
'ArcanistPhpLinter' => 'lint/linter/ArcanistPhpLinter.php',
|
||||
|
@ -142,6 +190,8 @@ phutil_register_library_map(array(
|
|||
'ArcanistPhutilLibraryLinter' => 'lint/linter/ArcanistPhutilLibraryLinter.php',
|
||||
'ArcanistPhutilXHPASTLinter' => 'lint/linter/ArcanistPhutilXHPASTLinter.php',
|
||||
'ArcanistPhutilXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistPhutilXHPASTLinterTestCase.php',
|
||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php',
|
||||
'ArcanistPregQuoteMisuseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPregQuoteMisuseXHPASTLinterRule.php',
|
||||
'ArcanistPuppetLintLinter' => 'lint/linter/ArcanistPuppetLintLinter.php',
|
||||
'ArcanistPuppetLintLinterTestCase' => 'lint/linter/__tests__/ArcanistPuppetLintLinterTestCase.php',
|
||||
'ArcanistPyFlakesLinter' => 'lint/linter/ArcanistPyFlakesLinter.php',
|
||||
|
@ -151,40 +201,57 @@ phutil_register_library_map(array(
|
|||
'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php',
|
||||
'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php',
|
||||
'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php',
|
||||
'ArcanistReusedAsIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedAsIteratorXHPASTLinterRule.php',
|
||||
'ArcanistReusedIteratorReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedIteratorReferenceXHPASTLinterRule.php',
|
||||
'ArcanistReusedIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedIteratorXHPASTLinterRule.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',
|
||||
'ArcanistSemicolonSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSemicolonSpacingXHPASTLinterRule.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',
|
||||
'ArcanistSpellingLinter' => 'lint/linter/ArcanistSpellingLinter.php',
|
||||
'ArcanistSpellingLinterTestCase' => 'lint/linter/__tests__/ArcanistSpellingLinterTestCase.php',
|
||||
'ArcanistStartWorkflow' => 'workflow/ArcanistStartWorkflow.php',
|
||||
'ArcanistStaticThisXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistStaticThisXHPASTLinterRule.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',
|
||||
'ArcanistTestResultParser' => 'unit/parser/ArcanistTestResultParser.php',
|
||||
'ArcanistTestXHPASTLintSwitchHook' => 'lint/linter/__tests__/ArcanistTestXHPASTLintSwitchHook.php',
|
||||
'ArcanistTextLinter' => 'lint/linter/ArcanistTextLinter.php',
|
||||
'ArcanistTextLinterTestCase' => 'lint/linter/__tests__/ArcanistTextLinterTestCase.php',
|
||||
'ArcanistTimeWorkflow' => 'workflow/ArcanistTimeWorkflow.php',
|
||||
'ArcanistToStringExceptionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistToStringExceptionXHPASTLinterRule.php',
|
||||
'ArcanistTodoCommentXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistTodoCommentXHPASTLinterRule.php',
|
||||
'ArcanistTodoWorkflow' => 'workflow/ArcanistTodoWorkflow.php',
|
||||
'ArcanistUSEnglishTranslation' => 'internationalization/ArcanistUSEnglishTranslation.php',
|
||||
'ArcanistUnableToParseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnableToParseXHPASTLinterRule.php',
|
||||
'ArcanistUndeclaredVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUndeclaredVariableXHPASTLinterRule.php',
|
||||
'ArcanistUnitConsoleRenderer' => 'unit/renderer/ArcanistUnitConsoleRenderer.php',
|
||||
'ArcanistUnitRenderer' => 'unit/renderer/ArcanistUnitRenderer.php',
|
||||
'ArcanistUnitTestEngine' => 'unit/engine/ArcanistUnitTestEngine.php',
|
||||
'ArcanistUnitTestResult' => 'unit/ArcanistUnitTestResult.php',
|
||||
'ArcanistUnitTestableLintEngine' => 'lint/engine/ArcanistUnitTestableLintEngine.php',
|
||||
'ArcanistUnitWorkflow' => 'workflow/ArcanistUnitWorkflow.php',
|
||||
'ArcanistUnnecessaryFinalModifierXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnnecessaryFinalModifierXHPASTLinterRule.php',
|
||||
'ArcanistUnnecessarySemicolonXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnnecessarySemicolonXHPASTLinterRule.php',
|
||||
'ArcanistUpgradeWorkflow' => 'workflow/ArcanistUpgradeWorkflow.php',
|
||||
'ArcanistUploadWorkflow' => 'workflow/ArcanistUploadWorkflow.php',
|
||||
'ArcanistUsageException' => 'exception/ArcanistUsageException.php',
|
||||
'ArcanistUselessOverridingMethodXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUselessOverridingMethodXHPASTLinterRule.php',
|
||||
'ArcanistUserAbortException' => 'exception/usage/ArcanistUserAbortException.php',
|
||||
'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php',
|
||||
'ArcanistVersionWorkflow' => 'workflow/ArcanistVersionWorkflow.php',
|
||||
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
|
||||
'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php',
|
||||
|
@ -193,6 +260,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistXHPASTLintNamingHookTestCase' => 'lint/linter/xhpast/__tests__/ArcanistXHPASTLintNamingHookTestCase.php',
|
||||
'ArcanistXHPASTLintSwitchHook' => 'lint/linter/xhpast/ArcanistXHPASTLintSwitchHook.php',
|
||||
'ArcanistXHPASTLinter' => 'lint/linter/ArcanistXHPASTLinter.php',
|
||||
'ArcanistXHPASTLinterRule' => 'lint/linter/xhpast/ArcanistXHPASTLinterRule.php',
|
||||
'ArcanistXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php',
|
||||
'ArcanistXMLLinter' => 'lint/linter/ArcanistXMLLinter.php',
|
||||
'ArcanistXMLLinterTestCase' => 'lint/linter/__tests__/ArcanistXMLLinterTestCase.php',
|
||||
|
@ -213,13 +281,19 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'function' => array(),
|
||||
'xmap' => array(
|
||||
'ArcanistAliasFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistAliasWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistAmendWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistAnoidWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistArraySeparatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistBackoutWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistBaseCommitParserTestCase' => 'PhutilTestCase',
|
||||
'ArcanistBaseXHPASTLinter' => 'ArcanistFutureLinter',
|
||||
'ArcanistBinaryExpressionSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistBlacklistedFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistBookmarkWorkflow' => 'ArcanistFeatureWorkflow',
|
||||
'ArcanistBraceFormattingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistBranchWorkflow' => 'ArcanistFeatureWorkflow',
|
||||
'ArcanistBritishTestCase' => 'PhutilTestCase',
|
||||
'ArcanistBrowseWorkflow' => 'ArcanistWorkflow',
|
||||
|
@ -228,37 +302,57 @@ phutil_register_library_map(array(
|
|||
'ArcanistCSSLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistCSharpLinter' => 'ArcanistLinter',
|
||||
'ArcanistCallConduitWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistCallTimePassByReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistCapabilityNotSupportedException' => 'Exception',
|
||||
'ArcanistCastSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistCheckstyleXMLLintRenderer' => 'ArcanistLintRenderer',
|
||||
'ArcanistChmodLinter' => 'ArcanistLinter',
|
||||
'ArcanistChmodLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
'ArcanistClassFilenameMismatchXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistClassNameLiteralXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistCloseRevisionWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistCloseWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistClosingCallParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistClosingDeclarationParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistClosureLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistClosureLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistCoffeeLintLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistCoffeeLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistCommentRemoverTestCase' => 'PhutilTestCase',
|
||||
'ArcanistCommentSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistCommentStyleXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistCommitWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistCompilerLintRenderer' => 'ArcanistLintRenderer',
|
||||
'ArcanistComprehensiveLintEngine' => 'ArcanistLintEngine',
|
||||
'ArcanistConcatenationOperatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistConfigurationDrivenLintEngine' => 'ArcanistLintEngine',
|
||||
'ArcanistConsoleLintRenderer' => 'ArcanistLintRenderer',
|
||||
'ArcanistConstructorParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistControlStatementSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistCoverWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistCppcheckLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistCppcheckLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistCpplintLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistCpplintLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistDefaultParametersXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistDiffParserTestCase' => 'PhutilTestCase',
|
||||
'ArcanistDiffUtilsTestCase' => 'PhutilTestCase',
|
||||
'ArcanistDiffWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistDifferentialCommitMessageParserException' => 'Exception',
|
||||
'ArcanistDifferentialDependencyGraph' => 'AbstractDirectedGraph',
|
||||
'ArcanistDoubleQuoteXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistDownloadWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistDuplicateKeysInArrayXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistDynamicDefineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistElseIfUsageXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistEmptyStatementXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistEventType' => 'PhutilEventType',
|
||||
'ArcanistExitExpressionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistExportWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistExternalLinter' => 'ArcanistFutureLinter',
|
||||
'ArcanistExternalLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
'ArcanistExtractUseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistFeatureWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistFileDataRef' => 'Phobject',
|
||||
'ArcanistFileUploader' => 'Phobject',
|
||||
|
@ -267,6 +361,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistFlagWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistFlake8Linter' => 'ArcanistExternalLinter',
|
||||
'ArcanistFlake8LinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistFormattedStringXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistFutureLinter' => 'ArcanistLinter',
|
||||
'ArcanistGeneratedLinter' => 'ArcanistLinter',
|
||||
'ArcanistGeneratedLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
|
@ -281,7 +376,14 @@ phutil_register_library_map(array(
|
|||
'ArcanistHelpWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistHgClientChannel' => 'PhutilProtocolChannel',
|
||||
'ArcanistHgServerChannel' => 'PhutilProtocolChannel',
|
||||
'ArcanistImplicitConstructorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistImplicitFallthroughXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistImplicitVisibilityXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistInnerFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistInstallCertificateWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistInstanceOfOperatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistInvalidDefaultParameterXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistInvalidModifiersXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistJSHintLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistJSHintLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistJSONLintLinter' => 'ArcanistExternalLinter',
|
||||
|
@ -291,7 +393,10 @@ phutil_register_library_map(array(
|
|||
'ArcanistJSONLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
'ArcanistJscsLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistJscsLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistKeywordCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistLambdaFuncFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistLandWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistLanguageConstructParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistLesscLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistLesscLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistLiberateWorkflow' => 'ArcanistWorkflow',
|
||||
|
@ -300,18 +405,29 @@ phutil_register_library_map(array(
|
|||
'ArcanistLinterTestCase' => 'PhutilTestCase',
|
||||
'ArcanistLintersWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistListWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistLogicalOperatorsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
|
||||
'ArcanistMercurialParserTestCase' => 'PhutilTestCase',
|
||||
'ArcanistMergeConflictLinter' => 'ArcanistLinter',
|
||||
'ArcanistMergeConflictLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
'ArcanistMissingLinterException' => 'Exception',
|
||||
'ArcanistModifierOrderingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistNamingConventionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistNoEffectException' => 'ArcanistUsageException',
|
||||
'ArcanistNoEngineException' => 'ArcanistUsageException',
|
||||
'ArcanistNoLintLinter' => 'ArcanistLinter',
|
||||
'ArcanistNoLintLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
'ArcanistNoParentScopeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistNoneLintRenderer' => 'ArcanistLintRenderer',
|
||||
'ArcanistPEP8Linter' => 'ArcanistExternalLinter',
|
||||
'ArcanistPEP8LinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistPHPCloseTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistPHPCompatibilityXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistPHPEchoTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistPHPOpenTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistPHPShortTagXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistParenthesesSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistPasteWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistPatchWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistPhpLinter' => 'ArcanistExternalLinter',
|
||||
|
@ -323,6 +439,8 @@ phutil_register_library_map(array(
|
|||
'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
|
||||
'ArcanistPhutilXHPASTLinter' => 'ArcanistBaseXHPASTLinter',
|
||||
'ArcanistPhutilXHPASTLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistPregQuoteMisuseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistPuppetLintLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistPuppetLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistPyFlakesLinter' => 'ArcanistExternalLinter',
|
||||
|
@ -331,35 +449,52 @@ phutil_register_library_map(array(
|
|||
'ArcanistPyLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistRepositoryAPIMiscTestCase' => 'PhutilTestCase',
|
||||
'ArcanistRepositoryAPIStateTestCase' => 'PhutilTestCase',
|
||||
'ArcanistReusedAsIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistReusedIteratorReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistReusedIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistRevertWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistRuboCopLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistRuboCopLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistRubyLinter' => 'ArcanistExternalLinter',
|
||||
'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase',
|
||||
'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
|
||||
'ArcanistSelfMemberReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistSemicolonSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistSetConfigWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistShellCompleteWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistSingleLintEngine' => 'ArcanistLintEngine',
|
||||
'ArcanistSlownessXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistSpellingLinter' => 'ArcanistLinter',
|
||||
'ArcanistSpellingLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
'ArcanistStartWorkflow' => 'ArcanistPhrequentWorkflow',
|
||||
'ArcanistStaticThisXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistStopWorkflow' => 'ArcanistPhrequentWorkflow',
|
||||
'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI',
|
||||
'ArcanistSummaryLintRenderer' => 'ArcanistLintRenderer',
|
||||
'ArcanistSyntaxErrorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistTasksWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistTautologicalExpressionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistTestXHPASTLintSwitchHook' => 'ArcanistXHPASTLintSwitchHook',
|
||||
'ArcanistTextLinter' => 'ArcanistLinter',
|
||||
'ArcanistTextLinterTestCase' => 'ArcanistLinterTestCase',
|
||||
'ArcanistTimeWorkflow' => 'ArcanistPhrequentWorkflow',
|
||||
'ArcanistToStringExceptionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistTodoCommentXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistTodoWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistUSEnglishTranslation' => 'PhutilTranslation',
|
||||
'ArcanistUnableToParseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistUndeclaredVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistUnitConsoleRenderer' => 'ArcanistUnitRenderer',
|
||||
'ArcanistUnitTestableLintEngine' => 'ArcanistLintEngine',
|
||||
'ArcanistUnitWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistUnnecessaryFinalModifierXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistUnnecessarySemicolonXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistUpgradeWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistUploadWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistUsageException' => 'Exception',
|
||||
'ArcanistUselessOverridingMethodXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistUserAbortException' => 'ArcanistUsageException',
|
||||
'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistVersionWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistWhichWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistWorkflow' => 'Phobject',
|
||||
|
|
|
@ -201,7 +201,7 @@ abstract class ArcanistBaseXHPASTLinter extends ArcanistFutureLinter {
|
|||
}
|
||||
|
||||
|
||||
/* -( Utility )------------------------------------------------------------ */
|
||||
/* -( Deprecated )--------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Retrieve all calls to some specified function(s).
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,7 @@
|
|||
$x = array();
|
||||
sizeof($x);
|
||||
die();
|
||||
sizeOf($x);
|
||||
sizeOf($x); // fixme
|
||||
~~~~~~~~~~
|
||||
advice:4:1
|
||||
advice:5:1
|
||||
|
@ -14,4 +14,4 @@ advice:6:1
|
|||
$x = array();
|
||||
count($x);
|
||||
exit();
|
||||
count($x);
|
||||
sizeof($x); // fixme
|
||||
|
|
|
@ -4,4 +4,10 @@ eval('evil code');
|
|||
error:2:1
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~
|
||||
{"config": {"xhpast.blacklisted.function": {"eval": "Evil function"}}}
|
||||
{
|
||||
"config": {
|
||||
"xhpast.blacklisted.function": {
|
||||
"eval": "Evil function"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,4 +26,8 @@ error:12:7
|
|||
warning:16:3
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~
|
||||
{"config": {"xhpast.php-version": "5.3.0"}}
|
||||
{
|
||||
"config": {
|
||||
"xhpast.php-version": "5.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,8 @@ f()[0];
|
|||
error:2:5
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~
|
||||
{"config": {"xhpast.php-version": "5.3.0"}}
|
||||
{
|
||||
"config": {
|
||||
"xhpast.php-version": "5.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,4 +6,8 @@ EOT;
|
|||
error:2:6
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~
|
||||
{"config": {"xhpast.php-version": "5.2.3"}}
|
||||
{
|
||||
"config": {
|
||||
"xhpast.php-version": "5.2.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,8 @@ error:3:1
|
|||
error:4:10
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~
|
||||
{"config": {"xhpast.php-version": "5.4.0"}}
|
||||
{
|
||||
"config": {
|
||||
"xhpast.php-version": "5.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,4 +22,8 @@ error:10:1
|
|||
error:13:6
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~
|
||||
{"config": {"xhpast.php-version": "5.2.3"}}
|
||||
{
|
||||
"config": {
|
||||
"xhpast.php-version": "5.2.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,8 @@ error:3:5
|
|||
error:4:9
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~
|
||||
{"config": {"xhpast.php-version": "5.3.0"}}
|
||||
{
|
||||
"config": {
|
||||
"xhpast.php-version": "5.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,4 +93,8 @@ warning:71:3
|
|||
warning:75:3
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~
|
||||
{"config":{"xhpast.switchhook":"ArcanistTestXHPASTLintSwitchHook"}}
|
||||
{
|
||||
"config":{
|
||||
"xhpast.switchhook":"ArcanistTestXHPASTLintSwitchHook"
|
||||
}
|
||||
}
|
||||
|
|
167
src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php
Normal file
167
src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php
Normal file
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
abstract class ArcanistXHPASTLinterRule {
|
||||
|
||||
private $linter = null;
|
||||
|
||||
final public function getLintID() {
|
||||
$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)));
|
||||
}
|
||||
|
||||
return $const;
|
||||
}
|
||||
|
||||
abstract public function getLintName();
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ERROR;
|
||||
}
|
||||
|
||||
public function getLinterConfigurationOptions() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function setLinterConfigurationValue($key, $value) {}
|
||||
|
||||
abstract public function process(XHPASTNode $root);
|
||||
|
||||
final public function setLinter(ArcanistXHPASTLinter $linter) {
|
||||
$this->linter = $linter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// 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 public function raiseLintAtOffset(
|
||||
$offset,
|
||||
$desc,
|
||||
$text = null,
|
||||
$replace = null) {
|
||||
|
||||
return $this->linter->raiseLintAtOffset(
|
||||
$offset,
|
||||
$this->getLintID(),
|
||||
$desc,
|
||||
$text,
|
||||
$replace);
|
||||
}
|
||||
|
||||
final protected function raiseLintAtToken(
|
||||
XHPASTToken $token,
|
||||
$desc,
|
||||
$replace = null) {
|
||||
|
||||
return $this->linter->raiseLintAtToken(
|
||||
$token,
|
||||
$this->getLintID(),
|
||||
$desc,
|
||||
$replace);
|
||||
}
|
||||
|
||||
/* -( Utility )------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* 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<string> 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);
|
||||
}
|
||||
|
||||
public function getSuperGlobalNames() {
|
||||
return array(
|
||||
'$GLOBALS',
|
||||
'$_SERVER',
|
||||
'$_GET',
|
||||
'$_POST',
|
||||
'$_FILES',
|
||||
'$_COOKIE',
|
||||
'$_SESSION',
|
||||
'$_REQUEST',
|
||||
'$_ENV',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistAliasFunctionXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 65;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Alias Functions');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$aliases = $this->getFunctionAliases();
|
||||
$functions = $this->getFunctionCalls($root, array_keys($aliases));
|
||||
|
||||
foreach ($functions as $function) {
|
||||
$function_name = $function->getChildByIndex(0);
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$function_name,
|
||||
pht('Alias functions should be avoided.'),
|
||||
$aliases[phutil_utf8_strtolower($function_name->getConcreteString())]);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFunctionAliases() {
|
||||
return array(
|
||||
'_' => 'gettext',
|
||||
'chop' => 'rtrim',
|
||||
'close' => 'closedir',
|
||||
'com_get' => 'com_propget',
|
||||
'com_propset' => 'com_propput',
|
||||
'com_set' => 'com_propput',
|
||||
'die' => 'exit',
|
||||
'diskfreespace' => 'disk_free_space',
|
||||
'doubleval' => 'floatval',
|
||||
'drawarc' => 'swfshape_drawarc',
|
||||
'drawcircle' => 'swfshape_drawcircle',
|
||||
'drawcubic' => 'swfshape_drawcubic',
|
||||
'drawcubicto' => 'swfshape_drawcubicto',
|
||||
'drawcurve' => 'swfshape_drawcurve',
|
||||
'drawcurveto' => 'swfshape_drawcurveto',
|
||||
'drawglyph' => 'swfshape_drawglyph',
|
||||
'drawline' => 'swfshape_drawline',
|
||||
'drawlineto' => 'swfshape_drawlineto',
|
||||
'fbsql' => 'fbsql_db_query',
|
||||
'fputs' => 'fwrite',
|
||||
'gzputs' => 'gzwrite',
|
||||
'i18n_convert' => 'mb_convert_encoding',
|
||||
'i18n_discover_encoding' => 'mb_detect_encoding',
|
||||
'i18n_http_input' => 'mb_http_input',
|
||||
'i18n_http_output' => 'mb_http_output',
|
||||
'i18n_internal_encoding' => 'mb_internal_encoding',
|
||||
'i18n_ja_jp_hantozen' => 'mb_convert_kana',
|
||||
'i18n_mime_header_decode' => 'mb_decode_mimeheader',
|
||||
'i18n_mime_header_encode' => 'mb_encode_mimeheader',
|
||||
'imap_create' => 'imap_createmailbox',
|
||||
'imap_fetchtext' => 'imap_body',
|
||||
'imap_getmailboxes' => 'imap_list_full',
|
||||
'imap_getsubscribed' => 'imap_lsub_full',
|
||||
'imap_header' => 'imap_headerinfo',
|
||||
'imap_listmailbox' => 'imap_list',
|
||||
'imap_listsubscribed' => 'imap_lsub',
|
||||
'imap_rename' => 'imap_renamemailbox',
|
||||
'imap_scan' => 'imap_listscan',
|
||||
'imap_scanmailbox' => 'imap_listscan',
|
||||
'ini_alter' => 'ini_set',
|
||||
'is_double' => 'is_float',
|
||||
'is_integer' => 'is_int',
|
||||
'is_long' => 'is_int',
|
||||
'is_real' => 'is_float',
|
||||
'is_writeable' => 'is_writable',
|
||||
'join' => 'implode',
|
||||
'key_exists' => 'array_key_exists',
|
||||
'ldap_close' => 'ldap_unbind',
|
||||
'magic_quotes_runtime' => 'set_magic_quotes_runtime',
|
||||
'mbstrcut' => 'mb_strcut',
|
||||
'mbstrlen' => 'mb_strlen',
|
||||
'mbstrpos' => 'mb_strpos',
|
||||
'mbstrrpos' => 'mb_strrpos',
|
||||
'mbsubstr' => 'mb_substr',
|
||||
'ming_setcubicthreshold' => 'ming_setCubicThreshold',
|
||||
'ming_setscale' => 'ming_setScale',
|
||||
'msql' => 'msql_db_query',
|
||||
'msql_createdb' => 'msql_create_db',
|
||||
'msql_dbname' => 'msql_result',
|
||||
'msql_dropdb' => 'msql_drop_db',
|
||||
'msql_fieldflags' => 'msql_field_flags',
|
||||
'msql_fieldlen' => 'msql_field_len',
|
||||
'msql_fieldname' => 'msql_field_name',
|
||||
'msql_fieldtable' => 'msql_field_table',
|
||||
'msql_fieldtype' => 'msql_field_type',
|
||||
'msql_freeresult' => 'msql_free_result',
|
||||
'msql_listdbs' => 'msql_list_dbs',
|
||||
'msql_listfields' => 'msql_list_fields',
|
||||
'msql_listtables' => 'msql_list_tables',
|
||||
'msql_numfields' => 'msql_num_fields',
|
||||
'msql_numrows' => 'msql_num_rows',
|
||||
'msql_regcase' => 'sql_regcase',
|
||||
'msql_selectdb' => 'msql_select_db',
|
||||
'msql_tablename' => 'msql_result',
|
||||
'mssql_affected_rows' => 'sybase_affected_rows',
|
||||
'mssql_close' => 'sybase_close',
|
||||
'mssql_connect' => 'sybase_connect',
|
||||
'mssql_data_seek' => 'sybase_data_seek',
|
||||
'mssql_fetch_array' => 'sybase_fetch_array',
|
||||
'mssql_fetch_field' => 'sybase_fetch_field',
|
||||
'mssql_fetch_object' => 'sybase_fetch_object',
|
||||
'mssql_fetch_row' => 'sybase_fetch_row',
|
||||
'mssql_field_seek' => 'sybase_field_seek',
|
||||
'mssql_free_result' => 'sybase_free_result',
|
||||
'mssql_get_last_message' => 'sybase_get_last_message',
|
||||
'mssql_min_client_severity' => 'sybase_min_client_severity',
|
||||
'mssql_min_error_severity' => 'sybase_min_error_severity',
|
||||
'mssql_min_message_severity' => 'sybase_min_message_severity',
|
||||
'mssql_min_server_severity' => 'sybase_min_server_severity',
|
||||
'mssql_num_fields' => 'sybase_num_fields',
|
||||
'mssql_num_rows' => 'sybase_num_rows',
|
||||
'mssql_pconnect' => 'sybase_pconnect',
|
||||
'mssql_query' => 'sybase_query',
|
||||
'mssql_result' => 'sybase_result',
|
||||
'mssql_select_db' => 'sybase_select_db',
|
||||
'multcolor' => 'swfdisplayitem_multColor',
|
||||
'mysql' => 'mysql_db_query',
|
||||
'mysql_createdb' => 'mysql_create_db',
|
||||
'mysql_db_name' => 'mysql_result',
|
||||
'mysql_dbname' => 'mysql_result',
|
||||
'mysql_dropdb' => 'mysql_drop_db',
|
||||
'mysql_fieldflags' => 'mysql_field_flags',
|
||||
'mysql_fieldlen' => 'mysql_field_len',
|
||||
'mysql_fieldname' => 'mysql_field_name',
|
||||
'mysql_fieldtable' => 'mysql_field_table',
|
||||
'mysql_fieldtype' => 'mysql_field_type',
|
||||
'mysql_freeresult' => 'mysql_free_result',
|
||||
'mysql_listdbs' => 'mysql_list_dbs',
|
||||
'mysql_listfields' => 'mysql_list_fields',
|
||||
'mysql_listtables' => 'mysql_list_tables',
|
||||
'mysql_numfields' => 'mysql_num_fields',
|
||||
'mysql_numrows' => 'mysql_num_rows',
|
||||
'mysql_selectdb' => 'mysql_select_db',
|
||||
'mysql_tablename' => 'mysql_result',
|
||||
'ociassignelem' => 'OCI-Collection::assignElem',
|
||||
'ocibindbyname' => 'oci_bind_by_name',
|
||||
'ocicancel' => 'oci_cancel',
|
||||
'ocicloselob' => 'OCI-Lob::close',
|
||||
'ocicollappend' => 'OCI-Collection::append',
|
||||
'ocicollassign' => 'OCI-Collection::assign',
|
||||
'ocicollmax' => 'OCI-Collection::max',
|
||||
'ocicollsize' => 'OCI-Collection::size',
|
||||
'ocicolltrim' => 'OCI-Collection::trim',
|
||||
'ocicolumnisnull' => 'oci_field_is_null',
|
||||
'ocicolumnname' => 'oci_field_name',
|
||||
'ocicolumnprecision' => 'oci_field_precision',
|
||||
'ocicolumnscale' => 'oci_field_scale',
|
||||
'ocicolumnsize' => 'oci_field_size',
|
||||
'ocicolumntype' => 'oci_field_type',
|
||||
'ocicolumntyperaw' => 'oci_field_type_raw',
|
||||
'ocicommit' => 'oci_commit',
|
||||
'ocidefinebyname' => 'oci_define_by_name',
|
||||
'ocierror' => 'oci_error',
|
||||
'ociexecute' => 'oci_execute',
|
||||
'ocifetch' => 'oci_fetch',
|
||||
'ocifetchinto' => 'oci_fetch_array(),',
|
||||
'ocifetchstatement' => 'oci_fetch_all',
|
||||
'ocifreecollection' => 'OCI-Collection::free',
|
||||
'ocifreecursor' => 'oci_free_statement',
|
||||
'ocifreedesc' => 'oci_free_descriptor',
|
||||
'ocifreestatement' => 'oci_free_statement',
|
||||
'ocigetelem' => 'OCI-Collection::getElem',
|
||||
'ociinternaldebug' => 'oci_internal_debug',
|
||||
'ociloadlob' => 'OCI-Lob::load',
|
||||
'ocilogon' => 'oci_connect',
|
||||
'ocinewcollection' => 'oci_new_collection',
|
||||
'ocinewcursor' => 'oci_new_cursor',
|
||||
'ocinewdescriptor' => 'oci_new_descriptor',
|
||||
'ocinlogon' => 'oci_new_connect',
|
||||
'ocinumcols' => 'oci_num_fields',
|
||||
'ociparse' => 'oci_parse',
|
||||
'ocipasswordchange' => 'oci_password_change',
|
||||
'ociplogon' => 'oci_pconnect',
|
||||
'ociresult' => 'oci_result',
|
||||
'ocirollback' => 'oci_rollback',
|
||||
'ocisavelob' => 'OCI-Lob::save',
|
||||
'ocisavelobfile' => 'OCI-Lob::import',
|
||||
'ociserverversion' => 'oci_server_version',
|
||||
'ocisetprefetch' => 'oci_set_prefetch',
|
||||
'ocistatementtype' => 'oci_statement_type',
|
||||
'ociwritelobtofile' => 'OCI-Lob::export',
|
||||
'ociwritetemporarylob' => 'OCI-Lob::writeTemporary',
|
||||
'odbc_do' => 'odbc_exec',
|
||||
'odbc_field_precision' => 'odbc_field_len',
|
||||
'pdf_add_outline' => 'pdf_add_bookmark',
|
||||
'pg_clientencoding' => 'pg_client_encoding',
|
||||
'pg_setclientencoding' => 'pg_set_client_encoding',
|
||||
'pos' => 'current',
|
||||
'recode' => 'recode_string',
|
||||
'show_source' => 'highlight_file',
|
||||
'sizeof' => 'count',
|
||||
'snmpwalkoid' => 'snmprealwalk',
|
||||
'strchr' => 'strstr',
|
||||
'streammp3' => 'swfmovie_streamMp3',
|
||||
'swfaction' => 'swfaction_init',
|
||||
'swfbitmap' => 'swfbitmap_init',
|
||||
'swfbutton' => 'swfbutton_init',
|
||||
'swffill' => 'swffill_init',
|
||||
'swffont' => 'swffont_init',
|
||||
'swfgradient' => 'swfgradient_init',
|
||||
'swfmorph' => 'swfmorph_init',
|
||||
'swfmovie' => 'swfmovie_init',
|
||||
'swfshape' => 'swfshape_init',
|
||||
'swfsprite' => 'swfsprite_init',
|
||||
'swftext' => 'swftext_init',
|
||||
'swftextfield' => 'swftextfield_init',
|
||||
'xptr_new_context' => 'xpath_new_context',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistArrayIndexSpacingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 28;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Spacing Before Array Index');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$indexes = $root->selectDescendantsOfType('n_INDEX_ACCESS');
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
$tokens = $index->getChildByIndex(0)->getTokens();
|
||||
$last = array_pop($tokens);
|
||||
$trailing = $last->getNonsemanticTokensAfter();
|
||||
$trailing_text = implode('', mpull($trailing, 'getValue'));
|
||||
|
||||
if (preg_match('/^ +$/', $trailing_text)) {
|
||||
$this->raiseLintAtOffset(
|
||||
$last->getOffset() + strlen($last->getValue()),
|
||||
pht('Convention: no spaces before index access.'),
|
||||
$trailing_text,
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistArraySeparatorXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 48;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Array Separator');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$arrays = $root->selectDescendantsOfType('n_ARRAY_LITERAL');
|
||||
|
||||
foreach ($arrays as $array) {
|
||||
$value_list = $array->getChildOfType(0, 'n_ARRAY_VALUE_LIST');
|
||||
$values = $value_list->getChildrenOfType('n_ARRAY_VALUE');
|
||||
|
||||
if (!$values) {
|
||||
// There is no need to check an empty array.
|
||||
continue;
|
||||
}
|
||||
|
||||
$multiline = $array->getLineNumber() != $array->getEndLineNumber();
|
||||
|
||||
$value = last($values);
|
||||
$after = last($value->getTokens())->getNextToken();
|
||||
|
||||
if ($multiline) {
|
||||
if (!$after || $after->getValue() != ',') {
|
||||
if ($value->getChildByIndex(1)->getTypeName() == 'n_HEREDOC') {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($before, $after) = $value->getSurroundingNonsemanticTokens();
|
||||
$after = implode('', mpull($after, 'getValue'));
|
||||
|
||||
$original = $value->getConcreteString();
|
||||
$replacement = $value->getConcreteString().',';
|
||||
|
||||
if (strpos($after, "\n") === false) {
|
||||
$original .= $after;
|
||||
$replacement .= $after."\n".$array->getIndentation();
|
||||
}
|
||||
|
||||
$this->raiseLintAtOffset(
|
||||
$value->getOffset(),
|
||||
pht('Multi-lined arrays should have trailing commas.'),
|
||||
$original,
|
||||
$replacement);
|
||||
} else if ($value->getLineNumber() == $array->getEndLineNumber()) {
|
||||
$close = last($array->getTokens());
|
||||
|
||||
$this->raiseLintAtToken(
|
||||
$close,
|
||||
pht('Closing parenthesis should be on a new line.'),
|
||||
"\n".$array->getIndentation().$close->getValue());
|
||||
}
|
||||
} else if ($after && $after->getValue() == ',') {
|
||||
$this->raiseLintAtToken(
|
||||
$after,
|
||||
pht('Single lined arrays should not have a trailing comma.'),
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistBinaryExpressionSpacingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 27;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Space Around Binary Operator');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
|
||||
foreach ($expressions as $expression) {
|
||||
$operator = $expression->getChildByIndex(1);
|
||||
$operator_value = $operator->getConcreteString();
|
||||
list($before, $after) = $operator->getSurroundingNonsemanticTokens();
|
||||
|
||||
$replace = null;
|
||||
if (empty($before) && empty($after)) {
|
||||
$replace = " {$operator_value} ";
|
||||
} else if (empty($before)) {
|
||||
$replace = " {$operator_value}";
|
||||
} else if (empty($after)) {
|
||||
$replace = "{$operator_value} ";
|
||||
}
|
||||
|
||||
if ($replace !== null) {
|
||||
$this->raiseLintAtNode(
|
||||
$operator,
|
||||
pht(
|
||||
'Convention: logical and arithmetic operators should be '.
|
||||
'surrounded by whitespace.'),
|
||||
$replace);
|
||||
}
|
||||
}
|
||||
|
||||
$tokens = $root->selectTokensOfType(',');
|
||||
foreach ($tokens as $token) {
|
||||
$next = $token->getNextToken();
|
||||
switch ($next->getTypeName()) {
|
||||
case ')':
|
||||
case 'T_WHITESPACE':
|
||||
break;
|
||||
default:
|
||||
$this->raiseLintAtToken(
|
||||
$token,
|
||||
pht('Convention: comma should be followed by space.'),
|
||||
', ');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$tokens = $root->selectTokensOfType('T_DOUBLE_ARROW');
|
||||
foreach ($tokens as $token) {
|
||||
$prev = $token->getPrevToken();
|
||||
$next = $token->getNextToken();
|
||||
|
||||
$prev_type = $prev->getTypeName();
|
||||
$next_type = $next->getTypeName();
|
||||
|
||||
$prev_space = ($prev_type === 'T_WHITESPACE');
|
||||
$next_space = ($next_type === 'T_WHITESPACE');
|
||||
|
||||
$replace = null;
|
||||
if (!$prev_space && !$next_space) {
|
||||
$replace = ' => ';
|
||||
} else if ($prev_space && !$next_space) {
|
||||
$replace = '=> ';
|
||||
} else if (!$prev_space && $next_space) {
|
||||
$replace = ' =>';
|
||||
}
|
||||
|
||||
if ($replace !== null) {
|
||||
$this->raiseLintAtToken(
|
||||
$token,
|
||||
pht('Convention: double arrow should be surrounded by whitespace.'),
|
||||
$replace);
|
||||
}
|
||||
}
|
||||
|
||||
$parameters = $root->selectDescendantsOfType('n_DECLARATION_PARAMETER');
|
||||
foreach ($parameters as $parameter) {
|
||||
if ($parameter->getChildByIndex(2)->getTypeName() == 'n_EMPTY') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$operator = head($parameter->selectTokensOfType('='));
|
||||
$before = $operator->getNonsemanticTokensBefore();
|
||||
$after = $operator->getNonsemanticTokensAfter();
|
||||
|
||||
$replace = null;
|
||||
if (empty($before) && empty($after)) {
|
||||
$replace = ' = ';
|
||||
} else if (empty($before)) {
|
||||
$replace = ' =';
|
||||
} else if (empty($after)) {
|
||||
$replace = '= ';
|
||||
}
|
||||
|
||||
if ($replace !== null) {
|
||||
$this->raiseLintAtToken(
|
||||
$operator,
|
||||
pht(
|
||||
'Convention: logical and arithmetic operators should be '.
|
||||
'surrounded by whitespace.'),
|
||||
$replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistBlacklistedFunctionXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 51;
|
||||
|
||||
private $blacklistedFunctions = array();
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Use of Blacklisted Function');
|
||||
}
|
||||
|
||||
public function getLinterConfigurationOptions() {
|
||||
return parent::getLinterConfigurationOptions() + array(
|
||||
'xhpast.blacklisted.function' => array(
|
||||
'type' => 'optional map<string, string>',
|
||||
'help' => pht('Blacklisted functions which should not be used.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function setLinterConfigurationValue($key, $value) {
|
||||
switch ($key) {
|
||||
case 'xhpast.blacklisted.function':
|
||||
$this->blacklistedFunctions = $value;
|
||||
return;
|
||||
|
||||
default:
|
||||
return parent::getLinterConfigurationOptions();
|
||||
}
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
|
||||
|
||||
foreach ($calls as $call) {
|
||||
$node = $call->getChildByIndex(0);
|
||||
$name = $node->getConcreteString();
|
||||
|
||||
$reason = idx($this->blacklistedFunctions, $name);
|
||||
|
||||
if ($reason) {
|
||||
$this->raiseLintAtNode($node, $reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistBraceFormattingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 24;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Brace Placement');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
foreach ($root->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.'));
|
||||
}
|
||||
}
|
||||
|
||||
$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 = last($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.'),
|
||||
' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistCallTimePassByReferenceXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 53;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Call-Time Pass-By-Reference');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$nodes = $root->selectDescendantsOfType('n_CALL_PARAMETER_LIST');
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$parameters = $node->getChildrenOfType('n_VARIABLE_REFERENCE');
|
||||
|
||||
foreach ($parameters as $parameter) {
|
||||
$this->raiseLintAtNode(
|
||||
$parameter,
|
||||
pht('Call-time pass-by-reference calls are prohibited.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistCastSpacingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 66;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Cast Spacing');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$cast_expressions = $root->selectDescendantsOfType('n_CAST_EXPRESSION');
|
||||
|
||||
foreach ($cast_expressions as $cast_expression) {
|
||||
$cast = $cast_expression->getChildOfType(0, 'n_CAST');
|
||||
|
||||
list($before, $after) = $cast->getSurroundingNonsemanticTokens();
|
||||
$after = head($after);
|
||||
|
||||
if ($after) {
|
||||
$this->raiseLintAtToken(
|
||||
$after,
|
||||
pht('A cast statement must not be followed by a space.'),
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Lint that if the file declares exactly one interface or class, the name of
|
||||
* the file matches the name of the class, unless the class name is funky like
|
||||
* an XHP element.
|
||||
*/
|
||||
final class ArcanistClassFilenameMismatchXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 19;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Class-Filename Mismatch');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
$interfaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION');
|
||||
|
||||
if (count($classes) + count($interfaces) !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$declarations = count($classes) ? $classes : $interfaces;
|
||||
$declarations->rewind();
|
||||
$declaration = $declarations->current();
|
||||
|
||||
$decl_name = $declaration->getChildByIndex(1);
|
||||
$decl_string = $decl_name->getConcreteString();
|
||||
|
||||
// Exclude strangely named classes, e.g. XHP tags.
|
||||
if (!preg_match('/^\w+$/', $decl_string)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rename = $decl_string.'.php';
|
||||
|
||||
$path = $this->getActivePath();
|
||||
$filename = basename($path);
|
||||
|
||||
if ($rename === $filename) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$decl_name,
|
||||
pht(
|
||||
"The name of this file differs from the name of the ".
|
||||
"class or interface it declares. Rename the file to '%s'.",
|
||||
$rename));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistClassNameLiteralXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 62;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Class Name Literal');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$class_declarations = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
|
||||
foreach ($class_declarations as $class_declaration) {
|
||||
$class_name = $class_declaration
|
||||
->getChildOfType(1, 'n_CLASS_NAME')
|
||||
->getConcreteString();
|
||||
|
||||
$strings = $class_declaration->selectDescendantsOfType('n_STRING_SCALAR');
|
||||
|
||||
foreach ($strings as $string) {
|
||||
$contents = substr($string->getSemanticString(), 1, -1);
|
||||
$replacement = null;
|
||||
|
||||
if ($contents == $class_name) {
|
||||
$replacement = '__CLASS__';
|
||||
}
|
||||
|
||||
$regex = '/\b'.preg_quote($class_name, '/').'\b/';
|
||||
if (!preg_match($regex, $contents)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$string,
|
||||
pht(
|
||||
"Don't hard-code class names, use %s instead.",
|
||||
'__CLASS__'),
|
||||
$replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistClosingCallParenthesesXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 37;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Call Formatting');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$calls = $root->selectDescendantsOfTypes(array(
|
||||
'n_FUNCTION_CALL',
|
||||
'n_METHOD_CALL',
|
||||
));
|
||||
|
||||
foreach ($calls as $call) {
|
||||
// If the last parameter of a call is a HEREDOC, don't apply this rule.
|
||||
$params = $call
|
||||
->getChildOfType(1, 'n_CALL_PARAMETER_LIST')
|
||||
->getChildren();
|
||||
|
||||
if ($params) {
|
||||
$last_param = last($params);
|
||||
if ($last_param->getTypeName() === 'n_HEREDOC') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$tokens = $call->getTokens();
|
||||
$last = array_pop($tokens);
|
||||
|
||||
$trailing = $last->getNonsemanticTokensBefore();
|
||||
$trailing_text = implode('', mpull($trailing, 'getValue'));
|
||||
if (preg_match('/^\s+$/', $trailing_text)) {
|
||||
$this->raiseLintAtOffset(
|
||||
$last->getOffset() - strlen($trailing_text),
|
||||
pht('Convention: no spaces before closing parenthesis in calls.'),
|
||||
$trailing_text,
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistClosingDeclarationParenthesesXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 38;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Declaration Formatting');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$decs = $root->selectDescendantsOfTypes(array(
|
||||
'n_FUNCTION_DECLARATION',
|
||||
'n_METHOD_DECLARATION',
|
||||
));
|
||||
|
||||
foreach ($decs as $dec) {
|
||||
$params = $dec->getChildOfType(3, 'n_DECLARATION_PARAMETER_LIST');
|
||||
$tokens = $params->getTokens();
|
||||
$last = array_pop($tokens);
|
||||
|
||||
$trailing = $last->getNonsemanticTokensBefore();
|
||||
$trailing_text = implode('', mpull($trailing, 'getValue'));
|
||||
|
||||
if (preg_match('/^\s+$/', $trailing_text)) {
|
||||
$this->raiseLintAtOffset(
|
||||
$last->getOffset() - strlen($trailing_text),
|
||||
pht(
|
||||
'Convention: no spaces before closing parenthesis in '.
|
||||
'function and method declarations.'),
|
||||
$trailing_text,
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistCommentSpacingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 34;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Comment Spaces');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
foreach ($root->selectTokensOfType('T_COMMENT') as $comment) {
|
||||
$value = $comment->getValue();
|
||||
|
||||
if ($value[0] !== '#') {
|
||||
$match = null;
|
||||
|
||||
if (preg_match('@^(/[/*]+)[^/*\s]@', $value, $match)) {
|
||||
$this->raiseLintAtOffset(
|
||||
$comment->getOffset(),
|
||||
pht('Put space after comment start.'),
|
||||
$match[1],
|
||||
$match[1].' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistCommentStyleXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 18;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Comment Style');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
foreach ($root->selectTokensOfType('T_COMMENT') as $comment) {
|
||||
$value = $comment->getValue();
|
||||
|
||||
if ($value[0] !== '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->raiseLintAtOffset(
|
||||
$comment->getOffset(),
|
||||
pht('Use "%s" single-line comments, not "%s".', '//', '#'),
|
||||
'#',
|
||||
preg_match('/^#\S/', $value) ? '// ' : '//');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistConcatenationOperatorXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 44;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Concatenation Spacing');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$tokens = $root->selectTokensOfType('.');
|
||||
foreach ($tokens as $token) {
|
||||
$prev = $token->getPrevToken();
|
||||
$next = $token->getNextToken();
|
||||
|
||||
foreach (array('prev' => $prev, 'next' => $next) as $wtoken) {
|
||||
if ($wtoken->getTypeName() !== 'T_WHITESPACE') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $wtoken->getValue();
|
||||
if (strpos($value, "\n") !== false) {
|
||||
// If the whitespace has a newline, it's conventional.
|
||||
continue;
|
||||
}
|
||||
|
||||
$next = $wtoken->getNextToken();
|
||||
if ($next && $next->getTypeName() === 'T_COMMENT') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->raiseLintAtToken(
|
||||
$wtoken,
|
||||
pht('Convention: no spaces around string concatenation operator.'),
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistConstructorParenthesesXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 49;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Constructor Parentheses');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$nodes = $root->selectDescendantsOfType('n_NEW');
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$class = $node->getChildByIndex(0);
|
||||
$params = $node->getChildByIndex(1);
|
||||
|
||||
if ($params->getTypeName() == 'n_EMPTY') {
|
||||
$this->raiseLintAtNode(
|
||||
$class,
|
||||
pht('Use parentheses when invoking a constructor.'),
|
||||
$class->getConcreteString().'()');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistControlStatementSpacingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 26;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Space After Control Statement');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
foreach ($root->getTokens() as $id => $token) {
|
||||
switch ($token->getTypeName()) {
|
||||
case 'T_IF':
|
||||
case 'T_ELSE':
|
||||
case 'T_FOR':
|
||||
case 'T_FOREACH':
|
||||
case 'T_WHILE':
|
||||
case 'T_DO':
|
||||
case 'T_SWITCH':
|
||||
$after = $token->getNonsemanticTokensAfter();
|
||||
if (empty($after)) {
|
||||
$this->raiseLintAtToken(
|
||||
$token,
|
||||
pht('Convention: put a space after control statements.'),
|
||||
$token->getValue().' ');
|
||||
} else if (count($after) === 1) {
|
||||
$space = head($after);
|
||||
|
||||
// If we have an else clause with braces, $space may not be
|
||||
// a single white space. e.g.,
|
||||
//
|
||||
// if ($x)
|
||||
// echo 'foo'
|
||||
// else // <- $space is not " " but "\n ".
|
||||
// echo 'bar'
|
||||
//
|
||||
// We just require it starts with either a whitespace or a newline.
|
||||
if ($token->getTypeName() === 'T_ELSE' ||
|
||||
$token->getTypeName() === 'T_DO') {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($space->isAnyWhitespace() && $space->getValue() !== ' ') {
|
||||
$this->raiseLintAtToken(
|
||||
$space,
|
||||
pht('Convention: put a single space after control statements.'),
|
||||
' ');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistDefaultParametersXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 60;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Default Parameters');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$parameter_lists = $root->selectDescendantsOfType(
|
||||
'n_DECLARATION_PARAMETER_LIST');
|
||||
|
||||
foreach ($parameter_lists as $parameter_list) {
|
||||
$default_found = false;
|
||||
$parameters = $parameter_list->selectDescendantsOfType(
|
||||
'n_DECLARATION_PARAMETER');
|
||||
|
||||
foreach ($parameters as $parameter) {
|
||||
$default_value = $parameter->getChildByIndex(2);
|
||||
|
||||
if ($default_value->getTypeName() != 'n_EMPTY') {
|
||||
$default_found = true;
|
||||
} else if ($default_found) {
|
||||
$this->raiseLintAtNode(
|
||||
$parameter_list,
|
||||
pht(
|
||||
'Arguments with default values must be at the end '.
|
||||
'of the argument list.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistDoubleQuoteXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 41;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Unnecessary Double Quotes');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$nodes = $root->selectDescendantsOfTypes(array(
|
||||
'n_CONCATENATION_LIST',
|
||||
'n_STRING_SCALAR',
|
||||
));
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$strings = array();
|
||||
|
||||
if ($node->getTypeName() === 'n_CONCATENATION_LIST') {
|
||||
$strings = $node->selectDescendantsOfType('n_STRING_SCALAR');
|
||||
} else if ($node->getTypeName() === 'n_STRING_SCALAR') {
|
||||
$strings = array($node);
|
||||
|
||||
if ($node->getParentNode()->getTypeName() === 'n_CONCATENATION_LIST') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$valid = false;
|
||||
$invalid_nodes = array();
|
||||
$fixes = array();
|
||||
|
||||
foreach ($strings as $string) {
|
||||
$concrete_string = $string->getConcreteString();
|
||||
$single_quoted = ($concrete_string[0] === "'");
|
||||
$contents = substr($concrete_string, 1, -1);
|
||||
|
||||
// Double quoted strings are allowed when the string contains the
|
||||
// following characters.
|
||||
static $allowed_chars = array(
|
||||
'\n',
|
||||
'\r',
|
||||
'\t',
|
||||
'\v',
|
||||
'\e',
|
||||
'\f',
|
||||
'\'',
|
||||
'\0',
|
||||
'\1',
|
||||
'\2',
|
||||
'\3',
|
||||
'\4',
|
||||
'\5',
|
||||
'\6',
|
||||
'\7',
|
||||
'\x',
|
||||
);
|
||||
|
||||
$contains_special_chars = false;
|
||||
foreach ($allowed_chars as $allowed_char) {
|
||||
if (strpos($contents, $allowed_char) !== false) {
|
||||
$contains_special_chars = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$string->isConstantString()) {
|
||||
$valid = true;
|
||||
} else if ($contains_special_chars && !$single_quoted) {
|
||||
$valid = true;
|
||||
} else if (!$contains_special_chars && !$single_quoted) {
|
||||
$invalid_nodes[] = $string;
|
||||
$fixes[$string->getID()] = "'".str_replace('\"', '"', $contents)."'";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$valid) {
|
||||
foreach ($invalid_nodes as $invalid_node) {
|
||||
$this->raiseLintAtNode(
|
||||
$invalid_node,
|
||||
pht(
|
||||
'String does not require double quotes. For consistency, '.
|
||||
'prefer single quotes.'),
|
||||
$fixes[$invalid_node->getID()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Finds duplicate keys in array initializers, as in
|
||||
* `array(1 => 'anything', 1 => 'foo')`. Since the first entry is ignored, this
|
||||
* is almost certainly an error.
|
||||
*/
|
||||
final class ArcanistDuplicateKeysInArrayXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 22;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Duplicate Keys in Array');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$array_literals = $root->selectDescendantsOfType('n_ARRAY_LITERAL');
|
||||
|
||||
foreach ($array_literals as $array_literal) {
|
||||
$nodes_by_key = array();
|
||||
$keys_warn = array();
|
||||
$list_node = $array_literal->getChildByIndex(0);
|
||||
|
||||
foreach ($list_node->getChildren() as $array_entry) {
|
||||
$key_node = $array_entry->getChildByIndex(0);
|
||||
|
||||
switch ($key_node->getTypeName()) {
|
||||
case 'n_STRING_SCALAR':
|
||||
case 'n_NUMERIC_SCALAR':
|
||||
// Scalars: array(1 => 'v1', '1' => 'v2');
|
||||
$key = 'scalar:'.(string)$key_node->evalStatic();
|
||||
break;
|
||||
|
||||
case 'n_SYMBOL_NAME':
|
||||
case 'n_VARIABLE':
|
||||
case 'n_CLASS_STATIC_ACCESS':
|
||||
// Constants: array(CONST => 'v1', CONST => 'v2');
|
||||
// Variables: array($a => 'v1', $a => 'v2');
|
||||
// Class constants and vars: array(C::A => 'v1', C::A => 'v2');
|
||||
$key = $key_node->getTypeName().':'.$key_node->getConcreteString();
|
||||
break;
|
||||
|
||||
default:
|
||||
$key = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($key !== null) {
|
||||
if (isset($nodes_by_key[$key])) {
|
||||
$keys_warn[$key] = true;
|
||||
}
|
||||
$nodes_by_key[$key][] = $key_node;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($keys_warn as $key => $_) {
|
||||
$node = array_pop($nodes_by_key[$key]);
|
||||
$message = $this->raiseLintAtNode(
|
||||
$node,
|
||||
pht(
|
||||
'Duplicate key in array initializer. '.
|
||||
'PHP will ignore all but the last entry.'));
|
||||
|
||||
$locations = array();
|
||||
foreach ($nodes_by_key[$key] as $node) {
|
||||
$locations[] = $this->getOtherLocation($node->getOffset());
|
||||
}
|
||||
$message->setOtherLocations($locations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistDuplicateSwitchCaseXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 50;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Duplicate Case Statements');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$switch_statements = $root->selectDescendantsOfType('n_SWITCH');
|
||||
|
||||
foreach ($switch_statements as $switch_statement) {
|
||||
$case_statements = $switch_statement
|
||||
->getChildOfType(1, 'n_STATEMENT_LIST')
|
||||
->getChildrenOfType('n_CASE');
|
||||
$nodes_by_case = array();
|
||||
|
||||
foreach ($case_statements as $case_statement) {
|
||||
$case = $case_statement
|
||||
->getChildByIndex(0)
|
||||
->getSemanticString();
|
||||
$nodes_by_case[$case][] = $case_statement;
|
||||
}
|
||||
|
||||
foreach ($nodes_by_case as $case => $nodes) {
|
||||
if (count($nodes) <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$node = array_pop($nodes_by_case[$case]);
|
||||
$message = $this->raiseLintAtNode(
|
||||
$node,
|
||||
pht(
|
||||
'Duplicate case in switch statement. PHP will ignore all '.
|
||||
'but the first case.'));
|
||||
|
||||
$locations = array();
|
||||
foreach ($nodes_by_case[$case] as $node) {
|
||||
$locations[] = $this->getOtherLocation($node->getOffset());
|
||||
}
|
||||
$message->setOtherLocations($locations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistDynamicDefineXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 12;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Dynamic %s', 'define()');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$calls = $this->getFunctionCalls($root, array('define'));
|
||||
|
||||
foreach ($calls as $call) {
|
||||
$parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
|
||||
$defined = $parameter_list->getChildByIndex(0);
|
||||
|
||||
if (!$defined->isStaticScalar()) {
|
||||
$this->raiseLintAtNode(
|
||||
$defined,
|
||||
pht(
|
||||
'First argument to %s must be a string literal.',
|
||||
'define()'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistElseIfUsageXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 42;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('ElseIf Usage');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$tokens = $root->selectTokensOfType('T_ELSEIF');
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
$this->raiseLintAtToken(
|
||||
$token,
|
||||
pht('Usage of `%s` is preferred over `%s`.', 'else if', 'elseif'),
|
||||
'else if');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistEmptyStatementXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 47;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Empty Block Statement');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$nodes = $root->selectDescendantsOfType('n_STATEMENT_LIST');
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$tokens = $node->getTokens();
|
||||
$token = head($tokens);
|
||||
|
||||
if (count($tokens) <= 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Safety check... if the first token isn't an opening brace then
|
||||
// there's nothing to do here.
|
||||
if ($token->getTypeName() != '{') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$only_whitespace = true;
|
||||
for ($token = $token->getNextToken();
|
||||
$token && $token->getTypeName() != '}';
|
||||
$token = $token->getNextToken()) {
|
||||
$only_whitespace = $only_whitespace && $token->isAnyWhitespace();
|
||||
}
|
||||
|
||||
if (count($tokens) > 2 && $only_whitespace) {
|
||||
$this->raiseLintAtNode(
|
||||
$node,
|
||||
pht(
|
||||
"Braces for an empty block statement shouldn't ".
|
||||
"contain only whitespace."),
|
||||
'{}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Exit is parsed as an expression, but using it as such is almost always
|
||||
* wrong. That is, this is valid:
|
||||
*
|
||||
* strtoupper(33 * exit - 6);
|
||||
*
|
||||
* When exit is used as an expression, it causes the program to terminate with
|
||||
* exit code 0. This is likely not what is intended; these statements have
|
||||
* different effects:
|
||||
*
|
||||
* exit(-1);
|
||||
* exit -1;
|
||||
*
|
||||
* The former exits with a failure code, the latter with a success code!
|
||||
*/
|
||||
final class ArcanistExitExpressionXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 17;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Exit Used as Expression');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$unaries = $root->selectDescendantsOfType('n_UNARY_PREFIX_EXPRESSION');
|
||||
|
||||
foreach ($unaries as $unary) {
|
||||
$operator = $unary->getChildByIndex(0)->getConcreteString();
|
||||
|
||||
if (strtolower($operator) === 'exit') {
|
||||
if ($unary->getParentNode()->getTypeName() !== 'n_STATEMENT') {
|
||||
$this->raiseLintAtNode(
|
||||
$unary,
|
||||
pht('Use `%s` as a statement, not an expression.', 'exit'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistExtractUseXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 4;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Use of %s', 'extract()');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$calls = $this->getFunctionCalls($root, array('extract'));
|
||||
|
||||
foreach ($calls as $call) {
|
||||
$this->raiseLintAtNode(
|
||||
$call,
|
||||
pht(
|
||||
'Avoid %s. It is confusing and hinders static analysis.',
|
||||
'extract()'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistFormattedStringXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 54;
|
||||
|
||||
private $printfFunctions = array();
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Formatted String');
|
||||
}
|
||||
|
||||
public function getLinterConfigurationOptions() {
|
||||
return parent::getLinterConfigurationOptions() + array(
|
||||
'xhpast.printf-functions' => array(
|
||||
'type' => 'optional map<string, int>',
|
||||
'help' => pht(
|
||||
'%s-style functions which take a format string and list of values '.
|
||||
'as arguments. The value for the mapping is the start index of the '.
|
||||
'function parameters (the index of the format string parameter).',
|
||||
'printf()'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function setLinterConfigurationValue($key, $value) {
|
||||
switch ($key) {
|
||||
case 'xhpast.printf-functions':
|
||||
$this->printfFunctions = $value;
|
||||
return;
|
||||
default:
|
||||
return parent::setLinterConfigurationValue($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
static $functions = array(
|
||||
// Core PHP
|
||||
'fprintf' => 1,
|
||||
'printf' => 0,
|
||||
'sprintf' => 0,
|
||||
'vfprintf' => 1,
|
||||
|
||||
// libphutil
|
||||
'csprintf' => 0,
|
||||
'execx' => 0,
|
||||
'exec_manual' => 0,
|
||||
'hgsprintf' => 0,
|
||||
'hsprintf' => 0,
|
||||
'jsprintf' => 0,
|
||||
'pht' => 0,
|
||||
'phutil_passthru' => 0,
|
||||
'qsprintf' => 1,
|
||||
'queryfx' => 1,
|
||||
'queryfx_all' => 1,
|
||||
'queryfx_one' => 1,
|
||||
'vcsprintf' => 0,
|
||||
'vqsprintf' => 1,
|
||||
);
|
||||
|
||||
$function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
|
||||
|
||||
foreach ($function_calls as $call) {
|
||||
$name = $call->getChildByIndex(0)->getConcreteString();
|
||||
|
||||
$name = strtolower($name);
|
||||
$start = idx($functions + $this->printfFunctions, $name);
|
||||
|
||||
if ($start === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameters = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
|
||||
$argc = count($parameters->getChildren()) - $start;
|
||||
|
||||
if ($argc < 1) {
|
||||
$this->raiseLintAtNode(
|
||||
$call,
|
||||
pht('This function is expected to have a format string.'));
|
||||
continue;
|
||||
}
|
||||
|
||||
$format = $parameters->getChildByIndex($start);
|
||||
if ($format->getTypeName() != 'n_STRING_SCALAR') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$argv = array($format->evalStatic()) + array_fill(0, $argc, null);
|
||||
|
||||
try {
|
||||
xsprintf(null, null, $argv);
|
||||
} catch (BadFunctionCallException $ex) {
|
||||
$this->raiseLintAtNode(
|
||||
$call,
|
||||
str_replace('xsprintf', $name, $ex->getMessage()));
|
||||
} catch (InvalidArgumentException $ex) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistImplicitConstructorXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 10;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Implicit Constructor');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$class_name = $class->getChildByIndex(1)->getConcreteString();
|
||||
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$method_name_token = $method->getChildByIndex(2);
|
||||
$method_name = $method_name_token->getConcreteString();
|
||||
|
||||
if (strtolower($class_name) === strtolower($method_name)) {
|
||||
$this->raiseLintAtNode(
|
||||
$method_name_token,
|
||||
pht(
|
||||
'Name constructors %s explicitly. This method is a constructor '.
|
||||
' because it has the same name as the class it is defined in.',
|
||||
'__construct()'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistImplicitFallthroughXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 30;
|
||||
|
||||
private $switchhook;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Implicit Fallthrough');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function getLinterConfigurationOptions() {
|
||||
return parent::getLinterConfigurationOptions() + array(
|
||||
'xhpast.switchhook' => array(
|
||||
'type' => 'optional string',
|
||||
'help' => pht(
|
||||
'Name of a concrete subclass of %s which tunes the '.
|
||||
'analysis of %s statements for this linter.',
|
||||
'ArcanistXHPASTLintSwitchHook',
|
||||
'switch()'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function setLinterConfigurationValue($key, $value) {
|
||||
switch ($key) {
|
||||
case 'xhpast.switchhook':
|
||||
$this->switchhook = $value;
|
||||
return;
|
||||
|
||||
default:
|
||||
return parent::setLinterConfigurationValue($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$hook_obj = null;
|
||||
$hook_class = $this->switchhook;
|
||||
|
||||
if ($hook_class) {
|
||||
$hook_obj = newv($hook_class, array());
|
||||
assert_instances_of(array($hook_obj), 'ArcanistXHPASTLintSwitchHook');
|
||||
}
|
||||
|
||||
$switches = $root->selectDescendantsOfType('n_SWITCH');
|
||||
foreach ($switches as $switch) {
|
||||
$blocks = array();
|
||||
|
||||
$cases = $switch->selectDescendantsOfType('n_CASE');
|
||||
foreach ($cases as $case) {
|
||||
$blocks[] = $case;
|
||||
}
|
||||
|
||||
$defaults = $switch->selectDescendantsOfType('n_DEFAULT');
|
||||
foreach ($defaults as $default) {
|
||||
$blocks[] = $default;
|
||||
}
|
||||
|
||||
|
||||
foreach ($blocks as $key => $block) {
|
||||
// Collect all the tokens in this block which aren't at top level.
|
||||
// We want to ignore "break", and "continue" in these blocks.
|
||||
$lower_level = $block->selectDescendantsOfTypes(array(
|
||||
'n_WHILE',
|
||||
'n_DO_WHILE',
|
||||
'n_FOR',
|
||||
'n_FOREACH',
|
||||
'n_SWITCH',
|
||||
));
|
||||
$lower_level_tokens = array();
|
||||
foreach ($lower_level as $lower_level_block) {
|
||||
$lower_level_tokens += $lower_level_block->getTokens();
|
||||
}
|
||||
|
||||
// Collect all the tokens in this block which aren't in this scope
|
||||
// (because they're inside class, function or interface declarations).
|
||||
// We want to ignore all of these tokens.
|
||||
$decls = $block->selectDescendantsOfTypes(array(
|
||||
'n_FUNCTION_DECLARATION',
|
||||
'n_CLASS_DECLARATION',
|
||||
|
||||
// For completeness; these can't actually have anything.
|
||||
'n_INTERFACE_DECLARATION',
|
||||
));
|
||||
|
||||
$different_scope_tokens = array();
|
||||
foreach ($decls as $decl) {
|
||||
$different_scope_tokens += $decl->getTokens();
|
||||
}
|
||||
|
||||
$lower_level_tokens += $different_scope_tokens;
|
||||
|
||||
// Get all the trailing nonsemantic tokens, since we need to look for
|
||||
// "fallthrough" comments past the end of the semantic block.
|
||||
|
||||
$tokens = $block->getTokens();
|
||||
$last = end($tokens);
|
||||
while ($last && $last = $last->getNextToken()) {
|
||||
if ($last->isSemantic()) {
|
||||
break;
|
||||
}
|
||||
$tokens[$last->getTokenID()] = $last;
|
||||
}
|
||||
|
||||
$blocks[$key] = array(
|
||||
$tokens,
|
||||
$lower_level_tokens,
|
||||
$different_scope_tokens,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($blocks as $token_lists) {
|
||||
list(
|
||||
$tokens,
|
||||
$lower_level_tokens,
|
||||
$different_scope_tokens) = $token_lists;
|
||||
|
||||
// Test each block (case or default statement) to see if it's OK. It's
|
||||
// OK if:
|
||||
//
|
||||
// - it is empty; or
|
||||
// - it ends in break, return, throw, continue or exit at top level; or
|
||||
// - it has a comment with "fallthrough" in its text.
|
||||
|
||||
// Empty blocks are OK, so we start this at `true` and only set it to
|
||||
// false if we find a statement.
|
||||
$block_ok = true;
|
||||
|
||||
// Keeps track of whether the current statement is one that validates
|
||||
// the block (break, return, throw, continue) or something else.
|
||||
$statement_ok = false;
|
||||
|
||||
foreach ($tokens as $token_id => $token) {
|
||||
if (!$token->isSemantic()) {
|
||||
// Liberally match "fall" in the comment text so that comments like
|
||||
// "fallthru", "fall through", "fallthrough", etc., are accepted.
|
||||
if (preg_match('/fall/i', $token->getValue())) {
|
||||
$block_ok = true;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$tok_type = $token->getTypeName();
|
||||
|
||||
if ($tok_type === 'T_FUNCTION' ||
|
||||
$tok_type === 'T_CLASS' ||
|
||||
$tok_type === 'T_INTERFACE') {
|
||||
// These aren't statements, but mark the block as nonempty anyway.
|
||||
$block_ok = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tok_type === ';') {
|
||||
if ($statement_ok) {
|
||||
$statment_ok = false;
|
||||
} else {
|
||||
$block_ok = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tok_type === 'T_BREAK' || $tok_type === 'T_CONTINUE') {
|
||||
if (empty($lower_level_tokens[$token_id])) {
|
||||
$statement_ok = true;
|
||||
$block_ok = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tok_type === 'T_RETURN' ||
|
||||
$tok_type === 'T_THROW' ||
|
||||
$tok_type === 'T_EXIT' ||
|
||||
($hook_obj && $hook_obj->checkSwitchToken($token))) {
|
||||
if (empty($different_scope_tokens[$token_id])) {
|
||||
$statement_ok = true;
|
||||
$block_ok = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$block_ok) {
|
||||
$this->raiseLintAtToken(
|
||||
head($tokens),
|
||||
pht(
|
||||
"This '%s' or '%s' has a nonempty block which does not end ".
|
||||
"with '%s', '%s', '%s', '%s' or '%s'. Did you forget to add ".
|
||||
"one of those? If you intend to fall through, add a '%s' ".
|
||||
"comment to silence this warning.",
|
||||
'case',
|
||||
'default',
|
||||
'break',
|
||||
'continue',
|
||||
'return',
|
||||
'throw',
|
||||
'exit',
|
||||
'// fallthrough'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistImplicitVisibilityXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 52;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Implicit Method Visibility');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$this->lintMethodVisibility($root);
|
||||
$this->lintPropertyVisibility($root);
|
||||
}
|
||||
|
||||
private function lintMethodVisibility(XHPASTNode $root) {
|
||||
static $visibilities = array(
|
||||
'public',
|
||||
'protected',
|
||||
'private',
|
||||
);
|
||||
|
||||
$methods = $root->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$modifiers_list = $method->getChildOfType(0, 'n_METHOD_MODIFIER_LIST');
|
||||
|
||||
foreach ($modifiers_list->getChildren() as $modifier) {
|
||||
if (in_array($modifier->getConcreteString(), $visibilities)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if ($modifiers_list->getChildren()) {
|
||||
$node = $modifiers_list;
|
||||
} else {
|
||||
$node = $method;
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$node,
|
||||
pht('Methods should have their visibility declared explicitly.'),
|
||||
'public '.$node->getConcreteString());
|
||||
}
|
||||
}
|
||||
|
||||
private function lintPropertyVisibility(XHPASTNode $root) {
|
||||
static $visibilities = array(
|
||||
'public',
|
||||
'protected',
|
||||
'private',
|
||||
);
|
||||
|
||||
$nodes = $root->selectDescendantsOfType('n_CLASS_MEMBER_MODIFIER_LIST');
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$modifiers = $node->getChildren();
|
||||
|
||||
foreach ($modifiers as $modifier) {
|
||||
if ($modifier->getConcreteString() == 'var') {
|
||||
$this->raiseLintAtNode(
|
||||
$modifier,
|
||||
pht(
|
||||
'Use `%s` instead of `%s` to indicate public visibility.',
|
||||
'public',
|
||||
'var'),
|
||||
'public');
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if (in_array($modifier->getConcreteString(), $visibilities)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$node,
|
||||
pht('Properties should have their visibility declared explicitly.'),
|
||||
'public '.$node->getConcreteString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistInnerFunctionXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 59;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Inner Functions');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$function_decls = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
|
||||
|
||||
foreach ($function_decls as $function_declaration) {
|
||||
$inner_functions = $function_declaration
|
||||
->selectDescendantsOfType('n_FUNCTION_DECLARATION');
|
||||
|
||||
foreach ($inner_functions as $inner_function) {
|
||||
if ($inner_function->getChildByIndex(2)->getTypeName() == 'n_EMPTY') {
|
||||
// Anonymous closure.
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$inner_function,
|
||||
pht('Avoid the use of inner functions.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistInstanceOfOperatorXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 69;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('%s Operator', 'instanceof');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
|
||||
foreach ($expressions as $expression) {
|
||||
$operator = $expression->getChildOfType(1, 'n_OPERATOR');
|
||||
|
||||
if (strtolower($operator->getConcreteString()) != 'instanceof') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$object = $expression->getChildByIndex(0);
|
||||
|
||||
if ($object->isStaticScalar() ||
|
||||
$object->getTypeName() == 'n_SYMBOL_NAME') {
|
||||
$this->raiseLintAtNode(
|
||||
$object,
|
||||
pht(
|
||||
'%s expects an object instance, constant given.',
|
||||
'instanceof'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistInvalidDefaultParameterXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 70;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Invalid Default Parameter');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$parameters = $root->selectDescendantsOfType('n_DECLARATION_PARAMETER');
|
||||
|
||||
foreach ($parameters as $parameter) {
|
||||
$type = $parameter->getChildByIndex(0);
|
||||
$default = $parameter->getChildByIndex(2);
|
||||
|
||||
if ($type->getTypeName() == 'n_EMPTY') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($default->getTypeName() == 'n_EMPTY') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$default_is_null = $default->getTypeName() == 'n_SYMBOL_NAME' &&
|
||||
strtolower($default->getConcreteString()) == 'null';
|
||||
|
||||
switch (strtolower($type->getConcreteString())) {
|
||||
case 'array':
|
||||
if ($default->getTypeName() == 'n_ARRAY_LITERAL') {
|
||||
break;
|
||||
}
|
||||
if ($default_is_null) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$default,
|
||||
pht(
|
||||
'Default value for parameters with %s type hint '.
|
||||
'can only be an %s or %s.',
|
||||
'array',
|
||||
'array',
|
||||
'null'));
|
||||
break;
|
||||
|
||||
case 'callable':
|
||||
if ($default_is_null) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$default,
|
||||
pht(
|
||||
'Default value for parameters with %s type hint can only be %s.',
|
||||
'callable',
|
||||
'null'));
|
||||
break;
|
||||
|
||||
default:
|
||||
// Class/interface parameter.
|
||||
if ($default_is_null) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$default,
|
||||
pht(
|
||||
'Default value for parameters with a class type hint '.
|
||||
'can only be %s.',
|
||||
'null'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistInvalidModifiersXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 72;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Invalid Modifiers');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$methods = $root->selectDescendantsOfTypes(array(
|
||||
'n_CLASS_MEMBER_MODIFIER_LIST',
|
||||
'n_METHOD_MODIFIER_LIST',
|
||||
));
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$modifiers = $method->getChildren();
|
||||
|
||||
$is_abstract = false;
|
||||
$is_final = false;
|
||||
$is_static = false;
|
||||
$visibility = null;
|
||||
|
||||
foreach ($modifiers as $modifier) {
|
||||
switch ($modifier->getConcreteString()) {
|
||||
case 'abstract':
|
||||
if ($method->getTypeName() == 'n_CLASS_MEMBER_MODIFIER_LIST') {
|
||||
$this->raiseLintAtNode(
|
||||
$modifier,
|
||||
pht(
|
||||
'Properties cannot be declared %s.',
|
||||
'abstract'));
|
||||
}
|
||||
|
||||
if ($is_abstract) {
|
||||
$this->raiseLintAtNode(
|
||||
$modifier,
|
||||
pht(
|
||||
'Multiple %s modifiers are not allowed.',
|
||||
'abstract'));
|
||||
}
|
||||
|
||||
if ($is_final) {
|
||||
$this->raiseLintAtNode(
|
||||
$modifier,
|
||||
pht(
|
||||
'Cannot use the %s modifier on an %s class member',
|
||||
'final',
|
||||
'abstract'));
|
||||
}
|
||||
|
||||
$is_abstract = true;
|
||||
break;
|
||||
|
||||
case 'final':
|
||||
if ($is_abstract) {
|
||||
$this->raiseLintAtNode(
|
||||
$modifier,
|
||||
pht(
|
||||
'Cannot use the %s modifier on an %s class member',
|
||||
'final',
|
||||
'abstract'));
|
||||
}
|
||||
|
||||
if ($is_final) {
|
||||
$this->raiseLintAtNode(
|
||||
$modifier,
|
||||
pht(
|
||||
'Multiple %s modifiers are not allowed.',
|
||||
'final'));
|
||||
}
|
||||
|
||||
$is_final = true;
|
||||
break;
|
||||
case 'public':
|
||||
case 'protected':
|
||||
case 'private':
|
||||
if ($visibility) {
|
||||
$this->raiseLintAtNode(
|
||||
$modifier,
|
||||
pht('Multiple access type modifiers are not allowed.'));
|
||||
}
|
||||
|
||||
$visibility = $modifier->getConcreteString();
|
||||
break;
|
||||
|
||||
case 'static':
|
||||
if ($is_static) {
|
||||
$this->raiseLintAtNode(
|
||||
$modifier,
|
||||
pht(
|
||||
'Multiple %s modifiers are not allowed.',
|
||||
'static'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistKeywordCasingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 40;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Keyword Conventions');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$keywords = $root->selectTokensOfTypes(array(
|
||||
'T_REQUIRE_ONCE',
|
||||
'T_REQUIRE',
|
||||
'T_EVAL',
|
||||
'T_INCLUDE_ONCE',
|
||||
'T_INCLUDE',
|
||||
'T_LOGICAL_OR',
|
||||
'T_LOGICAL_XOR',
|
||||
'T_LOGICAL_AND',
|
||||
'T_PRINT',
|
||||
'T_INSTANCEOF',
|
||||
'T_CLONE',
|
||||
'T_NEW',
|
||||
'T_EXIT',
|
||||
'T_IF',
|
||||
'T_ELSEIF',
|
||||
'T_ELSE',
|
||||
'T_ENDIF',
|
||||
'T_ECHO',
|
||||
'T_DO',
|
||||
'T_WHILE',
|
||||
'T_ENDWHILE',
|
||||
'T_FOR',
|
||||
'T_ENDFOR',
|
||||
'T_FOREACH',
|
||||
'T_ENDFOREACH',
|
||||
'T_DECLARE',
|
||||
'T_ENDDECLARE',
|
||||
'T_AS',
|
||||
'T_SWITCH',
|
||||
'T_ENDSWITCH',
|
||||
'T_CASE',
|
||||
'T_DEFAULT',
|
||||
'T_BREAK',
|
||||
'T_CONTINUE',
|
||||
'T_GOTO',
|
||||
'T_FUNCTION',
|
||||
'T_CONST',
|
||||
'T_RETURN',
|
||||
'T_TRY',
|
||||
'T_CATCH',
|
||||
'T_THROW',
|
||||
'T_USE',
|
||||
'T_GLOBAL',
|
||||
'T_PUBLIC',
|
||||
'T_PROTECTED',
|
||||
'T_PRIVATE',
|
||||
'T_FINAL',
|
||||
'T_ABSTRACT',
|
||||
'T_STATIC',
|
||||
'T_VAR',
|
||||
'T_UNSET',
|
||||
'T_ISSET',
|
||||
'T_EMPTY',
|
||||
'T_HALT_COMPILER',
|
||||
'T_CLASS',
|
||||
'T_INTERFACE',
|
||||
'T_EXTENDS',
|
||||
'T_IMPLEMENTS',
|
||||
'T_LIST',
|
||||
'T_ARRAY',
|
||||
'T_NAMESPACE',
|
||||
'T_INSTEADOF',
|
||||
'T_CALLABLE',
|
||||
'T_TRAIT',
|
||||
'T_YIELD',
|
||||
'T_FINALLY',
|
||||
));
|
||||
foreach ($keywords as $keyword) {
|
||||
$value = $keyword->getValue();
|
||||
|
||||
if ($value != strtolower($value)) {
|
||||
$this->raiseLintAtToken(
|
||||
$keyword,
|
||||
pht(
|
||||
"Convention: spell keyword '%s' as '%s'.",
|
||||
$value,
|
||||
strtolower($value)),
|
||||
strtolower($value));
|
||||
}
|
||||
}
|
||||
|
||||
$symbols = $root->selectDescendantsOfType('n_SYMBOL_NAME');
|
||||
foreach ($symbols as $symbol) {
|
||||
static $interesting_symbols = array(
|
||||
'false' => true,
|
||||
'null' => true,
|
||||
'true' => true,
|
||||
);
|
||||
|
||||
$symbol_name = $symbol->getConcreteString();
|
||||
|
||||
if ($symbol->getParentNode()->getTypeName() == 'n_FUNCTION_CALL') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idx($interesting_symbols, strtolower($symbol_name))) {
|
||||
if ($symbol_name != strtolower($symbol_name)) {
|
||||
$this->raiseLintAtNode(
|
||||
$symbol,
|
||||
pht(
|
||||
"Convention: spell keyword '%s' as '%s'.",
|
||||
$symbol_name,
|
||||
strtolower($symbol_name)),
|
||||
strtolower($symbol_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$magic_constants = $root->selectTokensOfTypes(array(
|
||||
'T_CLASS_C',
|
||||
'T_METHOD_C',
|
||||
'T_FUNC_C',
|
||||
'T_LINE',
|
||||
'T_FILE',
|
||||
'T_NS_C',
|
||||
'T_DIR',
|
||||
'T_TRAIT_C',
|
||||
));
|
||||
|
||||
foreach ($magic_constants as $magic_constant) {
|
||||
$value = $magic_constant->getValue();
|
||||
|
||||
if ($value != strtoupper($value)) {
|
||||
$this->raiseLintAtToken(
|
||||
$magic_constant,
|
||||
pht('Magic constants should be uppercase.'),
|
||||
strtoupper($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistLambdaFuncFunctionXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 68;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('%s Function', '__lambda_func');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$function_declarations = $root
|
||||
->selectDescendantsOfType('n_FUNCTION_DECLARATION');
|
||||
|
||||
foreach ($function_declarations as $function_declaration) {
|
||||
$function_name = $function_declaration->getChildByIndex(2);
|
||||
|
||||
if ($function_name->getTypeName() == 'n_EMPTY') {
|
||||
// Anonymous closure.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($function_name->getConcreteString() != '__lambda_func') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$function_declaration,
|
||||
pht(
|
||||
'Declaring a function named %s causes any call to %s to fail. '.
|
||||
'This is because %s eval-declares the function %s, then '.
|
||||
'modifies the symbol table so that the function is instead '.
|
||||
'named %s, and returns that name.',
|
||||
'__lambda_func',
|
||||
'create_function',
|
||||
'create_function',
|
||||
'__lambda_func',
|
||||
'"\0lambda_".(++$i)'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistLanguageConstructParenthesesXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 46;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Language Construct Parentheses');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$nodes = $root->selectDescendantsOfTypes(array(
|
||||
'n_INCLUDE_FILE',
|
||||
'n_ECHO_LIST',
|
||||
));
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$child = head($node->getChildren());
|
||||
|
||||
if ($child->getTypeName() === 'n_PARENTHETICAL_EXPRESSION') {
|
||||
list($before, $after) = $child->getSurroundingNonsemanticTokens();
|
||||
|
||||
$replace = preg_replace(
|
||||
'/^\((.*)\)$/',
|
||||
'$1',
|
||||
$child->getConcreteString());
|
||||
|
||||
if (!$before) {
|
||||
$replace = ' '.$replace;
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$child,
|
||||
pht('Language constructs do not require parentheses.'),
|
||||
$replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistLogicalOperatorsXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 58;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Logical Operators');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$logical_ands = $root->selectTokensOfType('T_LOGICAL_AND');
|
||||
$logical_ors = $root->selectTokensOfType('T_LOGICAL_OR');
|
||||
|
||||
foreach ($logical_ands as $logical_and) {
|
||||
$this->raiseLintAtToken(
|
||||
$logical_and,
|
||||
pht('Use `%s` instead of `%s`.', '&&', 'and'),
|
||||
'&&');
|
||||
}
|
||||
|
||||
foreach ($logical_ors as $logical_or) {
|
||||
$this->raiseLintAtToken(
|
||||
$logical_or,
|
||||
pht('Use `%s` instead of `%s`.', '||', 'or'),
|
||||
'||');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistLowercaseFunctionsXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 61;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Lowercase Functions');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
static $builtin_functions = null;
|
||||
|
||||
if ($builtin_functions === null) {
|
||||
$builtin_functions = array_fuse(
|
||||
idx(get_defined_functions(), 'internal', array()));
|
||||
}
|
||||
|
||||
$function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
|
||||
|
||||
foreach ($function_calls as $function_call) {
|
||||
$function = $function_call->getChildByIndex(0);
|
||||
|
||||
if ($function->getTypeName() != 'n_SYMBOL_NAME') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$function_name = $function->getConcreteString();
|
||||
|
||||
if (!idx($builtin_functions, strtolower($function_name))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($function_name != strtolower($function_name)) {
|
||||
$this->raiseLintAtNode(
|
||||
$function,
|
||||
pht('Calls to built-in PHP functions should be lowercase.'),
|
||||
strtolower($function_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistModifierOrderingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 71;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Modifier Ordering');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$this->lintMethodModifierOrdering($root);
|
||||
$this->lintPropertyModifierOrdering($root);
|
||||
}
|
||||
|
||||
private function lintMethodModifierOrdering(XHPASTNode $root) {
|
||||
static $modifiers = array(
|
||||
'abstract',
|
||||
'final',
|
||||
'public',
|
||||
'protected',
|
||||
'private',
|
||||
'static',
|
||||
);
|
||||
|
||||
$methods = $root->selectDescendantsOfType('n_METHOD_MODIFIER_LIST');
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$modifier_ordering = array_values(
|
||||
mpull($method->getChildren(), 'getConcreteString'));
|
||||
$expected_modifier_ordering = array_values(
|
||||
array_intersect(
|
||||
$modifiers,
|
||||
$modifier_ordering));
|
||||
|
||||
if (count($modifier_ordering) != count($expected_modifier_ordering)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($modifier_ordering != $expected_modifier_ordering) {
|
||||
$this->raiseLintAtNode(
|
||||
$method,
|
||||
pht('Non-conventional modifier ordering.'),
|
||||
implode(' ', $expected_modifier_ordering));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function lintPropertyModifierOrdering(XHPASTNode $root) {
|
||||
static $modifiers = array(
|
||||
'public',
|
||||
'protected',
|
||||
'private',
|
||||
'static',
|
||||
);
|
||||
|
||||
$properties = $root->selectDescendantsOfType(
|
||||
'n_CLASS_MEMBER_MODIFIER_LIST');
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$modifier_ordering = array_values(
|
||||
mpull($property->getChildren(), 'getConcreteString'));
|
||||
$expected_modifier_ordering = array_values(
|
||||
array_intersect(
|
||||
$modifiers,
|
||||
$modifier_ordering));
|
||||
|
||||
if (count($modifier_ordering) != count($expected_modifier_ordering)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($modifier_ordering != $expected_modifier_ordering) {
|
||||
$this->raiseLintAtNode(
|
||||
$property,
|
||||
pht('Non-conventional modifier ordering.'),
|
||||
implode(' ', $expected_modifier_ordering));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,349 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistNamingConventionsXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 9;
|
||||
|
||||
private $naminghook;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Naming Conventions');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function getLinterConfigurationOptions() {
|
||||
return parent::getLinterConfigurationOptions() + array(
|
||||
'xhpast.naminghook' => array(
|
||||
'type' => 'optional string',
|
||||
'help' => pht(
|
||||
'Name of a concrete subclass of %s which enforces more '.
|
||||
'granular naming convention rules for symbols.',
|
||||
'ArcanistXHPASTLintNamingHook'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function setLinterConfigurationValue($key, $value) {
|
||||
switch ($key) {
|
||||
case 'xhpast.naminghook':
|
||||
$this->naminghook = $value;
|
||||
return;
|
||||
|
||||
default:
|
||||
return parent::setLinterConfigurationValue($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
// We're going to build up a list of <type, name, token, error> tuples
|
||||
// and then try to instantiate a hook class which has the opportunity to
|
||||
// override us.
|
||||
$names = array();
|
||||
|
||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
foreach ($classes as $class) {
|
||||
$name_token = $class->getChildByIndex(1);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
|
||||
$names[] = array(
|
||||
'class',
|
||||
$name_string,
|
||||
$name_token,
|
||||
ArcanistXHPASTLintNamingHook::isUpperCamelCase($name_string)
|
||||
? null
|
||||
: pht(
|
||||
'Follow naming conventions: classes should be named using '.
|
||||
'UpperCamelCase.'),
|
||||
);
|
||||
}
|
||||
|
||||
$ifaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION');
|
||||
foreach ($ifaces as $iface) {
|
||||
$name_token = $iface->getChildByIndex(1);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
$names[] = array(
|
||||
'interface',
|
||||
$name_string,
|
||||
$name_token,
|
||||
ArcanistXHPASTLintNamingHook::isUpperCamelCase($name_string)
|
||||
? null
|
||||
: pht(
|
||||
'Follow naming conventions: interfaces should be named using '.
|
||||
'UpperCamelCase.'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$functions = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
|
||||
foreach ($functions as $function) {
|
||||
$name_token = $function->getChildByIndex(2);
|
||||
if ($name_token->getTypeName() === 'n_EMPTY') {
|
||||
// Unnamed closure.
|
||||
continue;
|
||||
}
|
||||
$name_string = $name_token->getConcreteString();
|
||||
$names[] = array(
|
||||
'function',
|
||||
$name_string,
|
||||
$name_token,
|
||||
ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores(
|
||||
ArcanistXHPASTLintNamingHook::stripPHPFunction($name_string))
|
||||
? null
|
||||
: pht(
|
||||
'Follow naming conventions: functions should be named using '.
|
||||
'lowercase_with_underscores.'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$methods = $root->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
foreach ($methods as $method) {
|
||||
$name_token = $method->getChildByIndex(2);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
$names[] = array(
|
||||
'method',
|
||||
$name_string,
|
||||
$name_token,
|
||||
ArcanistXHPASTLintNamingHook::isLowerCamelCase(
|
||||
ArcanistXHPASTLintNamingHook::stripPHPFunction($name_string))
|
||||
? null
|
||||
: pht(
|
||||
'Follow naming conventions: methods should be named using '.
|
||||
'lowerCamelCase.'),
|
||||
);
|
||||
}
|
||||
|
||||
$param_tokens = array();
|
||||
|
||||
$params = $root->selectDescendantsOfType('n_DECLARATION_PARAMETER_LIST');
|
||||
foreach ($params as $param_list) {
|
||||
foreach ($param_list->getChildren() as $param) {
|
||||
$name_token = $param->getChildByIndex(1);
|
||||
if ($name_token->getTypeName() === 'n_VARIABLE_REFERENCE') {
|
||||
$name_token = $name_token->getChildOfType(0, 'n_VARIABLE');
|
||||
}
|
||||
$param_tokens[$name_token->getID()] = true;
|
||||
$name_string = $name_token->getConcreteString();
|
||||
|
||||
$names[] = array(
|
||||
'parameter',
|
||||
$name_string,
|
||||
$name_token,
|
||||
ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores(
|
||||
ArcanistXHPASTLintNamingHook::stripPHPVariable($name_string))
|
||||
? null
|
||||
: pht(
|
||||
'Follow naming conventions: parameters should be named using '.
|
||||
'lowercase_with_underscores.'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$constants = $root->selectDescendantsOfType(
|
||||
'n_CLASS_CONSTANT_DECLARATION_LIST');
|
||||
foreach ($constants as $constant_list) {
|
||||
foreach ($constant_list->getChildren() as $constant) {
|
||||
$name_token = $constant->getChildByIndex(0);
|
||||
$name_string = $name_token->getConcreteString();
|
||||
$names[] = array(
|
||||
'constant',
|
||||
$name_string,
|
||||
$name_token,
|
||||
ArcanistXHPASTLintNamingHook::isUppercaseWithUnderscores($name_string)
|
||||
? null
|
||||
: pht(
|
||||
'Follow naming conventions: class constants should be named '.
|
||||
'using UPPERCASE_WITH_UNDERSCORES.'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$member_tokens = array();
|
||||
|
||||
$props = $root->selectDescendantsOfType('n_CLASS_MEMBER_DECLARATION_LIST');
|
||||
foreach ($props as $prop_list) {
|
||||
foreach ($prop_list->getChildren() as $token_id => $prop) {
|
||||
if ($prop->getTypeName() === 'n_CLASS_MEMBER_MODIFIER_LIST') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name_token = $prop->getChildByIndex(0);
|
||||
$member_tokens[$name_token->getID()] = true;
|
||||
|
||||
$name_string = $name_token->getConcreteString();
|
||||
$names[] = array(
|
||||
'member',
|
||||
$name_string,
|
||||
$name_token,
|
||||
ArcanistXHPASTLintNamingHook::isLowerCamelCase(
|
||||
ArcanistXHPASTLintNamingHook::stripPHPVariable($name_string))
|
||||
? null
|
||||
: pht(
|
||||
'Follow naming conventions: class properties should be named '.
|
||||
'using lowerCamelCase.'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$superglobal_map = array_fill_keys(
|
||||
$this->getSuperGlobalNames(),
|
||||
true);
|
||||
|
||||
|
||||
$defs = $root->selectDescendantsOfTypes(array(
|
||||
'n_FUNCTION_DECLARATION',
|
||||
'n_METHOD_DECLARATION',
|
||||
));
|
||||
|
||||
foreach ($defs as $def) {
|
||||
$globals = $def->selectDescendantsOfType('n_GLOBAL_DECLARATION_LIST');
|
||||
$globals = $globals->selectDescendantsOfType('n_VARIABLE');
|
||||
|
||||
$globals_map = array();
|
||||
foreach ($globals as $global) {
|
||||
$global_string = $global->getConcreteString();
|
||||
$globals_map[$global_string] = true;
|
||||
$names[] = array(
|
||||
'user',
|
||||
$global_string,
|
||||
$global,
|
||||
|
||||
// No advice for globals, but hooks have an option to provide some.
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
// Exclude access of static properties, since lint will be raised at
|
||||
// their declaration if they're invalid and they may not conform to
|
||||
// variable rules. This is slightly overbroad (includes the entire
|
||||
// RHS of a "Class::..." token) to cover cases like "Class:$x[0]". These
|
||||
// variables are simply made exempt from naming conventions.
|
||||
$exclude_tokens = array();
|
||||
$statics = $def->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
|
||||
foreach ($statics as $static) {
|
||||
$rhs = $static->getChildByIndex(1);
|
||||
if ($rhs->getTypeName() == 'n_VARIABLE') {
|
||||
$exclude_tokens[$rhs->getID()] = true;
|
||||
} else {
|
||||
$rhs_vars = $rhs->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($rhs_vars as $var) {
|
||||
$exclude_tokens[$var->getID()] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$vars = $def->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($vars as $token_id => $var) {
|
||||
if (isset($member_tokens[$token_id])) {
|
||||
continue;
|
||||
}
|
||||
if (isset($param_tokens[$token_id])) {
|
||||
continue;
|
||||
}
|
||||
if (isset($exclude_tokens[$token_id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$var_string = $var->getConcreteString();
|
||||
|
||||
// Awkward artifact of "$o->{$x}".
|
||||
$var_string = trim($var_string, '{}');
|
||||
|
||||
if (isset($superglobal_map[$var_string])) {
|
||||
continue;
|
||||
}
|
||||
if (isset($globals_map[$var_string])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$names[] = array(
|
||||
'variable',
|
||||
$var_string,
|
||||
$var,
|
||||
ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores(
|
||||
ArcanistXHPASTLintNamingHook::stripPHPVariable($var_string))
|
||||
? null
|
||||
: pht(
|
||||
'Follow naming conventions: variables should be named using '.
|
||||
'lowercase_with_underscores.'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If a naming hook is configured, give it a chance to override the
|
||||
// default results for all the symbol names.
|
||||
$hook_class = $this->naminghook;
|
||||
if ($hook_class) {
|
||||
$hook_obj = newv($hook_class, array());
|
||||
foreach ($names as $k => $name_attrs) {
|
||||
list($type, $name, $token, $default) = $name_attrs;
|
||||
$result = $hook_obj->lintSymbolName($type, $name, $default);
|
||||
$names[$k][3] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Raise anything we're left with.
|
||||
foreach ($names as $k => $name_attrs) {
|
||||
list($type, $name, $token, $result) = $name_attrs;
|
||||
if ($result) {
|
||||
$this->raiseLintAtNode(
|
||||
$token,
|
||||
$result);
|
||||
}
|
||||
}
|
||||
|
||||
// Lint constant declarations.
|
||||
$defines = $this
|
||||
->getFunctionCalls($root, array('define'))
|
||||
->add($root->selectDescendantsOfTypes(array(
|
||||
'n_CLASS_CONSTANT_DECLARATION',
|
||||
'n_CONSTANT_DECLARATION',
|
||||
)));
|
||||
|
||||
foreach ($defines as $define) {
|
||||
switch ($define->getTypeName()) {
|
||||
case 'n_CLASS_CONSTANT_DECLARATION':
|
||||
case 'n_CONSTANT_DECLARATION':
|
||||
$constant = $define->getChildByIndex(0);
|
||||
|
||||
if ($constant->getTypeName() !== 'n_STRING') {
|
||||
$constant = null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'n_FUNCTION_CALL':
|
||||
$constant = $define
|
||||
->getChildOfType(1, 'n_CALL_PARAMETER_LIST')
|
||||
->getChildByIndex(0);
|
||||
|
||||
if ($constant->getTypeName() !== 'n_STRING_SCALAR') {
|
||||
$constant = null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$constant = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$constant) {
|
||||
continue;
|
||||
}
|
||||
$constant_name = $constant->getConcreteString();
|
||||
|
||||
if ($constant_name !== strtoupper($constant_name)) {
|
||||
$this->raiseLintAtNode(
|
||||
$constant,
|
||||
pht('Constants should be uppercase.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistNoParentScopeXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 64;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('No Parent Scope');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
|
||||
if ($class->getChildByIndex(2)->getTypeName() == 'n_EXTENDS_LIST') {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$static_accesses = $method
|
||||
->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
|
||||
|
||||
foreach ($static_accesses as $static_access) {
|
||||
$called_class = $static_access->getChildByIndex(0);
|
||||
|
||||
if ($called_class->getTypeName() != 'n_CLASS_NAME') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($called_class->getConcreteString() == 'parent') {
|
||||
$this->raiseLintAtNode(
|
||||
$static_access,
|
||||
pht(
|
||||
'Cannot access %s when current class scope has no parent.',
|
||||
'parent::'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistPHPCloseTagXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 8;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Use of Close Tag "%s"', '?>');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
foreach ($root->selectTokensOfType('T_CLOSE_TAG') as $token) {
|
||||
$this->raiseLintAtToken(
|
||||
$token,
|
||||
pht('Do not use the PHP closing tag, "%s".', '?>'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,435 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistPHPCompatibilityXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 45;
|
||||
|
||||
private $version;
|
||||
private $windowsVersion;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('PHP Compatibility');
|
||||
}
|
||||
|
||||
public function getLinterConfigurationOptions() {
|
||||
return parent::getLinterConfigurationOptions() + 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;
|
||||
|
||||
default:
|
||||
return parent::setLinterConfigurationValue($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
static $compat_info;
|
||||
|
||||
if (!$this->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(1);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistPHPEchoTagXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 7;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Use of Echo Tag "%s"', '<?=');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$tokens = $root->getTokens();
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
if ($token->getTypeName() === 'T_OPEN_TAG_WITH_ECHO') {
|
||||
$this->raiseLintAtToken(
|
||||
$token,
|
||||
pht('Avoid the PHP echo short form, "%s".', '<?='));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistPHPOpenTagXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 15;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Expected Open Tag');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$tokens = $root->getTokens();
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
if ($token->getTypeName() === 'T_OPEN_TAG') {
|
||||
break;
|
||||
} else if ($token->getTypeName() === 'T_OPEN_TAG_WITH_ECHO') {
|
||||
break;
|
||||
} else {
|
||||
if (!preg_match('/^#!/', $token->getValue())) {
|
||||
$this->raiseLintAtToken(
|
||||
$token,
|
||||
pht(
|
||||
'PHP files should start with "%s", which may be preceded by '.
|
||||
'a "%s" line for scripts.',
|
||||
'<?php',
|
||||
'#!'));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistPHPShortTagXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 6;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Use of Short Tag "%s"', '<?');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$tokens = $root->getTokens();
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
if ($token->getTypeName() === 'T_OPEN_TAG') {
|
||||
if (trim($token->getValue()) === '<?') {
|
||||
$this->raiseLintAtToken(
|
||||
$token,
|
||||
pht(
|
||||
'Use the full form of the PHP open tag, "%s".',
|
||||
'<?php'),
|
||||
"<?php\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistParenthesesSpacingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 25;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Spaces Inside Parentheses');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$all_paren_groups = $root->selectDescendantsOfTypes(array(
|
||||
'n_CALL_PARAMETER_LIST',
|
||||
'n_CONTROL_CONDITION',
|
||||
'n_FOR_EXPRESSION',
|
||||
'n_FOREACH_EXPRESSION',
|
||||
'n_DECLARATION_PARAMETER_LIST',
|
||||
));
|
||||
|
||||
foreach ($all_paren_groups as $group) {
|
||||
$tokens = $group->getTokens();
|
||||
|
||||
$token_o = array_shift($tokens);
|
||||
$token_c = array_pop($tokens);
|
||||
if ($token_o->getTypeName() !== '(') {
|
||||
throw new Exception(pht('Expected open parentheses.'));
|
||||
}
|
||||
if ($token_c->getTypeName() !== ')') {
|
||||
throw new Exception(pht('Expected close parentheses.'));
|
||||
}
|
||||
|
||||
$nonsem_o = $token_o->getNonsemanticTokensAfter();
|
||||
$nonsem_c = $token_c->getNonsemanticTokensBefore();
|
||||
|
||||
if (!$nonsem_o) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$raise = array();
|
||||
|
||||
$string_o = implode('', mpull($nonsem_o, 'getValue'));
|
||||
if (preg_match('/^[ ]+$/', $string_o)) {
|
||||
$raise[] = array($nonsem_o, $string_o);
|
||||
}
|
||||
|
||||
if ($nonsem_o !== $nonsem_c) {
|
||||
$string_c = implode('', mpull($nonsem_c, 'getValue'));
|
||||
if (preg_match('/^[ ]+$/', $string_c)) {
|
||||
$raise[] = array($nonsem_c, $string_c);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($raise as $warning) {
|
||||
list($tokens, $string) = $warning;
|
||||
$this->raiseLintAtOffset(
|
||||
reset($tokens)->getOffset(),
|
||||
pht('Parentheses should hug their contents.'),
|
||||
$string,
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistPlusOperatorOnStringsXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 21;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Not String Concatenation');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$binops = $root->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
|
||||
foreach ($binops as $binop) {
|
||||
$op = $binop->getChildByIndex(1);
|
||||
if ($op->getConcreteString() !== '+') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$left = $binop->getChildByIndex(0);
|
||||
$right = $binop->getChildByIndex(2);
|
||||
|
||||
if ($left->getTypeName() === 'n_STRING_SCALAR' ||
|
||||
$right->getTypeName() === 'n_STRING_SCALAR') {
|
||||
$this->raiseLintAtNode(
|
||||
$binop,
|
||||
pht(
|
||||
"In PHP, '%s' is the string concatenation operator, not '%s'. ".
|
||||
"This expression uses '+' with a string literal as an operand.",
|
||||
'.',
|
||||
'+'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @{function:preg_quote} takes two arguments, but the second one is optional
|
||||
* because it is possible to use `()`, `[]` or `{}` as regular expression
|
||||
* delimiters. If you don't pass a second argument, you're probably going to
|
||||
* get something wrong.
|
||||
*/
|
||||
final class ArcanistPregQuoteMisuseXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 14;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Misuse of %s', 'preg_quote()');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$function_calls = $this->getFunctionCalls($root, array('preg_quote'));
|
||||
|
||||
foreach ($function_calls as $call) {
|
||||
$parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
|
||||
if (count($parameter_list->getChildren()) !== 2) {
|
||||
$this->raiseLintAtNode(
|
||||
$call,
|
||||
pht(
|
||||
'If you use pattern delimiters that require escaping '.
|
||||
'(such as `%s`, but not `%s`) then you should pass two '.
|
||||
'arguments to %s, so that %s knows which delimiter to escape.',
|
||||
'//',
|
||||
'()',
|
||||
'preg_quote()',
|
||||
'preg_quote()'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistReusedAsIteratorXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 32;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Variable Reused As Iterator');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$defs = $root->selectDescendantsOfTypes(array(
|
||||
'n_FUNCTION_DECLARATION',
|
||||
'n_METHOD_DECLARATION',
|
||||
));
|
||||
|
||||
foreach ($defs as $def) {
|
||||
|
||||
// We keep track of the first offset where scope becomes unknowable, and
|
||||
// silence any warnings after that. Default it to INT_MAX so we can min()
|
||||
// it later to keep track of the first problem we encounter.
|
||||
$scope_destroyed_at = PHP_INT_MAX;
|
||||
|
||||
$declarations = array(
|
||||
'$this' => 0,
|
||||
) + array_fill_keys($this->getSuperGlobalNames(), 0);
|
||||
$declaration_tokens = array();
|
||||
$exclude_tokens = array();
|
||||
$vars = array();
|
||||
|
||||
// First up, find all the different kinds of declarations, as explained
|
||||
// above. Put the tokens into the $vars array.
|
||||
|
||||
$param_list = $def->getChildOfType(3, 'n_DECLARATION_PARAMETER_LIST');
|
||||
$param_vars = $param_list->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($param_vars as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
|
||||
// This is PHP5.3 closure syntax: function () use ($x) {};
|
||||
$lexical_vars = $def
|
||||
->getChildByIndex(4)
|
||||
->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($lexical_vars as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
|
||||
$body = $def->getChildByIndex(5);
|
||||
if ($body->getTypeName() === 'n_EMPTY') {
|
||||
// Abstract method declaration.
|
||||
continue;
|
||||
}
|
||||
|
||||
$static_vars = $body
|
||||
->selectDescendantsOfType('n_STATIC_DECLARATION')
|
||||
->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($static_vars as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
|
||||
|
||||
$global_vars = $body
|
||||
->selectDescendantsOfType('n_GLOBAL_DECLARATION_LIST');
|
||||
foreach ($global_vars as $var_list) {
|
||||
foreach ($var_list->getChildren() as $var) {
|
||||
if ($var->getTypeName() === 'n_VARIABLE') {
|
||||
$vars[] = $var;
|
||||
} else {
|
||||
// Dynamic global variable, i.e. "global $$x;".
|
||||
$scope_destroyed_at = min($scope_destroyed_at, $var->getOffset());
|
||||
// An error is raised elsewhere, no need to raise here.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Include "catch (Exception $ex)", but not variables in the body of the
|
||||
// catch block.
|
||||
$catches = $body->selectDescendantsOfType('n_CATCH');
|
||||
foreach ($catches as $catch) {
|
||||
$vars[] = $catch->getChildOfType(1, 'n_VARIABLE');
|
||||
}
|
||||
|
||||
$binary = $body->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
foreach ($binary as $expr) {
|
||||
if ($expr->getChildByIndex(1)->getConcreteString() !== '=') {
|
||||
continue;
|
||||
}
|
||||
$lval = $expr->getChildByIndex(0);
|
||||
if ($lval->getTypeName() === 'n_VARIABLE') {
|
||||
$vars[] = $lval;
|
||||
} else if ($lval->getTypeName() === 'n_LIST') {
|
||||
// Recursivey grab everything out of list(), since the grammar
|
||||
// permits list() to be nested. Also note that list() is ONLY valid
|
||||
// as an lval assignments, so we could safely lift this out of the
|
||||
// n_BINARY_EXPRESSION branch.
|
||||
$assign_vars = $lval->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($assign_vars as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
}
|
||||
|
||||
if ($lval->getTypeName() === 'n_VARIABLE_VARIABLE') {
|
||||
$scope_destroyed_at = min($scope_destroyed_at, $lval->getOffset());
|
||||
// No need to raise here since we raise an error elsewhere.
|
||||
}
|
||||
}
|
||||
|
||||
$calls = $body->selectDescendantsOfType('n_FUNCTION_CALL');
|
||||
foreach ($calls as $call) {
|
||||
$name = strtolower($call->getChildByIndex(0)->getConcreteString());
|
||||
|
||||
if ($name === 'empty' || $name === 'isset') {
|
||||
$params = $call
|
||||
->getChildOfType(1, 'n_CALL_PARAMETER_LIST')
|
||||
->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($params as $var) {
|
||||
$exclude_tokens[$var->getID()] = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($name !== 'extract') {
|
||||
continue;
|
||||
}
|
||||
$scope_destroyed_at = min($scope_destroyed_at, $call->getOffset());
|
||||
}
|
||||
|
||||
// Now we have every declaration except foreach(), handled below. Build
|
||||
// two maps, one which just keeps track of which tokens are part of
|
||||
// declarations ($declaration_tokens) and one which has the first offset
|
||||
// where a variable is declared ($declarations).
|
||||
|
||||
foreach ($vars as $var) {
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$declarations[$concrete] = min(
|
||||
idx($declarations, $concrete, PHP_INT_MAX),
|
||||
$var->getOffset());
|
||||
$declaration_tokens[$var->getID()] = true;
|
||||
}
|
||||
|
||||
// Excluded tokens are ones we don't "count" as being used, described
|
||||
// above. Put them into $exclude_tokens.
|
||||
|
||||
$class_statics = $body
|
||||
->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
|
||||
$class_static_vars = $class_statics
|
||||
->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($class_static_vars as $var) {
|
||||
$exclude_tokens[$var->getID()] = true;
|
||||
}
|
||||
|
||||
|
||||
// Find all the variables in scope, and figure out where they are used.
|
||||
// We want to find foreach() iterators which are both declared before and
|
||||
// used after the foreach() loop.
|
||||
|
||||
$uses = array();
|
||||
|
||||
$all_vars = $body->selectDescendantsOfType('n_VARIABLE');
|
||||
$all = array();
|
||||
|
||||
// NOTE: $all_vars is not a real array so we can't unset() it.
|
||||
foreach ($all_vars as $var) {
|
||||
|
||||
// Be strict since it's easier; we don't let you reuse an iterator you
|
||||
// declared before a loop after the loop, even if you're just assigning
|
||||
// to it.
|
||||
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$uses[$concrete][$var->getID()] = $var->getOffset();
|
||||
|
||||
if (isset($declaration_tokens[$var->getID()])) {
|
||||
// We know this is part of a declaration, so it's fine.
|
||||
continue;
|
||||
}
|
||||
if (isset($exclude_tokens[$var->getID()])) {
|
||||
// We know this is part of isset() or similar, so it's fine.
|
||||
continue;
|
||||
}
|
||||
|
||||
$all[$var->getOffset()] = $concrete;
|
||||
}
|
||||
|
||||
|
||||
// Do foreach() last, we want to handle implicit redeclaration of a
|
||||
// variable already in scope since this probably means we're ovewriting a
|
||||
// local.
|
||||
|
||||
// NOTE: Processing foreach expressions in order allows programs which
|
||||
// reuse iterator variables in other foreach() loops -- this is fine. We
|
||||
// have a separate warning to prevent nested loops from reusing the same
|
||||
// iterators.
|
||||
|
||||
$foreaches = $body->selectDescendantsOfType('n_FOREACH');
|
||||
$all_foreach_vars = array();
|
||||
foreach ($foreaches as $foreach) {
|
||||
$foreach_expr = $foreach->getChildOfType(0, 'n_FOREACH_EXPRESSION');
|
||||
|
||||
$foreach_vars = array();
|
||||
|
||||
// Determine the end of the foreach() loop.
|
||||
$foreach_tokens = $foreach->getTokens();
|
||||
$last_token = end($foreach_tokens);
|
||||
$foreach_end = $last_token->getOffset();
|
||||
|
||||
$key_var = $foreach_expr->getChildByIndex(1);
|
||||
if ($key_var->getTypeName() === 'n_VARIABLE') {
|
||||
$foreach_vars[] = $key_var;
|
||||
}
|
||||
|
||||
$value_var = $foreach_expr->getChildByIndex(2);
|
||||
if ($value_var->getTypeName() === 'n_VARIABLE') {
|
||||
$foreach_vars[] = $value_var;
|
||||
} else {
|
||||
// The root-level token may be a reference, as in:
|
||||
// foreach ($a as $b => &$c) { ... }
|
||||
// Reach into the n_VARIABLE_REFERENCE node to grab the n_VARIABLE
|
||||
// node.
|
||||
$var = $value_var->getChildByIndex(0);
|
||||
if ($var->getTypeName() === 'n_VARIABLE_VARIABLE') {
|
||||
$var = $var->getChildByIndex(0);
|
||||
}
|
||||
$foreach_vars[] = $var;
|
||||
}
|
||||
|
||||
// Remove all uses of the iterators inside of the foreach() loop from
|
||||
// the $uses map.
|
||||
|
||||
foreach ($foreach_vars as $var) {
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$offset = $var->getOffset();
|
||||
|
||||
foreach ($uses[$concrete] as $id => $use_offset) {
|
||||
if (($use_offset >= $offset) && ($use_offset < $foreach_end)) {
|
||||
unset($uses[$concrete][$id]);
|
||||
}
|
||||
}
|
||||
|
||||
$all_foreach_vars[] = $var;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($all_foreach_vars as $var) {
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$offset = $var->getOffset();
|
||||
|
||||
// If a variable was declared before a foreach() and is used after
|
||||
// it, raise a message.
|
||||
|
||||
if (isset($declarations[$concrete])) {
|
||||
if ($declarations[$concrete] < $offset) {
|
||||
if (!empty($uses[$concrete]) &&
|
||||
max($uses[$concrete]) > $offset) {
|
||||
$message = $this->raiseLintAtNode(
|
||||
$var,
|
||||
pht(
|
||||
'This iterator variable is a previously declared local '.
|
||||
'variable. To avoid overwriting locals, do not reuse them '.
|
||||
'as iterator variables.'));
|
||||
$message->setOtherLocations(array(
|
||||
$this->getOtherLocation($declarations[$concrete]),
|
||||
$this->getOtherLocation(max($uses[$concrete])),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a declaration, exclude it from the "declare variables prior
|
||||
// to use" check below.
|
||||
unset($all[$var->getOffset()]);
|
||||
|
||||
$vars[] = $var;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Find cases where a `foreach` loop is being iterated using a variable
|
||||
* reference and the same variable is used outside of the loop without calling
|
||||
* `unset()` or reassigning the variable to another variable reference.
|
||||
*
|
||||
* COUNTEREXAMPLE
|
||||
* foreach ($ar as &$a) {
|
||||
* // ...
|
||||
* }
|
||||
* $a = 1; // <-- Raises an error for using $a
|
||||
*/
|
||||
final class ArcanistReusedIteratorReferenceXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 39;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Reuse of Iterator References');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$defs = $root->selectDescendantsOfTypes(array(
|
||||
'n_FUNCTION_DECLARATION',
|
||||
'n_METHOD_DECLARATION',
|
||||
));
|
||||
|
||||
foreach ($defs as $def) {
|
||||
$body = $def->getChildByIndex(5);
|
||||
if ($body->getTypeName() === 'n_EMPTY') {
|
||||
// Abstract method declaration.
|
||||
continue;
|
||||
}
|
||||
|
||||
$exclude = array();
|
||||
|
||||
// Exclude uses of variables, unsets, and foreach loops
|
||||
// within closures - they are checked on their own
|
||||
$func_defs = $body->selectDescendantsOfType('n_FUNCTION_DECLARATION');
|
||||
foreach ($func_defs as $func_def) {
|
||||
$vars = $func_def->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($vars as $var) {
|
||||
$exclude[$var->getID()] = true;
|
||||
}
|
||||
|
||||
$unset_lists = $func_def->selectDescendantsOfType('n_UNSET_LIST');
|
||||
foreach ($unset_lists as $unset_list) {
|
||||
$exclude[$unset_list->getID()] = true;
|
||||
}
|
||||
|
||||
$foreaches = $func_def->selectDescendantsOfType('n_FOREACH');
|
||||
foreach ($foreaches as $foreach) {
|
||||
$exclude[$foreach->getID()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all variables that are unset within the scope
|
||||
$unset_vars = array();
|
||||
$unset_lists = $body->selectDescendantsOfType('n_UNSET_LIST');
|
||||
foreach ($unset_lists as $unset_list) {
|
||||
if (isset($exclude[$unset_list->getID()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unset_list_vars = $unset_list->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($unset_list_vars as $var) {
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$unset_vars[$concrete][] = $var->getOffset();
|
||||
$exclude[$var->getID()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all reference variables in foreach expressions
|
||||
$reference_vars = array();
|
||||
$foreaches = $body->selectDescendantsOfType('n_FOREACH');
|
||||
foreach ($foreaches as $foreach) {
|
||||
if (isset($exclude[$foreach->getID()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$foreach_expr = $foreach->getChildOfType(0, 'n_FOREACH_EXPRESSION');
|
||||
$var = $foreach_expr->getChildByIndex(2);
|
||||
if ($var->getTypeName() !== 'n_VARIABLE_REFERENCE') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reference = $var->getChildByIndex(0);
|
||||
if ($reference->getTypeName() !== 'n_VARIABLE') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reference_name = $this->getConcreteVariableString($reference);
|
||||
$reference_vars[$reference_name][] = $reference->getOffset();
|
||||
$exclude[$reference->getID()] = true;
|
||||
|
||||
// Exclude uses of the reference variable within the foreach loop
|
||||
$foreach_vars = $foreach->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($foreach_vars as $var) {
|
||||
$name = $this->getConcreteVariableString($var);
|
||||
if ($name === $reference_name) {
|
||||
$exclude[$var->getID()] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow usage if the reference variable is assigned to another
|
||||
// reference variable
|
||||
$binary = $body->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
foreach ($binary as $expr) {
|
||||
if ($expr->getChildByIndex(1)->getConcreteString() !== '=') {
|
||||
continue;
|
||||
}
|
||||
$lval = $expr->getChildByIndex(0);
|
||||
if ($lval->getTypeName() !== 'n_VARIABLE') {
|
||||
continue;
|
||||
}
|
||||
$rval = $expr->getChildByIndex(2);
|
||||
if ($rval->getTypeName() !== 'n_VARIABLE_REFERENCE') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Counts as unsetting a variable
|
||||
$concrete = $this->getConcreteVariableString($lval);
|
||||
$unset_vars[$concrete][] = $lval->getOffset();
|
||||
$exclude[$lval->getID()] = true;
|
||||
}
|
||||
|
||||
$all_vars = array();
|
||||
$all = $body->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($all as $var) {
|
||||
if (isset($exclude[$var->getID()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $this->getConcreteVariableString($var);
|
||||
|
||||
if (!isset($reference_vars[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the closest reference offset to this variable
|
||||
$reference_offset = null;
|
||||
foreach ($reference_vars[$name] as $offset) {
|
||||
if ($offset < $var->getOffset()) {
|
||||
$reference_offset = $offset;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$reference_offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if an unset exists between reference and usage of this
|
||||
// variable
|
||||
$warn = true;
|
||||
if (isset($unset_vars[$name])) {
|
||||
foreach ($unset_vars[$name] as $unset_offset) {
|
||||
if ($unset_offset > $reference_offset &&
|
||||
$unset_offset < $var->getOffset()) {
|
||||
$warn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($warn) {
|
||||
$this->raiseLintAtNode(
|
||||
$var,
|
||||
pht(
|
||||
'This variable was used already as a by-reference iterator '.
|
||||
'variable. Such variables survive outside the %s loop, '.
|
||||
'do not reuse.',
|
||||
'foreach'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Find cases where loops get nested inside each other but use the same
|
||||
* iterator variable. For example:
|
||||
*
|
||||
* COUNTEREXAMPLE
|
||||
* foreach ($list as $thing) {
|
||||
* foreach ($stuff as $thing) { // <-- Raises an error for reuse of $thing
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
final class ArcanistReusedIteratorXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 23;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Reuse of Iterator Variable');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$used_vars = array();
|
||||
|
||||
$for_loops = $root->selectDescendantsOfType('n_FOR');
|
||||
foreach ($for_loops as $for_loop) {
|
||||
$var_map = array();
|
||||
|
||||
// Find all the variables that are assigned to in the for() expression.
|
||||
$for_expr = $for_loop->getChildOfType(0, 'n_FOR_EXPRESSION');
|
||||
$bin_exprs = $for_expr->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
foreach ($bin_exprs as $bin_expr) {
|
||||
if ($bin_expr->getChildByIndex(1)->getConcreteString() === '=') {
|
||||
$var = $bin_expr->getChildByIndex(0);
|
||||
$var_map[$var->getConcreteString()] = $var;
|
||||
}
|
||||
}
|
||||
|
||||
$used_vars[$for_loop->getID()] = $var_map;
|
||||
}
|
||||
|
||||
$foreach_loops = $root->selectDescendantsOfType('n_FOREACH');
|
||||
foreach ($foreach_loops as $foreach_loop) {
|
||||
$var_map = array();
|
||||
|
||||
$foreach_expr = $foreach_loop->getChildOfType(0, 'n_FOREACH_EXPRESSION');
|
||||
|
||||
// We might use one or two vars, i.e. "foreach ($x as $y => $z)" or
|
||||
// "foreach ($x as $y)".
|
||||
$possible_used_vars = array(
|
||||
$foreach_expr->getChildByIndex(1),
|
||||
$foreach_expr->getChildByIndex(2),
|
||||
);
|
||||
foreach ($possible_used_vars as $var) {
|
||||
if ($var->getTypeName() === 'n_EMPTY') {
|
||||
continue;
|
||||
}
|
||||
$name = $var->getConcreteString();
|
||||
$name = trim($name, '&'); // Get rid of ref silliness.
|
||||
$var_map[$name] = $var;
|
||||
}
|
||||
|
||||
$used_vars[$foreach_loop->getID()] = $var_map;
|
||||
}
|
||||
|
||||
$all_loops = $for_loops->add($foreach_loops);
|
||||
foreach ($all_loops as $loop) {
|
||||
$child_loops = $loop->selectDescendantsOfTypes(array(
|
||||
'n_FOR',
|
||||
'n_FOREACH',
|
||||
));
|
||||
|
||||
$outer_vars = $used_vars[$loop->getID()];
|
||||
foreach ($child_loops as $inner_loop) {
|
||||
$inner_vars = $used_vars[$inner_loop->getID()];
|
||||
$shared = array_intersect_key($outer_vars, $inner_vars);
|
||||
if ($shared) {
|
||||
$shared_desc = implode(', ', array_keys($shared));
|
||||
$message = $this->raiseLintAtNode(
|
||||
$inner_loop->getChildByIndex(0),
|
||||
pht(
|
||||
'This loop reuses iterator variables (%s) from an '.
|
||||
'outer loop. You might be clobbering the outer iterator. '.
|
||||
'Change the inner loop to use a different iterator name.',
|
||||
$shared_desc));
|
||||
|
||||
$locations = array();
|
||||
foreach ($shared as $var) {
|
||||
$locations[] = $this->getOtherLocation($var->getOffset());
|
||||
}
|
||||
$message->setOtherLocations($locations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistSelfMemberReferenceXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 57;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Self Member Reference');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$class_declarations = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
|
||||
foreach ($class_declarations as $class_declaration) {
|
||||
$class_name = $class_declaration
|
||||
->getChildOfType(1, 'n_CLASS_NAME')
|
||||
->getConcreteString();
|
||||
|
||||
$class_static_accesses = $class_declaration
|
||||
->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
|
||||
|
||||
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($class_name) == strtolower($class_ref_name)) {
|
||||
$this->raiseLintAtNode(
|
||||
$class_ref,
|
||||
pht(
|
||||
'Use `%s` for local static member references.',
|
||||
'self::'),
|
||||
'self');
|
||||
}
|
||||
|
||||
static $self_refs = array(
|
||||
'parent',
|
||||
'self',
|
||||
'static',
|
||||
);
|
||||
|
||||
if (!in_array(strtolower($class_ref_name), $self_refs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($class_ref_name != strtolower($class_ref_name)) {
|
||||
$this->raiseLintAtNode(
|
||||
$class_ref,
|
||||
pht('PHP keywords should be lowercase.'),
|
||||
strtolower($class_ref_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$double_colons = $root->selectTokensOfType('T_PAAMAYIM_NEKUDOTAYIM');
|
||||
|
||||
foreach ($double_colons as $double_colon) {
|
||||
$tokens = $double_colon->getNonsemanticTokensBefore() +
|
||||
$double_colon->getNonsemanticTokensAfter();
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
if ($token->isAnyWhitespace()) {
|
||||
if (strpos($token->getValue(), "\n") !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->raiseLintAtToken(
|
||||
$token,
|
||||
pht('Unnecessary whitespace around double colon operator.'),
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistSemicolonSpacingXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 43;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Semicolon Spacing');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$tokens = $root->selectTokensOfType(';');
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
$prev = $token->getPrevToken();
|
||||
|
||||
if ($prev->isAnyWhitespace()) {
|
||||
$this->raiseLintAtToken(
|
||||
$prev,
|
||||
pht('Space found before semicolon.'),
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistSlownessXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 36;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Slow Construct');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$this->lintStrstrUsedForCheck($root);
|
||||
$this->lintStrposUsedForStart($root);
|
||||
}
|
||||
|
||||
private function lintStrstrUsedForCheck(XHPASTNode $root) {
|
||||
$expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
|
||||
foreach ($expressions as $expression) {
|
||||
$operator = $expression->getChildOfType(1, 'n_OPERATOR');
|
||||
$operator = $operator->getConcreteString();
|
||||
|
||||
if ($operator !== '===' && $operator !== '!==') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$false = $expression->getChildByIndex(0);
|
||||
if ($false->getTypeName() === 'n_SYMBOL_NAME' &&
|
||||
$false->getConcreteString() === 'false') {
|
||||
$strstr = $expression->getChildByIndex(2);
|
||||
} else {
|
||||
$strstr = $false;
|
||||
$false = $expression->getChildByIndex(2);
|
||||
if ($false->getTypeName() !== 'n_SYMBOL_NAME' ||
|
||||
$false->getConcreteString() !== 'false') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($strstr->getTypeName() !== 'n_FUNCTION_CALL') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = strtolower($strstr->getChildByIndex(0)->getConcreteString());
|
||||
if ($name === 'strstr' || $name === 'strchr') {
|
||||
$this->raiseLintAtNode(
|
||||
$strstr,
|
||||
pht(
|
||||
'Use %s for checking if the string contains something.',
|
||||
'strpos()'));
|
||||
} else if ($name === 'stristr') {
|
||||
$this->raiseLintAtNode(
|
||||
$strstr,
|
||||
pht(
|
||||
'Use %s for checking if the string contains something.',
|
||||
'stripos()'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function lintStrposUsedForStart(XHPASTNode $root) {
|
||||
$expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
|
||||
foreach ($expressions as $expression) {
|
||||
$operator = $expression->getChildOfType(1, 'n_OPERATOR');
|
||||
$operator = $operator->getConcreteString();
|
||||
|
||||
if ($operator !== '===' && $operator !== '!==') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$zero = $expression->getChildByIndex(0);
|
||||
if ($zero->getTypeName() === 'n_NUMERIC_SCALAR' &&
|
||||
$zero->getConcreteString() === '0') {
|
||||
$strpos = $expression->getChildByIndex(2);
|
||||
} else {
|
||||
$strpos = $zero;
|
||||
$zero = $expression->getChildByIndex(2);
|
||||
if ($zero->getTypeName() !== 'n_NUMERIC_SCALAR' ||
|
||||
$zero->getConcreteString() !== '0') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($strpos->getTypeName() !== 'n_FUNCTION_CALL') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = strtolower($strpos->getChildByIndex(0)->getConcreteString());
|
||||
if ($name === 'strpos') {
|
||||
$this->raiseLintAtNode(
|
||||
$strpos,
|
||||
pht(
|
||||
'Use %s for checking if the string starts with something.',
|
||||
'strncmp()'));
|
||||
} else if ($name === 'stripos') {
|
||||
$this->raiseLintAtNode(
|
||||
$strpos,
|
||||
pht(
|
||||
'Use %s for checking if the string starts with something.',
|
||||
'strncasecmp()'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistStaticThisXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 13;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Use of %s in Static Context', '$this');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$attributes = $method
|
||||
->getChildByIndex(0, 'n_METHOD_MODIFIER_LIST')
|
||||
->selectDescendantsOfType('n_STRING');
|
||||
|
||||
$method_is_static = false;
|
||||
$method_is_abstract = false;
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if (strtolower($attribute->getConcreteString()) === 'static') {
|
||||
$method_is_static = true;
|
||||
}
|
||||
if (strtolower($attribute->getConcreteString()) === 'abstract') {
|
||||
$method_is_abstract = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($method_is_abstract) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$method_is_static) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$body = $method->getChildOfType(5, 'n_STATEMENT_LIST');
|
||||
$variables = $body->selectDescendantsOfType('n_VARIABLE');
|
||||
|
||||
foreach ($variables as $variable) {
|
||||
if ($method_is_static &&
|
||||
strtolower($variable->getConcreteString()) === '$this') {
|
||||
$this->raiseLintAtNode(
|
||||
$variable,
|
||||
pht(
|
||||
'You can not reference `%s` inside a static method.',
|
||||
'$this'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistSyntaxErrorXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 1;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('PHP Syntax Error!');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
// This linter rule isn't used explicitly.
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistTautologicalExpressionXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 20;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Tautological Expression');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
|
||||
static $operators = array(
|
||||
'-' => true,
|
||||
'/' => true,
|
||||
'-=' => true,
|
||||
'/=' => true,
|
||||
'<=' => true,
|
||||
'<' => true,
|
||||
'==' => true,
|
||||
'===' => true,
|
||||
'!=' => true,
|
||||
'!==' => true,
|
||||
'>=' => true,
|
||||
'>' => true,
|
||||
);
|
||||
|
||||
static $logical = array(
|
||||
'||' => true,
|
||||
'&&' => true,
|
||||
);
|
||||
|
||||
foreach ($expressions as $expr) {
|
||||
$operator = $expr->getChildByIndex(1)->getConcreteString();
|
||||
if (!empty($operators[$operator])) {
|
||||
$left = $expr->getChildByIndex(0)->getSemanticString();
|
||||
$right = $expr->getChildByIndex(2)->getSemanticString();
|
||||
|
||||
if ($left === $right) {
|
||||
$this->raiseLintAtNode(
|
||||
$expr,
|
||||
pht(
|
||||
'Both sides of this expression are identical, so it always '.
|
||||
'evaluates to a constant.'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($logical[$operator])) {
|
||||
$left = $expr->getChildByIndex(0)->getSemanticString();
|
||||
$right = $expr->getChildByIndex(2)->getSemanticString();
|
||||
|
||||
// NOTE: These will be null to indicate "could not evaluate".
|
||||
$left = $this->evaluateStaticBoolean($left);
|
||||
$right = $this->evaluateStaticBoolean($right);
|
||||
|
||||
if (($operator === '||' && ($left === true || $right === true)) ||
|
||||
($operator === '&&' && ($left === false || $right === false))) {
|
||||
$this->raiseLintAtNode(
|
||||
$expr,
|
||||
pht(
|
||||
'The logical value of this expression is static. '.
|
||||
'Did you forget to remove some debugging code?'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistToStringExceptionXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 67;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Throwing Exception in %s Method', '__toString');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$methods = $root->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$name = $method
|
||||
->getChildOfType(2, 'n_STRING')
|
||||
->getConcreteString();
|
||||
|
||||
if ($name != '__toString') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$statements = $method->getChildByIndex(5);
|
||||
|
||||
if ($statements->getTypeName() != 'n_STATEMENT_LIST') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$throws = $statements->selectDescendantsOfType('n_THROW');
|
||||
|
||||
foreach ($throws as $throw) {
|
||||
$this->raiseLintAtNode(
|
||||
$throw,
|
||||
pht(
|
||||
'It is not possible to throw an %s from within the %s method.',
|
||||
'Exception',
|
||||
'__toString'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistTodoCommentXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 16;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('TODO Comment');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_DISABLED;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$comments = $root->selectTokensOfTypes(array(
|
||||
'T_COMMENT',
|
||||
'T_DOC_COMMENT',
|
||||
));
|
||||
|
||||
foreach ($comments as $token) {
|
||||
$value = $token->getValue();
|
||||
if ($token->getTypeName() === 'T_DOC_COMMENT') {
|
||||
$regex = '/(TODO|@todo)/';
|
||||
} else {
|
||||
$regex = '/TODO/';
|
||||
}
|
||||
|
||||
$matches = null;
|
||||
$preg = preg_match_all(
|
||||
$regex,
|
||||
$value,
|
||||
$matches,
|
||||
PREG_OFFSET_CAPTURE);
|
||||
|
||||
foreach ($matches[0] as $match) {
|
||||
list($string, $offset) = $match;
|
||||
$this->raiseLintAtOffset(
|
||||
$token->getOffset() + $offset,
|
||||
pht('This comment has a TODO.'),
|
||||
$string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistUnableToParseXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 2;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Unable to Parse');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
// This linter rule isn't used explicitly.
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistUndeclaredVariableXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 5;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Use of Undeclared Variable');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
// These things declare variables in a function:
|
||||
// Explicit parameters
|
||||
// Assignment
|
||||
// Assignment via list()
|
||||
// Static
|
||||
// Global
|
||||
// Lexical vars
|
||||
// Builtins ($this)
|
||||
// foreach()
|
||||
// catch
|
||||
//
|
||||
// These things make lexical scope unknowable:
|
||||
// Use of extract()
|
||||
// Assignment to variable variables ($$x)
|
||||
// Global with variable variables
|
||||
//
|
||||
// These things don't count as "using" a variable:
|
||||
// isset()
|
||||
// empty()
|
||||
// Static class variables
|
||||
//
|
||||
// The general approach here is to find each function/method declaration,
|
||||
// then:
|
||||
//
|
||||
// 1. Identify all the variable declarations, and where they first occur
|
||||
// in the function/method declaration.
|
||||
// 2. Identify all the uses that don't really count (as above).
|
||||
// 3. Everything else must be a use of a variable.
|
||||
// 4. For each variable, check if any uses occur before the declaration
|
||||
// and warn about them.
|
||||
//
|
||||
// We also keep track of where lexical scope becomes unknowable (e.g.,
|
||||
// because the function calls extract() or uses dynamic variables,
|
||||
// preventing us from keeping track of which variables are defined) so we
|
||||
// can stop issuing warnings after that.
|
||||
//
|
||||
// TODO: Support functions defined inside other functions which is commonly
|
||||
// used with anonymous functions.
|
||||
|
||||
$defs = $root->selectDescendantsOfTypes(array(
|
||||
'n_FUNCTION_DECLARATION',
|
||||
'n_METHOD_DECLARATION',
|
||||
));
|
||||
|
||||
foreach ($defs as $def) {
|
||||
|
||||
// We keep track of the first offset where scope becomes unknowable, and
|
||||
// silence any warnings after that. Default it to INT_MAX so we can min()
|
||||
// it later to keep track of the first problem we encounter.
|
||||
$scope_destroyed_at = PHP_INT_MAX;
|
||||
|
||||
$declarations = array(
|
||||
'$this' => 0,
|
||||
) + array_fill_keys($this->getSuperGlobalNames(), 0);
|
||||
$declaration_tokens = array();
|
||||
$exclude_tokens = array();
|
||||
$vars = array();
|
||||
|
||||
// First up, find all the different kinds of declarations, as explained
|
||||
// above. Put the tokens into the $vars array.
|
||||
|
||||
$param_list = $def->getChildOfType(3, 'n_DECLARATION_PARAMETER_LIST');
|
||||
$param_vars = $param_list->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($param_vars as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
|
||||
// This is PHP5.3 closure syntax: function () use ($x) {};
|
||||
$lexical_vars = $def
|
||||
->getChildByIndex(4)
|
||||
->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($lexical_vars as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
|
||||
$body = $def->getChildByIndex(5);
|
||||
if ($body->getTypeName() === 'n_EMPTY') {
|
||||
// Abstract method declaration.
|
||||
continue;
|
||||
}
|
||||
|
||||
$static_vars = $body
|
||||
->selectDescendantsOfType('n_STATIC_DECLARATION')
|
||||
->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($static_vars as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
|
||||
|
||||
$global_vars = $body
|
||||
->selectDescendantsOfType('n_GLOBAL_DECLARATION_LIST');
|
||||
foreach ($global_vars as $var_list) {
|
||||
foreach ($var_list->getChildren() as $var) {
|
||||
if ($var->getTypeName() === 'n_VARIABLE') {
|
||||
$vars[] = $var;
|
||||
} else {
|
||||
// Dynamic global variable, i.e. "global $$x;".
|
||||
$scope_destroyed_at = min($scope_destroyed_at, $var->getOffset());
|
||||
// An error is raised elsewhere, no need to raise here.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Include "catch (Exception $ex)", but not variables in the body of the
|
||||
// catch block.
|
||||
$catches = $body->selectDescendantsOfType('n_CATCH');
|
||||
foreach ($catches as $catch) {
|
||||
$vars[] = $catch->getChildOfType(1, 'n_VARIABLE');
|
||||
}
|
||||
|
||||
$binary = $body->selectDescendantsOfType('n_BINARY_EXPRESSION');
|
||||
foreach ($binary as $expr) {
|
||||
if ($expr->getChildByIndex(1)->getConcreteString() !== '=') {
|
||||
continue;
|
||||
}
|
||||
$lval = $expr->getChildByIndex(0);
|
||||
if ($lval->getTypeName() === 'n_VARIABLE') {
|
||||
$vars[] = $lval;
|
||||
} else if ($lval->getTypeName() === 'n_LIST') {
|
||||
// Recursively grab everything out of list(), since the grammar
|
||||
// permits list() to be nested. Also note that list() is ONLY valid
|
||||
// as an lval assignments, so we could safely lift this out of the
|
||||
// n_BINARY_EXPRESSION branch.
|
||||
$assign_vars = $lval->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($assign_vars as $var) {
|
||||
$vars[] = $var;
|
||||
}
|
||||
}
|
||||
|
||||
if ($lval->getTypeName() === 'n_VARIABLE_VARIABLE') {
|
||||
$scope_destroyed_at = min($scope_destroyed_at, $lval->getOffset());
|
||||
// No need to raise here since we raise an error elsewhere.
|
||||
}
|
||||
}
|
||||
|
||||
$calls = $body->selectDescendantsOfType('n_FUNCTION_CALL');
|
||||
foreach ($calls as $call) {
|
||||
$name = strtolower($call->getChildByIndex(0)->getConcreteString());
|
||||
|
||||
if ($name === 'empty' || $name === 'isset') {
|
||||
$params = $call
|
||||
->getChildOfType(1, 'n_CALL_PARAMETER_LIST')
|
||||
->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($params as $var) {
|
||||
$exclude_tokens[$var->getID()] = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($name !== 'extract') {
|
||||
continue;
|
||||
}
|
||||
$scope_destroyed_at = min($scope_destroyed_at, $call->getOffset());
|
||||
}
|
||||
|
||||
// Now we have every declaration except foreach(), handled below. Build
|
||||
// two maps, one which just keeps track of which tokens are part of
|
||||
// declarations ($declaration_tokens) and one which has the first offset
|
||||
// where a variable is declared ($declarations).
|
||||
|
||||
foreach ($vars as $var) {
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$declarations[$concrete] = min(
|
||||
idx($declarations, $concrete, PHP_INT_MAX),
|
||||
$var->getOffset());
|
||||
$declaration_tokens[$var->getID()] = true;
|
||||
}
|
||||
|
||||
// Excluded tokens are ones we don't "count" as being used, described
|
||||
// above. Put them into $exclude_tokens.
|
||||
|
||||
$class_statics = $body
|
||||
->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
|
||||
$class_static_vars = $class_statics
|
||||
->selectDescendantsOfType('n_VARIABLE');
|
||||
foreach ($class_static_vars as $var) {
|
||||
$exclude_tokens[$var->getID()] = true;
|
||||
}
|
||||
|
||||
|
||||
// Find all the variables in scope, and figure out where they are used.
|
||||
// We want to find foreach() iterators which are both declared before and
|
||||
// used after the foreach() loop.
|
||||
|
||||
$uses = array();
|
||||
|
||||
$all_vars = $body->selectDescendantsOfType('n_VARIABLE');
|
||||
$all = array();
|
||||
|
||||
// NOTE: $all_vars is not a real array so we can't unset() it.
|
||||
foreach ($all_vars as $var) {
|
||||
|
||||
// Be strict since it's easier; we don't let you reuse an iterator you
|
||||
// declared before a loop after the loop, even if you're just assigning
|
||||
// to it.
|
||||
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$uses[$concrete][$var->getID()] = $var->getOffset();
|
||||
|
||||
if (isset($declaration_tokens[$var->getID()])) {
|
||||
// We know this is part of a declaration, so it's fine.
|
||||
continue;
|
||||
}
|
||||
if (isset($exclude_tokens[$var->getID()])) {
|
||||
// We know this is part of isset() or similar, so it's fine.
|
||||
continue;
|
||||
}
|
||||
|
||||
$all[$var->getOffset()] = $concrete;
|
||||
}
|
||||
|
||||
|
||||
// Do foreach() last, we want to handle implicit redeclaration of a
|
||||
// variable already in scope since this probably means we're ovewriting a
|
||||
// local.
|
||||
|
||||
// NOTE: Processing foreach expressions in order allows programs which
|
||||
// reuse iterator variables in other foreach() loops -- this is fine. We
|
||||
// have a separate warning to prevent nested loops from reusing the same
|
||||
// iterators.
|
||||
|
||||
$foreaches = $body->selectDescendantsOfType('n_FOREACH');
|
||||
$all_foreach_vars = array();
|
||||
foreach ($foreaches as $foreach) {
|
||||
$foreach_expr = $foreach->getChildOfType(0, 'n_FOREACH_EXPRESSION');
|
||||
|
||||
$foreach_vars = array();
|
||||
|
||||
// Determine the end of the foreach() loop.
|
||||
$foreach_tokens = $foreach->getTokens();
|
||||
$last_token = end($foreach_tokens);
|
||||
$foreach_end = $last_token->getOffset();
|
||||
|
||||
$key_var = $foreach_expr->getChildByIndex(1);
|
||||
if ($key_var->getTypeName() === 'n_VARIABLE') {
|
||||
$foreach_vars[] = $key_var;
|
||||
}
|
||||
|
||||
$value_var = $foreach_expr->getChildByIndex(2);
|
||||
if ($value_var->getTypeName() === 'n_VARIABLE') {
|
||||
$foreach_vars[] = $value_var;
|
||||
} else {
|
||||
// The root-level token may be a reference, as in:
|
||||
// foreach ($a as $b => &$c) { ... }
|
||||
// Reach into the n_VARIABLE_REFERENCE node to grab the n_VARIABLE
|
||||
// node.
|
||||
$var = $value_var->getChildByIndex(0);
|
||||
if ($var->getTypeName() === 'n_VARIABLE_VARIABLE') {
|
||||
$var = $var->getChildByIndex(0);
|
||||
}
|
||||
$foreach_vars[] = $var;
|
||||
}
|
||||
|
||||
// Remove all uses of the iterators inside of the foreach() loop from
|
||||
// the $uses map.
|
||||
|
||||
foreach ($foreach_vars as $var) {
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$offset = $var->getOffset();
|
||||
|
||||
foreach ($uses[$concrete] as $id => $use_offset) {
|
||||
if (($use_offset >= $offset) && ($use_offset < $foreach_end)) {
|
||||
unset($uses[$concrete][$id]);
|
||||
}
|
||||
}
|
||||
|
||||
$all_foreach_vars[] = $var;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($all_foreach_vars as $var) {
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$offset = $var->getOffset();
|
||||
|
||||
// This is a declaration, exclude it from the "declare variables prior
|
||||
// to use" check below.
|
||||
unset($all[$var->getOffset()]);
|
||||
|
||||
$vars[] = $var;
|
||||
}
|
||||
|
||||
// Now rebuild declarations to include foreach().
|
||||
|
||||
foreach ($vars as $var) {
|
||||
$concrete = $this->getConcreteVariableString($var);
|
||||
$declarations[$concrete] = min(
|
||||
idx($declarations, $concrete, PHP_INT_MAX),
|
||||
$var->getOffset());
|
||||
$declaration_tokens[$var->getID()] = true;
|
||||
}
|
||||
|
||||
foreach (array('n_STRING_SCALAR', 'n_HEREDOC') as $type) {
|
||||
foreach ($body->selectDescendantsOfType($type) as $string) {
|
||||
foreach ($string->getStringVariables() as $offset => $var) {
|
||||
$all[$string->getOffset() + $offset - 1] = '$'.$var;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Issue a warning for every variable token, unless it appears in a
|
||||
// declaration, we know about a prior declaration, we have explicitly
|
||||
// excluded it, or scope has been made unknowable before it appears.
|
||||
|
||||
$issued_warnings = array();
|
||||
foreach ($all as $offset => $concrete) {
|
||||
if ($offset >= $scope_destroyed_at) {
|
||||
// This appears after an extract() or $$var so we have no idea
|
||||
// whether it's legitimate or not. We raised a harshly-worded warning
|
||||
// when scope was made unknowable, so just ignore anything we can't
|
||||
// figure out.
|
||||
continue;
|
||||
}
|
||||
if ($offset >= idx($declarations, $concrete, PHP_INT_MAX)) {
|
||||
// The use appears after the variable is declared, so it's fine.
|
||||
continue;
|
||||
}
|
||||
if (!empty($issued_warnings[$concrete])) {
|
||||
// We've already issued a warning for this variable so we don't need
|
||||
// to issue another one.
|
||||
continue;
|
||||
}
|
||||
$this->raiseLintAtOffset(
|
||||
$offset,
|
||||
pht(
|
||||
'Declare variables prior to use (even if you are passing them '.
|
||||
'as reference parameters). You may have misspelled this '.
|
||||
'variable name.'),
|
||||
$concrete);
|
||||
$issued_warnings[$concrete] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistUnnecessaryFinalModifierXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 55;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Unnecessary Final Modifier');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$attributes = $class->getChildOfType(0, 'n_CLASS_ATTRIBUTES');
|
||||
$is_final = false;
|
||||
|
||||
foreach ($attributes->getChildren() as $attribute) {
|
||||
if ($attribute->getConcreteString() == 'final') {
|
||||
$is_final = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$is_final) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
foreach ($methods as $method) {
|
||||
$attributes = $method->getChildOfType(0, 'n_METHOD_MODIFIER_LIST');
|
||||
|
||||
foreach ($attributes->getChildren() as $attribute) {
|
||||
if ($attribute->getConcreteString() == 'final') {
|
||||
$this->raiseLintAtNode(
|
||||
$attribute,
|
||||
pht(
|
||||
'Unnecessary %s modifier in %s class.',
|
||||
'final',
|
||||
'final'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistUnnecessarySemicolonXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 56;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Unnecessary Semicolon');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$statements = $root->selectDescendantsOfType('n_STATEMENT');
|
||||
|
||||
foreach ($statements as $statement) {
|
||||
if ($statement->getParentNode()->getTypeName() == 'n_DECLARE') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($statement->getChildren()) > 1) {
|
||||
continue;
|
||||
} else if ($statement->getChildByIndex(0)->getTypeName() != 'n_EMPTY') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($statement->getConcreteString() == ';') {
|
||||
$this->raiseLintAtNode(
|
||||
$statement,
|
||||
pht('Unnecessary semicolons after statement.'),
|
||||
'');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistUselessOverridingMethodXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 63;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Useless Overriding Method');
|
||||
}
|
||||
|
||||
public function getLintSeverity() {
|
||||
return ArcanistLintSeverity::SEVERITY_ADVICE;
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$methods = $root->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$method_name = $method
|
||||
->getChildOfType(2, 'n_STRING')
|
||||
->getConcreteString();
|
||||
|
||||
$parameter_list = $method
|
||||
->getChildOfType(3, 'n_DECLARATION_PARAMETER_LIST');
|
||||
$parameters = array();
|
||||
|
||||
foreach ($parameter_list->getChildren() as $parameter) {
|
||||
$parameter = $parameter->getChildByIndex(1);
|
||||
|
||||
if ($parameter->getTypeName() == 'n_VARIABLE_REFERENCE') {
|
||||
$parameter = $parameter->getChildOfType(0, 'n_VARIABLE');
|
||||
}
|
||||
|
||||
$parameters[] = $parameter->getConcreteString();
|
||||
}
|
||||
|
||||
$statements = $method->getChildByIndex(5);
|
||||
|
||||
if ($statements->getTypeName() != 'n_STATEMENT_LIST') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($statements->getChildren()) != 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$statement = $statements
|
||||
->getChildOfType(0, 'n_STATEMENT')
|
||||
->getChildByIndex(0);
|
||||
|
||||
if ($statement->getTypeName() == 'n_RETURN') {
|
||||
$statement = $statement->getChildByIndex(0);
|
||||
}
|
||||
|
||||
if ($statement->getTypeName() != 'n_FUNCTION_CALL') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$function = $statement->getChildByIndex(0);
|
||||
|
||||
if ($function->getTypeName() != 'n_CLASS_STATIC_ACCESS') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$called_class = $function->getChildOfType(0, 'n_CLASS_NAME');
|
||||
$called_method = $function->getChildOfType(1, 'n_STRING');
|
||||
|
||||
if ($called_class->getConcreteString() != 'parent') {
|
||||
continue;
|
||||
} else if ($called_method->getConcreteString() != $method_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$params = $statement
|
||||
->getChildOfType(1, 'n_CALL_PARAMETER_LIST')
|
||||
->getChildren();
|
||||
|
||||
foreach ($params as $param) {
|
||||
if ($param->getTypeName() != 'n_VARIABLE') {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$expected = array_shift($parameters);
|
||||
|
||||
if ($param->getConcreteString() != $expected) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$this->raiseLintAtNode(
|
||||
$method,
|
||||
pht('Useless overriding method.'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistVariableVariableXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 3;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('Use of Variable Variable');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$vvars = $root->selectDescendantsOfType('n_VARIABLE_VARIABLE');
|
||||
|
||||
foreach ($vvars as $vvar) {
|
||||
$this->raiseLintAtNode(
|
||||
$vvar,
|
||||
pht(
|
||||
'Rewrite this code to use an array. Variable variables are unclear '.
|
||||
'and hinder static analysis.'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue